|
|
1 | ################################################################################ |
|
|
2 | # WeBWorK Online Homework Delivery System |
|
|
3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
|
|
4 | # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm,v 1.49 2004/05/13 16:02:55 toenail Exp $ |
|
|
5 | # |
|
|
6 | # This program is free software; you can redistribute it and/or modify it under |
|
|
7 | # the terms of either: (a) the GNU General Public License as published by the |
|
|
8 | # Free Software Foundation; either version 2, or (at your option) any later |
|
|
9 | # version, or (b) the "Artistic License" which comes with this package. |
|
|
10 | # |
|
|
11 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|
|
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
|
13 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the |
|
|
14 | # Artistic License for more details. |
|
|
15 | ################################################################################ |
|
|
16 | |
| 1 | package WeBWorK::ContentGenerator::Instructor::ProblemSetList; |
17 | package WeBWorK::ContentGenerator::Instructor::ProblemSetList; |
| 2 | use base qw(WeBWorK::ContentGenerator::Instructor); |
18 | use base qw(WeBWorK::ContentGenerator::Instructor); |
| 3 | |
19 | |
| 4 | =head1 NAME |
20 | =head1 NAME |
| 5 | |
21 | |
| … | |
… | |
| 7 | |
23 | |
| 8 | =cut |
24 | =cut |
| 9 | |
25 | |
| 10 | use strict; |
26 | use strict; |
| 11 | use warnings; |
27 | use warnings; |
|
|
28 | use Apache::Constants qw(REDIRECT); |
| 12 | use CGI qw(); |
29 | use CGI qw(); |
|
|
30 | use WeBWorK::Utils qw(formatDateTime); |
|
|
31 | use WeBWorK::Compatibility; |
| 13 | |
32 | |
| 14 | sub title { |
33 | use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts continuation)]; |
|
|
34 | |
|
|
35 | sub header { |
| 15 | my $self = shift; |
36 | my $self = shift; |
| 16 | return "Instructor Tools - Problem Set List for ".$self->{ce}->{courseName}; |
37 | my $r = $self->r; |
|
|
38 | my $urlpath = $r->urlpath; |
|
|
39 | my $ce = $r->ce; |
|
|
40 | my $courseName = $urlpath->arg("courseID"); |
|
|
41 | my $scoringPage = $urlpath -> newFromModule('WeBWorK::ContentGenerator::Instructor::Scoring', |
|
|
42 | courseID => $courseName |
|
|
43 | ); |
|
|
44 | if (defined $r->param('scoreSelected')) { |
|
|
45 | my $scoringPageURL = $self->systemLink($scoringPage, |
|
|
46 | params=>['scoreSelected','selectedSet','recordSingleSetScores' ] |
|
|
47 | ); |
|
|
48 | $r->header_out(Location => $scoringPageURL); |
|
|
49 | $self->{noContent} = 1; |
|
|
50 | return REDIRECT; |
|
|
51 | } |
|
|
52 | $r->content_type("text/html"); |
|
|
53 | $r->send_http_header(); |
| 17 | } |
54 | } |
| 18 | |
55 | |
|
|
56 | sub initialize { |
|
|
57 | my $self = shift; |
|
|
58 | my $r = $self->r; |
|
|
59 | my $urlpath = $r->urlpath; |
|
|
60 | my $db = $r->db; |
|
|
61 | my $ce = $r->ce; |
|
|
62 | my $authz = $r->authz; |
|
|
63 | my $courseName = $urlpath->arg("courseID"); |
|
|
64 | my $user = $r->param('user'); |
|
|
65 | |
|
|
66 | unless ($authz->hasPermissions($user, "create_and_delete_problem_sets")) { |
|
|
67 | $self->addmessage(CGI::div({class=>"ResultsWithError"}, CGI::p("You aren't authorized to create or delete problems"))); |
|
|
68 | return; |
|
|
69 | } |
|
|
70 | if (defined($r->param('update_global_user')) ){ |
|
|
71 | my $global_user_message = WeBWorK::Compatibility::update_global_user($self); |
|
|
72 | $self->{global_user_message} = $global_user_message; |
|
|
73 | } |
|
|
74 | if (defined($r->param('deleteSelected')) and defined($r->param('deleteSelectedSafety') ) |
|
|
75 | and $r->param('deleteSelectedSafety') == 1) { |
|
|
76 | |
|
|
77 | foreach my $wannaDelete ($r->param('selectedSet')) { |
|
|
78 | $db->deleteGlobalSet($wannaDelete); |
|
|
79 | } |
|
|
80 | } elsif (defined $r->param('scoreSelected')) { |
|
|
81 | # FIXME: this doesn't do anything! |
|
|
82 | } elsif (defined $r->param('makeNewSet')) { |
|
|
83 | my $newSetRecord = $db->{set}->{record}->new(); |
|
|
84 | my $newSetName = $r->param('newSetName'); |
|
|
85 | $newSetRecord->set_id($newSetName); |
|
|
86 | $newSetRecord->set_header(""); |
|
|
87 | $newSetRecord->problem_header(""); |
|
|
88 | $newSetRecord->open_date("0"); |
|
|
89 | $newSetRecord->due_date("0"); |
|
|
90 | $newSetRecord->answer_date("0"); |
|
|
91 | eval {$db->addGlobalSet($newSetRecord)}; |
|
|
92 | } elsif (defined $r->param('importSet') or defined $r->param('importSets')) { |
|
|
93 | my @setDefFiles = (); |
|
|
94 | my $newSetName = ""; |
|
|
95 | if (defined $r->param('importSet')) { |
|
|
96 | @setDefFiles = $r->param('set_definition_file'); |
|
|
97 | $newSetName = $r->param('newSetName'); |
|
|
98 | } elsif (defined $r->param('importSets')) { |
|
|
99 | @setDefFiles = $r->param('set_definition_files'); |
|
|
100 | } |
|
|
101 | |
|
|
102 | foreach my $set_definition_file (@setDefFiles) { |
|
|
103 | $WeBWorK::timer->continue("$set_definition_file: reading set definition file") if defined $WeBWorK::timer; |
|
|
104 | # read data in set definition file |
|
|
105 | my ($setName, $paperHeaderFile, $screenHeaderFile, |
|
|
106 | $openDate, $dueDate, $answerDate, $ra_problemData, |
|
|
107 | ) = $self->readSetDef($set_definition_file); |
|
|
108 | my @problemList = @{$ra_problemData}; |
|
|
109 | |
|
|
110 | # Use the original name if form doesn't specify a new one. |
|
|
111 | # The set acquires the new name specified by the form. A blank |
|
|
112 | # entry on the form indicates that the imported set name will be used. |
|
|
113 | $setName = $newSetName if $newSetName; |
|
|
114 | |
|
|
115 | $WeBWorK::timer->continue("$set_definition_file: adding set") if defined $WeBWorK::timer; |
|
|
116 | # add the data to the set record |
|
|
117 | #my $newSetRecord = $db->{set}->{record}->new(); |
|
|
118 | my $newSetRecord = $db->newGlobalSet; |
|
|
119 | $newSetRecord->set_id($setName); |
|
|
120 | $newSetRecord->set_header($screenHeaderFile); |
|
|
121 | $newSetRecord->problem_header($paperHeaderFile); |
|
|
122 | $newSetRecord->open_date($openDate); |
|
|
123 | $newSetRecord->due_date($dueDate); |
|
|
124 | $newSetRecord->answer_date($answerDate); |
|
|
125 | |
|
|
126 | #create the set |
|
|
127 | eval {$db->addGlobalSet($newSetRecord)}; |
|
|
128 | die "addGlobalSet $setName in ProblemSetList: $@" if $@; |
|
|
129 | |
|
|
130 | $WeBWorK::timer->continue("$set_definition_file: adding problems to database") if defined $WeBWorK::timer; |
|
|
131 | # add problems |
|
|
132 | my $freeProblemID = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1; |
|
|
133 | foreach my $rh_problem (@problemList) { |
|
|
134 | #my $problemRecord = new WeBWorK::DB::Record::Problem; |
|
|
135 | my $problemRecord = $db->newGlobalProblem; |
|
|
136 | $problemRecord->problem_id($freeProblemID++); |
|
|
137 | #warn "Adding problem $freeProblemID ", $rh_problem->source_file; |
|
|
138 | $problemRecord->set_id($setName); |
|
|
139 | $problemRecord->source_file($rh_problem->{source_file}); |
|
|
140 | $problemRecord->value($rh_problem->{value}); |
|
|
141 | $problemRecord->max_attempts($rh_problem->{max_attempts}); |
|
|
142 | # continuation flags??? |
|
|
143 | $db->addGlobalProblem($problemRecord); |
|
|
144 | #$self->assignProblemToAllSetUsers($problemRecord); # handled by parent |
|
|
145 | } |
|
|
146 | |
|
|
147 | if ($r->param("assignNewSet")) { |
|
|
148 | $WeBWorK::timer->continue("$set_definition_file: assigning set to all users") if defined $WeBWorK::timer; |
|
|
149 | # assign the set to all users |
|
|
150 | $self->assignSetToAllUsers($setName); |
|
|
151 | } |
|
|
152 | } |
|
|
153 | } |
|
|
154 | } |
|
|
155 | |
|
|
156 | |
|
|
157 | |
|
|
158 | |
| 19 | sub body { |
159 | sub body { |
|
|
160 | my $self = shift; |
|
|
161 | my $r = $self->r; |
|
|
162 | my $urlpath = $r->urlpath; |
|
|
163 | my $db = $r->db; |
|
|
164 | my $ce = $r->ce; |
|
|
165 | my $authz = $r->authz; |
|
|
166 | my $root = $ce->{webworkURLs}->{root}; |
|
|
167 | my $courseName = $urlpath->arg("courseID"); |
|
|
168 | my $user = $r->param('user'); |
|
|
169 | my $key = $r->param('key'); |
|
|
170 | my $effectiveUserName = $r->param('effectiveUser'); |
|
|
171 | |
|
|
172 | my $problemSetListPage = $urlpath->newFromModule($urlpath->module, courseID => $courseName) ; |
|
|
173 | my $problemSetListURL = $self->systemLink($problemSetListPage, authen=>0); |
|
|
174 | |
|
|
175 | #my $instructorBaseURL = "$root/$courseName/instructor"; |
|
|
176 | #my $importURL = "$instructorBaseURL/problemSetImport/"; |
|
|
177 | my $instructorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::Index", |
|
|
178 | courseID => $courseName |
|
|
179 | ); |
|
|
180 | my $sort = $r->param('sort') ? $r->param('sort') : "due_date"; |
|
|
181 | |
|
|
182 | my @set_definition_files = $self->read_dir($ce->{courseDirs}->{templates},'\\.def'); |
|
|
183 | return CGI::em("You are not authorized to access the instructor tools") |
|
|
184 | unless $authz->hasPermissions($user, "access_instructor_tools"); |
|
|
185 | |
|
|
186 | ############################################################################### |
|
|
187 | # Slurp each set record for this course in @sets |
|
|
188 | # Gather data from the database |
|
|
189 | ############################################################################### |
|
|
190 | my @users = $db->listUsers; |
|
|
191 | ############################################################################### |
|
|
192 | # for compatibility check to make sure that the global user has been defined. |
|
|
193 | ############################################################################### |
|
|
194 | my $globalUser = $ce->{dbLayout}->{set}->{params}->{globalUserID}; |
|
|
195 | my $database_type = $ce->{dbLayoutName}; |
|
|
196 | my $global_user_alert = ""; |
|
|
197 | my $global_user_message = ""; |
|
|
198 | $global_user_message = $self->{global_user_message} if defined($self->{global_user_message}); |
|
|
199 | if (defined($globalUser) and $globalUser and $database_type eq 'gdbm') { # if a name for the global user has been defined in database.conf |
|
|
200 | my $flag = 0; |
|
|
201 | foreach $user (@users) { |
|
|
202 | $flag = 1 if $user eq $globalUser; |
|
|
203 | } |
|
|
204 | |
|
|
205 | if ($flag == 0 ) { # no global user has been added to this course |
|
|
206 | $global_user_alert = join("", |
|
|
207 | CGI::div({class=>'ResultsWithError'}, |
|
|
208 | CGI::p("This is the first time that this course |
|
|
209 | has been used with WeBWorK 2.0 and no 'Global User' has been defined. In WeBWorK 2.0 |
|
|
210 | a set and it's problems can exist without being assigned to any real student or instructor -- |
|
|
211 | instead it is assigned to this fictional 'Global User' whose id is $globalUser. |
|
|
212 | Press this button to initialize the global user files so that you can view sets built in |
|
|
213 | WW1.9 in 2.0. You should only need to do this once -- the first time you use WW2.0 on a pre-existing course." |
|
|
214 | ), |
|
|
215 | CGI::p("Depending on the number of sets, this operation may take many minutes. Even if your browser times out |
|
|
216 | the updating process will continue until it is done. Time for coffee? :-)" |
|
|
217 | ), |
|
|
218 | CGI::start_form({"method"=>"POST", "action"=>$problemSetListURL}), |
|
|
219 | $self->hidden_authen_fields, |
|
|
220 | CGI::submit({-name=>'update_global_user', -value=>"Update Global User" }), |
|
|
221 | CGI::end_form(), |
|
|
222 | ), |
|
|
223 | ); |
|
|
224 | #$global_user_message = WeBWorK::Compatibility::update_global_user($self); |
|
|
225 | } |
|
|
226 | } |
|
|
227 | ############################################################################### |
|
|
228 | # end compatibility check |
|
|
229 | ############################################################################### |
|
|
230 | my @set_IDs = $db->listGlobalSets; |
|
|
231 | my @sets = $db->getGlobalSets(@set_IDs); #checked |
|
|
232 | my %counts; |
|
|
233 | my %problemCounts; |
|
|
234 | |
|
|
235 | |
|
|
236 | $WeBWorK::timer->continue("Begin obtaining problem info on sets") if defined $WeBWorK::timer; |
|
|
237 | foreach my $set_id (@set_IDs) { |
|
|
238 | $problemCounts{$set_id} = scalar($db->listGlobalProblems($set_id)); |
|
|
239 | #$counts{$set_id} = $db->listSetUsers($set_id); |
|
|
240 | } |
|
|
241 | $WeBWorK::timer->continue("End obtaining problem on sets") if defined $WeBWorK::timer; |
|
|
242 | |
|
|
243 | $WeBWorK::timer->continue("Begin obtaining assigned user info on sets") if defined $WeBWorK::timer; |
|
|
244 | foreach my $set_id (@set_IDs) { |
|
|
245 | #$problemCounts{$set_id} = scalar($db->listGlobalProblems($set_id)); |
|
|
246 | #$counts{$set_id} = $db->listSetUsers($set_id); |
|
|
247 | $counts{$set_id} = $db->countSetUsers($set_id); |
|
|
248 | } |
|
|
249 | $WeBWorK::timer->continue("End obtaining assigned user info on sets") if defined $WeBWorK::timer; |
| 20 | |
250 | |
|
|
251 | # Sort @sets based on the sort parameter |
|
|
252 | # Invalid sort types will just cause an unpredictable ordering, which is no big deal. |
|
|
253 | @sets = sort { |
|
|
254 | if ($sort eq "set_id") { |
|
|
255 | return $a->$sort cmp $b->$sort; |
|
|
256 | }elsif ($sort =~ /_date$/) { |
|
|
257 | return $a->$sort <=> $b->$sort; |
|
|
258 | } elsif ($sort eq "num_probs") { |
|
|
259 | return $problemCounts{$a->set_id} <=> $problemCounts{$b->set_id}; |
|
|
260 | } elsif ($sort eq "num_students") { |
|
|
261 | return $counts{$a->set_id} <=> $counts{$b->set_id}; |
|
|
262 | } |
|
|
263 | } @sets; |
|
|
264 | |
|
|
265 | my $table = CGI::Tr({}, |
|
|
266 | CGI::th([ |
|
|
267 | "Sel.", |
|
|
268 | CGI::a({"href"=>$self->systemLink($problemSetListPage,params=>{sort=>'set_id' })}, "Sort by name" ), |
|
|
269 | CGI::a({"href"=>$self->systemLink($problemSetListPage,params=>{sort=>'open_date' })}, "Sort by Open Date" ), |
|
|
270 | CGI::a({"href"=>$self->systemLink($problemSetListPage,params=>{sort=>'due_date' })}, "Sort by Due Date" ), |
|
|
271 | CGI::a({"href"=>$self->systemLink($problemSetListPage,params=>{sort=>'answer_date' })}, "Sort by Answer Date" ), |
|
|
272 | "Edit problems", |
|
|
273 | "Assign users" , |
|
|
274 | ]) |
|
|
275 | ); |
|
|
276 | |
|
|
277 | foreach my $set (@sets) { |
|
|
278 | my $count = $counts{$set->set_id}; |
|
|
279 | my $totalUsers = scalar(@users); #FIXME -- probably shouldn't count those who have dropped. |
|
|
280 | my $userCountMessage = $self->userCountMessage($count, scalar(@users)); |
|
|
281 | my $setEditorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemSetEditor", |
|
|
282 | courseID => $courseName, |
|
|
283 | setID => $set->set_id, |
|
|
284 | ); |
|
|
285 | my $problemListPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemList", |
|
|
286 | courseID => $courseName, |
|
|
287 | setID => $set->set_id, |
|
|
288 | ); |
|
|
289 | my $usersAssignedToSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", |
|
|
290 | courseID => $courseName, |
|
|
291 | setID => $set->set_id, |
|
|
292 | ); |
|
|
293 | my $problemSetEditorPage = $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::ProblemSetEditor', |
|
|
294 | courseID => $courseName, |
|
|
295 | setID => $set->set_id, |
|
|
296 | ); |
|
|
297 | |
|
|
298 | my $publishedClass = ($set->published) ? "Published" : "Unpublished"; |
|
|
299 | $table .= CGI::Tr({}, |
|
|
300 | CGI::td([ |
|
|
301 | CGI::checkbox({ |
|
|
302 | "name"=>"selectedSet", |
|
|
303 | "value"=>$set->set_id, |
|
|
304 | "label"=>"", |
|
|
305 | "checked"=>"0" |
|
|
306 | }), |
|
|
307 | CGI::font({class=>$publishedClass}, ' '.$set->set_id) . ' '.CGI::a({href=>$self->systemLink($setEditorPage)}, 'Edit'), |
|
|
308 | formatDateTime($set->open_date), |
|
|
309 | formatDateTime($set->due_date), |
|
|
310 | formatDateTime($set->answer_date), |
|
|
311 | CGI::a({href=>$self->systemLink($problemListPage )}, $problemCounts{$set->set_id}), |
|
|
312 | CGI::a({href=>$self->systemLink($usersAssignedToSetPage)}, "$count/$totalUsers") , |
|
|
313 | ]) |
|
|
314 | ) . "\n" |
|
|
315 | } |
|
|
316 | $table = CGI::table({"border"=>"1"}, "\n".$table."\n"); |
|
|
317 | |
|
|
318 | my $slownessWarning = ""; |
|
|
319 | if ($ce->{dbLayoutName} eq "sql") { |
|
|
320 | $slownessWarning = "In this version of WeBWorK, assigning sets is very slow. Your browser" |
|
|
321 | . " may time out if you import many sets or have many users to whom to assign them. If this" |
|
|
322 | . " happens, click your browser's reload button. This issue will be resolved in a later" |
|
|
323 | . " version of WeBWorK. (Hopefully the next version!)" |
|
|
324 | . CGI::br(); |
|
|
325 | } |
|
|
326 | |
|
|
327 | print join("\n", |
|
|
328 | $global_user_alert, |
|
|
329 | $global_user_message, |
|
|
330 | # Set table form (for deleting checked sets) |
|
|
331 | CGI::start_form({"method"=>"POST", "action"=>$problemSetListURL}), |
|
|
332 | $self->hidden_authen_fields, |
|
|
333 | $table, |
|
|
334 | CGI::br(), |
|
|
335 | |
|
|
336 | CGI::submit({"name"=>"scoreSelected", "label"=>"Score Selected"}), |
|
|
337 | CGI::div( {class=>'ResultsWithError'}, |
|
|
338 | "There is NO undo when deleting sets. Use cautiously. All data for the set is lost. |
|
|
339 | <br>If the set is re-assigned (rebuilt) all of the problem versions will be different.", |
|
|
340 | CGI::br(), |
|
|
341 | CGI::submit({"name"=>"deleteSelected", "label"=>"Delete Selected"}), |
|
|
342 | CGI::radio_group(-name=>"deleteSelectedSafety", -values=>[0,1], -default=>0, -labels=>{0=>'Read only', 1=>'Allow delete'}), |
|
|
343 | |
|
|
344 | ), |
|
|
345 | CGI::end_form(), |
|
|
346 | CGI::br(), |
|
|
347 | |
|
|
348 | # Empty set creation form |
|
|
349 | CGI::start_form({"method"=>"POST", "action"=>$problemSetListURL}), |
|
|
350 | $self->hidden_authen_fields, |
|
|
351 | CGI::b("Create an Empty Set"), |
|
|
352 | CGI::br(), |
|
|
353 | "New Set Name: ", |
|
|
354 | CGI::input({type=>"text", name=>"newSetName", value=>""}), |
|
|
355 | CGI::submit({"name"=>"makeNewSet", "label"=>"Create"}), |
|
|
356 | CGI::end_form(), |
|
|
357 | CGI::br(), |
|
|
358 | |
|
|
359 | # Single set import form |
|
|
360 | CGI::hr(), |
|
|
361 | CGI::start_form({"method"=>"POST", "action"=>$problemSetListURL}), |
|
|
362 | $self->hidden_authen_fields, |
|
|
363 | CGI::b("Import a Single Set"), |
|
|
364 | CGI::br(), |
|
|
365 | "From file: ", |
|
|
366 | CGI::popup_menu(-name=>'set_definition_file', -values=>\@set_definition_files), |
|
|
367 | CGI::br(), |
|
|
368 | "Set name: ", |
|
|
369 | CGI::input({type=>"text", name=>"newSetName", value=>""}), |
|
|
370 | " (leave blank to use name of set definition file)", |
|
|
371 | CGI::br(), |
|
|
372 | CGI::checkbox(-name=>"assignNewSet", |
|
|
373 | -label=>"Assign imported set to all current users"), |
|
|
374 | CGI::br(), |
|
|
375 | CGI::submit({"name"=>"importSet", "label"=>"Import a Single Set"}), |
|
|
376 | CGI::end_form(), |
|
|
377 | CGI::br(), |
|
|
378 | |
|
|
379 | # Multiple set import form |
|
|
380 | CGI::hr(), |
|
|
381 | CGI::start_form({"method"=>"POST", "action"=>$problemSetListURL}), |
|
|
382 | $self->hidden_authen_fields, |
|
|
383 | CGI::b("Import Multiple Sets"), |
|
|
384 | CGI::br(), |
|
|
385 | "Each set will be named based on the name of the set definition file, omitting", |
|
|
386 | " any leading ", CGI::i("set"), " and trailing ", CGI::i(".def"), ". Note that", |
|
|
387 | " the name of a set cannot be changed once it has been created.", |
|
|
388 | CGI::br(), |
|
|
389 | CGI::scrolling_list(-name=>"set_definition_files", -values=>\@set_definition_files, -size=>10, -multiple=>"true"), |
|
|
390 | CGI::br(), |
|
|
391 | CGI::checkbox(-name=>"assignNewSet", -label=>"Assign imported sets to all current users"), |
|
|
392 | CGI::br(), |
|
|
393 | $slownessWarning, |
|
|
394 | CGI::submit({"name"=>"importSets", "label"=>"Import Multiple Sets"}), |
|
|
395 | CGI::end_form(), |
|
|
396 | ); |
|
|
397 | |
|
|
398 | return ""; |
| 21 | } |
399 | } |
| 22 | |
400 | |
|
|
401 | ############################################################################################## |
|
|
402 | # Utility scripts -- may be moved to Utils.pm |
|
|
403 | ############################################################################################## |
|
|
404 | |
|
|
405 | |
|
|
406 | sub readSetDef { |
|
|
407 | my $self = shift; |
|
|
408 | my $fileName = shift; |
|
|
409 | my $templateDir = $self->{ce}->{courseDirs}->{templates}; |
|
|
410 | my $filePath = "$templateDir/$fileName"; |
|
|
411 | my $setNumber = ''; |
|
|
412 | if ($fileName =~ m|^set(\w+)\.def$|) { |
|
|
413 | $setNumber = $1; |
|
|
414 | } else { |
|
|
415 | warn qq{The setDefinition file name must begin with <CODE>set</CODE>}, |
|
|
416 | qq{and must end with <CODE>.def</CODE> . Every thing in between becomes the name of the set. }, |
|
|
417 | qq{For example <CODE>set1.def</CODE>, <CODE>setExam.def</CODE>, and <CODE>setsample7.def</CODE> }, |
|
|
418 | qq{define sets named <CODE>1</CODE>, <CODE>Exam</CODE>, and <CODE>sample7</CODE> respectively. }, |
|
|
419 | qq{The filename, $fileName, you entered is not legal\n }; |
|
|
420 | |
|
|
421 | } |
|
|
422 | |
|
|
423 | my ($line,$name,$value,$attemptLimit,$continueFlag); |
|
|
424 | my $paperHeaderFile = ''; |
|
|
425 | my $screenHeaderFile = ''; |
|
|
426 | my ($dueDate,$openDate,$answerDate); |
|
|
427 | my @problemData; |
|
|
428 | if ( open (SETFILENAME, "$filePath") ) { |
|
|
429 | ##################################################################### |
|
|
430 | # Read and check set data |
|
|
431 | ##################################################################### |
|
|
432 | while (<SETFILENAME>) { |
|
|
433 | chomp($line = $_); |
|
|
434 | $line =~ s|(#.*)||; ## don't read past comments |
|
|
435 | unless ($line =~ /\S/) {next;} ## skip blank lines |
|
|
436 | $line =~ s|\s*$||; ## trim trailing spaces |
|
|
437 | $line =~ m|^\s*(\w+)\s*=\s*(.*)|; |
|
|
438 | |
|
|
439 | ###################### |
|
|
440 | # sanity check entries |
|
|
441 | ###################### |
|
|
442 | my $item = $1; |
|
|
443 | $item = '' unless defined $item; |
|
|
444 | my $value = $2; |
|
|
445 | $value = '' unless defined $value; |
|
|
446 | |
|
|
447 | if ($item eq 'setNumber') { |
|
|
448 | next; |
|
|
449 | } elsif ($item eq 'paperHeaderFile') { |
|
|
450 | $paperHeaderFile = $value; |
|
|
451 | } elsif ($item eq 'screenHeaderFile') { |
|
|
452 | $screenHeaderFile = $value; |
|
|
453 | } elsif ($item eq 'dueDate') { |
|
|
454 | $dueDate = $value; |
|
|
455 | } elsif ($item eq 'openDate') { |
|
|
456 | $openDate = $value; |
|
|
457 | } elsif ($1 eq 'answerDate') { |
|
|
458 | $answerDate = $value; |
|
|
459 | } elsif ($item eq 'problemList') { |
|
|
460 | last; |
|
|
461 | } else { |
|
|
462 | warn "readSetDef error, can't read the line: ||$line||"; |
|
|
463 | } |
|
|
464 | } |
|
|
465 | ##################################################################### |
|
|
466 | # Check and format dates |
|
|
467 | ##################################################################### |
|
|
468 | my ($time1,$time2,$time3) = map { $_ =~ s/\s*at\s*/ /; WeBWorK::Utils::parseDateTime($_); } ($openDate, $dueDate, $answerDate); |
|
|
469 | |
|
|
470 | unless ($time1 <= $time2 and $time2 <= $time3) { |
|
|
471 | warn "The open date: $openDate, due date: $dueDate, and answer date: $answerDate must be defined and in chronologicasl order."; |
|
|
472 | } |
|
|
473 | # Check header file names |
|
|
474 | $paperHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space |
|
|
475 | $screenHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space |
|
|
476 | |
|
|
477 | # warn "setNumber: $setNumber\ndueDate: $dueDate\nopenDate: $openDate\nanswerDate: $answerDate\n"; |
|
|
478 | # warn "time1 $time1 time2 $time2 time3 $time3"; |
|
|
479 | ##################################################################### |
|
|
480 | # Read and check list of problems for the set |
|
|
481 | ##################################################################### |
|
|
482 | |
|
|
483 | while(<SETFILENAME>) { |
|
|
484 | chomp($line=$_); |
|
|
485 | $line =~ s/(#.*)//; ## don't read past comments |
|
|
486 | unless ($line =~ /\S/) {next;} ## skip blank lines |
|
|
487 | |
|
|
488 | ($name, $value, $attemptLimit, $continueFlag) = split (/\s*,\s*/,$line); |
|
|
489 | ##################### |
|
|
490 | # clean up problem values |
|
|
491 | ########################### |
|
|
492 | $name =~ s/\s*//g; |
|
|
493 | # push(@problemList, $name); |
|
|
494 | $value = "" unless defined($value); |
|
|
495 | $value =~ s/[^\d\.]*//g; |
|
|
496 | unless ($value =~ /\d+/) {$value = 1;} |
|
|
497 | # push(@problemValueList, $value); |
|
|
498 | $attemptLimit = "" unless defined($attemptLimit); |
|
|
499 | $attemptLimit =~ s/[^\d-]*//g; |
|
|
500 | unless ($attemptLimit =~ /\d+/) {$attemptLimit = -1;} |
|
|
501 | # push(@problemAttemptLimitList, $attemptLimit); |
|
|
502 | $continueFlag = "0" unless( defined($continueFlag) && @problemData ); |
|
|
503 | # can't put continuation flag ont the first problem |
|
|
504 | # push(@problemContinuationFlagList, $continueFlag); |
|
|
505 | push(@problemData, {source_file => $name, |
|
|
506 | value => $value, |
|
|
507 | max_attempts =>, $attemptLimit, |
|
|
508 | continuation => $continueFlag |
|
|
509 | }); |
|
|
510 | } |
|
|
511 | close(SETFILENAME); |
|
|
512 | ($setNumber, |
|
|
513 | $paperHeaderFile, |
|
|
514 | $screenHeaderFile, |
|
|
515 | $time1, |
|
|
516 | $time2, |
|
|
517 | $time3, |
|
|
518 | \@problemData, |
|
|
519 | ); |
|
|
520 | } else { |
|
|
521 | warn "Can't open file $filePath\n"; |
|
|
522 | } |
|
|
523 | } |
|
|
524 | |
| 23 | 1; |
525 | 1; |