[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetList.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1804 - (view) (download) (as text)

1 : sh002i 1663 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : sh002i 1804 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm,v 1.42 2004/02/12 04:26:51 sh002i Exp $
5 : sh002i 1663 #
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 :    
17 : malsyned 832 package WeBWorK::ContentGenerator::Instructor::ProblemSetList;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 :     WeBWorK::ContentGenerator::Instructor::ProblemSetList - Entry point for Problem and Set editing
23 :    
24 :     =cut
25 :    
26 :     use strict;
27 :     use warnings;
28 : malsyned 1410 use Apache::Constants qw(REDIRECT);
29 : malsyned 832 use CGI qw();
30 : malsyned 877 use WeBWorK::Utils qw(formatDateTime);
31 : malsyned 832
32 : gage 1428 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts continuation)];
33 :    
34 : malsyned 1410 sub header {
35 :     my $self = shift;
36 :     my $r = $self->{r};
37 :     my $ce = $self->{ce};
38 :     my $courseName = $ce->{courseName};
39 :     my $root = $ce->{webworkURLs}->{root};
40 :    
41 :     if (defined $r->param('scoreSelected')) {
42 :     $r->header_out(Location => "$root/$courseName/instructor/scoring?".$self->url_args);
43 :     $self->{noContent} = 1;
44 :     return REDIRECT;
45 :     }
46 :     $r->content_type("text/html");
47 :     $r->send_http_header();
48 :     }
49 :    
50 : malsyned 963 sub initialize {
51 :     my $self = shift;
52 :     my $r = $self->{r};
53 :     my $db = $self->{db};
54 :     my $ce = $self->{ce};
55 : malsyned 1017 my $authz = $self->{authz};
56 : malsyned 963 my $courseName = $ce->{courseName};
57 : malsyned 1017 my $user = $r->param('user');
58 : malsyned 963
59 : malsyned 1017 unless ($authz->hasPermissions($user, "create_and_delete_problem_sets")) {
60 :     $self->{submitError} = "You aren't authorized to create or delete problems";
61 :     return;
62 :     }
63 :    
64 : malsyned 963 if (defined($r->param('deleteSelected'))) {
65 :     foreach my $wannaDelete ($r->param('selectedSet')) {
66 :     $db->deleteGlobalSet($wannaDelete);
67 :     }
68 : sh002i 1660 } elsif (defined $r->param('scoreSelected')) {
69 :     # FIXME: this doesn't do anything!
70 :     } elsif (defined $r->param('makeNewSet')) {
71 : malsyned 1020 my $newSetRecord = $db->{set}->{record}->new();
72 : malsyned 963 my $newSetName = $r->param('newSetName');
73 :     $newSetRecord->set_id($newSetName);
74 : malsyned 977 $newSetRecord->set_header("");
75 :     $newSetRecord->problem_header("");
76 :     $newSetRecord->open_date("0");
77 :     $newSetRecord->due_date("0");
78 :     $newSetRecord->answer_date("0");
79 : malsyned 1020 eval {$db->addGlobalSet($newSetRecord)};
80 : sh002i 1660 } elsif (defined $r->param('importSet') or defined $r->param('importSets')) {
81 :     my @setDefFiles = ();
82 :     my $newSetName = "";
83 :     if (defined $r->param('importSet')) {
84 :     @setDefFiles = $r->param('set_definition_file');
85 :     $newSetName = $r->param('newSetName');
86 :     } elsif (defined $r->param('importSets')) {
87 :     @setDefFiles = $r->param('set_definition_files');
88 :     }
89 : gage 1428
90 : sh002i 1660 foreach my $set_definition_file (@setDefFiles) {
91 : sh002i 1790 $WeBWorK::timer->continue("$set_definition_file: reading set definition file") if defined $WeBWorK::timer;
92 : sh002i 1660 # read data in set definition file
93 :     my ($setName, $paperHeaderFile, $screenHeaderFile,
94 :     $openDate, $dueDate, $answerDate, $ra_problemData,
95 :     ) = $self->readSetDef($set_definition_file);
96 :     my @problemList = @{$ra_problemData};
97 :    
98 :     # Use the original name if form doesn't specify a new one.
99 :     # The set acquires the new name specified by the form. A blank
100 :     # entry on the form indicates that the imported set name will be used.
101 :     $setName = $newSetName if $newSetName;
102 : gage 1428
103 : sh002i 1790 $WeBWorK::timer->continue("$set_definition_file: adding set") if defined $WeBWorK::timer;
104 : sh002i 1660 # add the data to the set record
105 :     #my $newSetRecord = $db->{set}->{record}->new();
106 :     my $newSetRecord = $db->newGlobalSet;
107 :     $newSetRecord->set_id($setName);
108 :     $newSetRecord->set_header($screenHeaderFile);
109 :     $newSetRecord->problem_header($paperHeaderFile);
110 :     $newSetRecord->open_date($openDate);
111 :     $newSetRecord->due_date($dueDate);
112 :     $newSetRecord->answer_date($answerDate);
113 :    
114 :     #create the set
115 :     eval {$db->addGlobalSet($newSetRecord)};
116 :     die "addGlobalSet $setName in ProblemSetList: $@" if $@;
117 :    
118 : sh002i 1790 $WeBWorK::timer->continue("$set_definition_file: adding problems to database") if defined $WeBWorK::timer;
119 : sh002i 1660 # add problems
120 :     my $freeProblemID = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1;
121 :     foreach my $rh_problem (@problemList) {
122 :     #my $problemRecord = new WeBWorK::DB::Record::Problem;
123 :     my $problemRecord = $db->newGlobalProblem;
124 :     $problemRecord->problem_id($freeProblemID++);
125 :     #warn "Adding problem $freeProblemID ", $rh_problem->source_file;
126 :     $problemRecord->set_id($setName);
127 :     $problemRecord->source_file($rh_problem->{source_file});
128 :     $problemRecord->value($rh_problem->{value});
129 :     $problemRecord->max_attempts($rh_problem->{max_attempts});
130 :     # continuation flags???
131 :     $db->addGlobalProblem($problemRecord);
132 :     #$self->assignProblemToAllSetUsers($problemRecord); # handled by parent
133 :     }
134 :    
135 : sh002i 1702 if ($r->param("assignNewSet")) {
136 : sh002i 1790 $WeBWorK::timer->continue("$set_definition_file: assigning set to all users") if defined $WeBWorK::timer;
137 : sh002i 1702 # assign the set to all users
138 :     $self->assignSetToAllUsers($setName);
139 :     }
140 : gage 1428 }
141 :     }
142 : malsyned 963 }
143 :    
144 : gage 1295 sub path {
145 :     my ($self, $args) = @_;
146 :    
147 :     my $ce = $self->{ce};
148 :     my $root = $ce->{webworkURLs}->{root};
149 :     my $courseName = $ce->{courseName};
150 :     return $self->pathMacro($args,
151 : sh002i 1681 "Home" => "$root",
152 :     $courseName => "$root/$courseName",
153 :     'Instructor Tools' => "$root/$courseName/instructor",
154 :     'Set List' => '' # $root/$courseName/instructor/sets
155 : gage 1295 );
156 :     }
157 :    
158 : malsyned 836 sub title {
159 :     my $self = shift;
160 : sh002i 1681 return "Set List";
161 : malsyned 836 }
162 :    
163 :     sub body {
164 : gage 859 my $self = shift;
165 : malsyned 877 my $r = $self->{r};
166 :     my $db = $self->{db};
167 : gage 859 my $ce = $self->{ce};
168 : malsyned 1017 my $authz = $self->{authz};
169 : malsyned 877 my $root = $ce->{webworkURLs}->{root};
170 : gage 859 my $courseName = $ce->{courseName};
171 :     my $user = $r->param('user');
172 :     my $key = $r->param('key');
173 :     my $effectiveUserName = $r->param('effectiveUser');
174 : malsyned 883 my $URL = $r->uri;
175 :     my $instructorBaseURL = "$root/$courseName/instructor";
176 :     my $importURL = "$instructorBaseURL/problemSetImport/";
177 :     my $sort = $r->param('sort') ? $r->param('sort') : "due_date";
178 : gage 859
179 : gage 1399 my @set_definition_files = $self->read_dir($ce->{courseDirs}->{templates},'\\.def');
180 : malsyned 1017 return CGI::em("You are not authorized to access the instructor tools") unless $authz->hasPermissions($user, "access_instructor_tools");
181 :    
182 : malsyned 883 # Slurp each set record for this course in @sets
183 : malsyned 952 # Gather data from the database
184 :     my @users = $db->listUsers;
185 : gage 1606 my @set_IDs = $db->listGlobalSets;
186 : gage 1667 my @sets = $db->getGlobalSets(@set_IDs); #checked
187 : malsyned 883 my %counts;
188 : malsyned 954 my %problemCounts;
189 : gage 1606
190 : sh002i 1651 $WeBWorK::timer->continue("Begin obtaining problem info on sets") if defined $WeBWorK::timer;
191 : gage 1606 foreach my $set_id (@set_IDs) {
192 : malsyned 955 $problemCounts{$set_id} = scalar($db->listGlobalProblems($set_id));
193 : sh002i 1661 #$counts{$set_id} = $db->listSetUsers($set_id);
194 : malsyned 883 }
195 : sh002i 1651 $WeBWorK::timer->continue("End obtaining problem on sets") if defined $WeBWorK::timer;
196 : malsyned 883
197 : sh002i 1651 $WeBWorK::timer->continue("Begin obtaining assigned user info on sets") if defined $WeBWorK::timer;
198 : gage 1606 foreach my $set_id (@set_IDs) {
199 : sh002i 1661 #$problemCounts{$set_id} = scalar($db->listGlobalProblems($set_id));
200 :     #$counts{$set_id} = $db->listSetUsers($set_id);
201 :     $counts{$set_id} = $db->countSetUsers($set_id);
202 : gage 1606 }
203 : sh002i 1651 $WeBWorK::timer->continue("End obtaining assigned user info on sets") if defined $WeBWorK::timer;
204 : gage 1606
205 : malsyned 883 # Sort @sets based on the sort parameter
206 :     # Invalid sort types will just cause an unpredictable ordering, which is no big deal.
207 :     @sets = sort {
208 :     if ($sort eq "set_id") {
209 :     return $a->$sort cmp $b->$sort;
210 :     }elsif ($sort =~ /_date$/) {
211 :     return $a->$sort <=> $b->$sort;
212 :     } elsif ($sort eq "num_probs") {
213 : malsyned 954 return $problemCounts{$a->set_id} <=> $problemCounts{$b->set_id};
214 : malsyned 883 } elsif ($sort eq "num_students") {
215 :     return $counts{$a->set_id} <=> $counts{$b->set_id};
216 :     }
217 :     } @sets;
218 :    
219 :     my $table = CGI::Tr({},
220 :     CGI::th("Sel.")
221 : gage 1727 . CGI::th("Edit ", CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=set_id"}, "set"), " dates")
222 : malsyned 883 . CGI::th(CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=open_date"}, "Open Date"))
223 :     . CGI::th(CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=due_date"}, "Due Date"))
224 :     . CGI::th(CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=answer_date"}, "Answer Date"))
225 : gage 1727 . CGI::th("Edit problems") # CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=num_probs"}"Num. Problems"),
226 :     . CGI::th("Assign users") #, CGI::a({"href"=>$URL."?".$self->url_authen_args."&sort=num_students"}, "Assigned to:") )
227 : malsyned 883 ) . "\n";
228 :    
229 :     foreach my $set (@sets) {
230 : gage 1727 my $count = $counts{$set->set_id};
231 :     my $totalUsers = scalar(@users); #FIXME -- probably shouldn't count those who have dropped.
232 : malsyned 1005 my $userCountMessage = $self->userCountMessage($count, scalar(@users));
233 : malsyned 877
234 :     $table .= CGI::Tr({},
235 :     CGI::td({},
236 :     CGI::checkbox({
237 : malsyned 883 "name"=>"selectedSet",
238 : malsyned 877 "value"=>$set->set_id,
239 :     "label"=>"",
240 :     "checked"=>"0"
241 :     })
242 : malsyned 924 )
243 : malsyned 995 . CGI::td({}, CGI::a({href=>$r->uri.$set->set_id."/?".$self->url_authen_args}, $set->set_id))
244 : malsyned 883 . CGI::td({}, formatDateTime($set->open_date))
245 :     . CGI::td({}, formatDateTime($set->due_date))
246 :     . CGI::td({}, formatDateTime($set->answer_date))
247 : malsyned 1005 . CGI::td({}, CGI::a({href=>$r->uri.$set->set_id."/problems/?".$self->url_authen_args}, $problemCounts{$set->set_id}))
248 : gage 1727 . CGI::td({}, CGI::a({href=>$r->uri.$set->set_id."/users/?".$self->url_authen_args}, "$count/$totalUsers") ) #$userCountMessage))
249 : malsyned 877 ) . "\n"
250 :     }
251 : malsyned 883 $table = CGI::table({"border"=>"1"}, "\n".$table."\n");
252 : sh002i 1804
253 :     my $slownessWarning = "";
254 :     if ($ce->{dbLayoutName} eq "sql") {
255 :     $slownessWarning = "In this version of WeBWorK, assigning sets is very slow. Your browser"
256 :     . " may time out if you import many sets or have many users to whom to assign them. If this"
257 :     . " happens, click your browser's reload button. This issue will be resolved in a later"
258 :     . " version of WeBWorK. (Hopefully the next version!)"
259 :     . CGI::br();
260 :     }
261 : malsyned 883
262 : sh002i 1702 print join("\n",
263 :     # Set table form (for deleting checked sets)
264 :     CGI::start_form({"method"=>"POST", "action"=>$r->uri}),
265 :     $self->hidden_authen_fields,
266 :     $table,
267 :     CGI::br(),
268 : gage 1723
269 : sh002i 1702 CGI::submit({"name"=>"scoreSelected", "label"=>"Score Selected"}),
270 : gage 1723 CGI::div( {style=>'background-color:red'},
271 :     "There is NO undo when deleting sets. Use cautiously. All data for the set is lost.
272 :     <br>If the set is re-assigned (rebuilt) all of the problem versions will be different.",
273 :     CGI::br(),
274 :     CGI::submit({"name"=>"deleteSelected", "label"=>"Delete Selected"}),
275 :     ),
276 : sh002i 1702 CGI::end_form(),
277 :     CGI::br(),
278 : sh002i 1660
279 : sh002i 1702 # Empty set creation form
280 :     CGI::start_form({"method"=>"POST", "action"=>$r->uri}),
281 :     $self->hidden_authen_fields,
282 :     CGI::b("Create an Empty Set"),
283 :     CGI::br(),
284 : gage 1400 "New Set Name: ",
285 :     CGI::input({type=>"text", name=>"newSetName", value=>""}),
286 : sh002i 1702 CGI::submit({"name"=>"makeNewSet", "label"=>"Create"}),
287 :     CGI::end_form(),
288 :     CGI::br(),
289 : sh002i 1660
290 : sh002i 1702 # Single set import form
291 : gage 1723 CGI::hr(),
292 : sh002i 1702 CGI::start_form({"method"=>"POST", "action"=>$r->uri}),
293 :     $self->hidden_authen_fields,
294 :     CGI::b("Import a Single Set"),
295 :     CGI::br(),
296 :     "From file: ",
297 :     CGI::popup_menu(-name=>'set_definition_file', -values=>\@set_definition_files),
298 :     CGI::br(),
299 :     "Set name: ",
300 :     CGI::input({type=>"text", name=>"newSetName", value=>""}),
301 :     " (leave blank to use name of set definition file)",
302 :     CGI::br(),
303 :     CGI::checkbox(-name=>"assignNewSet",
304 :     -label=>"Assign imported set to all users"),
305 :     CGI::br(),
306 :     CGI::submit({"name"=>"importSet", "label"=>"Import a Single Set"}),
307 :     CGI::end_form(),
308 :     CGI::br(),
309 :    
310 :     # Multiple set import form
311 : gage 1723 CGI::hr(),
312 : sh002i 1702 CGI::start_form({"method"=>"POST", "action"=>$r->uri}),
313 :     $self->hidden_authen_fields,
314 :     CGI::b("Import Multiple Sets"),
315 :     CGI::br(),
316 : sh002i 1660 "Each set will be named based on the name of the set definition file, omitting",
317 :     " any leading ", CGI::i("set"), " and trailing ", CGI::i(".def"), ". Note that",
318 :     " the name of a set cannot be changed once it has been created.",
319 : sh002i 1702 CGI::br(),
320 :     CGI::scrolling_list(-name=>"set_definition_files", -values=>\@set_definition_files, -size=>10, -multiple=>"true"),
321 :     CGI::br(),
322 :     CGI::checkbox(-name=>"assignNewSet", -label=>"Assign imported sets to all users"),
323 :     CGI::br(),
324 : sh002i 1804 $slownessWarning,
325 : sh002i 1702 CGI::submit({"name"=>"importSets", "label"=>"Import Multiple Sets"}),
326 :     CGI::end_form(),
327 : gage 1400 );
328 : malsyned 877
329 :     return "";
330 : malsyned 836 }
331 : malsyned 924
332 : gage 1428 ##############################################################################################
333 :     # Utility scripts -- may be moved to Utils.pm
334 :     ##############################################################################################
335 :    
336 :    
337 :     sub readSetDef {
338 :     my $self = shift;
339 :     my $fileName = shift;
340 :     my $templateDir = $self->{ce}->{courseDirs}->{templates};
341 :     my $filePath = "$templateDir/$fileName";
342 :     my $setNumber = '';
343 :     if ($fileName =~ m|^set(\w+)\.def$|) {
344 :     $setNumber = $1;
345 :     } else {
346 :     warn qq{The setDefinition file name must begin with <CODE>set</CODE>},
347 :     qq{and must end with <CODE>.def</CODE> . Every thing in between becomes the name of the set. },
348 :     qq{For example <CODE>set1.def</CODE>, <CODE>setExam.def</CODE>, and <CODE>setsample7.def</CODE> },
349 :     qq{define sets named <CODE>1</CODE>, <CODE>Exam</CODE>, and <CODE>sample7</CODE> respectively. },
350 :     qq{The filename, $fileName, you entered is not legal\n };
351 :    
352 :     }
353 :    
354 :     my ($line,$name,$value,$attemptLimit,$continueFlag);
355 :     my $paperHeaderFile = '';
356 :     my $screenHeaderFile = '';
357 :     my ($dueDate,$openDate,$answerDate);
358 :     my @problemData;
359 :     if ( open (SETFILENAME, "$filePath") ) {
360 :     #####################################################################
361 :     # Read and check set data
362 :     #####################################################################
363 :     while (<SETFILENAME>) {
364 :     chomp($line = $_);
365 :     $line =~ s|(#.*)||; ## don't read past comments
366 :     unless ($line =~ /\S/) {next;} ## skip blank lines
367 :     $line =~ s|\s*$||; ## trim trailing spaces
368 :     $line =~ m|^\s*(\w+)\s*=\s*(.*)|;
369 : gage 1726
370 :     ######################
371 :     # sanity check entries
372 :     ######################
373 :     my $item = $1;
374 :     $item = '' unless defined $item;
375 :     my $value = $2;
376 :     $value = '' unless defined $value;
377 :    
378 :     if ($item eq 'setNumber') {
379 : gage 1428 next;
380 : gage 1726 } elsif ($item eq 'paperHeaderFile') {
381 :     $paperHeaderFile = $value;
382 :     } elsif ($item eq 'screenHeaderFile') {
383 :     $screenHeaderFile = $value;
384 :     } elsif ($item eq 'dueDate') {
385 :     $dueDate = $value;
386 :     } elsif ($item eq 'openDate') {
387 :     $openDate = $value;
388 : gage 1428 } elsif ($1 eq 'answerDate') {
389 : gage 1726 $answerDate = $value;
390 :     } elsif ($item eq 'problemList') {
391 : gage 1428 last;
392 :     } else {
393 : gage 1726 warn "readSetDef error, can't read the line: ||$line||";
394 : gage 1428 }
395 :     }
396 :     #####################################################################
397 :     # Check and format dates
398 :     #####################################################################
399 :     my ($time1,$time2,$time3) = map { $_ =~ s/\s*at\s*/ /; WeBWorK::Utils::parseDateTime($_); } ($openDate, $dueDate, $answerDate);
400 :    
401 :     unless ($time1 <= $time2 and $time2 <= $time3) {
402 :     warn "The open date: $openDate, due date: $dueDate, and answer date: $answerDate must be defined and in chronologicasl order.";
403 :     }
404 :     # Check header file names
405 :     $paperHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space
406 :     $screenHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space
407 :    
408 :     # warn "setNumber: $setNumber\ndueDate: $dueDate\nopenDate: $openDate\nanswerDate: $answerDate\n";
409 :     # warn "time1 $time1 time2 $time2 time3 $time3";
410 :     #####################################################################
411 :     # Read and check list of problems for the set
412 :     #####################################################################
413 :    
414 :     while(<SETFILENAME>) {
415 :     chomp($line=$_);
416 :     $line =~ s/(#.*)//; ## don't read past comments
417 :     unless ($line =~ /\S/) {next;} ## skip blank lines
418 :    
419 :     ($name, $value, $attemptLimit, $continueFlag) = split (/\s*,\s*/,$line);
420 :     #####################
421 :     # clean up problem values
422 :     ###########################
423 :     $name =~ s/\s*//g;
424 :     # push(@problemList, $name);
425 :     $value = "" unless defined($value);
426 :     $value =~ s/[^\d\.]*//g;
427 :     unless ($value =~ /\d+/) {$value = 1;}
428 :     # push(@problemValueList, $value);
429 :     $attemptLimit = "" unless defined($attemptLimit);
430 :     $attemptLimit =~ s/[^\d-]*//g;
431 :     unless ($attemptLimit =~ /\d+/) {$attemptLimit = -1;}
432 :     # push(@problemAttemptLimitList, $attemptLimit);
433 :     $continueFlag = "0" unless( defined($continueFlag) && @problemData );
434 :     # can't put continuation flag ont the first problem
435 :     # push(@problemContinuationFlagList, $continueFlag);
436 :     push(@problemData, {source_file => $name,
437 :     value => $value,
438 :     max_attempts =>, $attemptLimit,
439 :     continuation => $continueFlag
440 :     });
441 :     }
442 :     close(SETFILENAME);
443 :     ($setNumber,
444 :     $paperHeaderFile,
445 :     $screenHeaderFile,
446 :     $time1,
447 :     $time2,
448 :     $time3,
449 :     \@problemData,
450 :     );
451 :     } else {
452 :     warn "Can't open file $filePath\n";
453 :     }
454 :     }
455 :    
456 : malsyned 832 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9