[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetEditor.pm Repository:
ViewVC logotype

Annotation of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2103 - (view) (download) (as text)
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm

1 : sh002i 1663 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : toenail 2103 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm,v 1.50 2004/05/13 16:02:55 toenail 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 : gage 860 package WeBWorK::ContentGenerator::Instructor::ProblemSetEditor;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 :     WeBWorK::ContentGenerator::Instructor::ProblemSetEditor - Edit a set definition list
23 :    
24 :     =cut
25 :    
26 :     use strict;
27 :     use warnings;
28 :     use CGI qw();
29 : jj 2038 use File::Copy;
30 : malsyned 976 use WeBWorK::DB::Record::Problem;
31 :     use WeBWorK::Utils qw(readFile formatDateTime parseDateTime list2hash readDirectory max);
32 : gage 860
33 : gage 869 our $rowheight = 20; #controls the length of the popup menus.
34 : gage 875 our $libraryName; #library directory name
35 : malsyned 935
36 : toenail 2097 use constant SET_FIELDS => [qw(open_date due_date answer_date set_header problem_header published)];
37 : malsyned 957 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
38 : malsyned 959 use constant PROBLEM_USER_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
39 : malsyned 947
40 : malsyned 935 sub getSetName {
41 :     my ($self, $pathSetName) = @_;
42 :     if (ref $pathSetName eq "HASH") {
43 :     $pathSetName = undef;
44 :     }
45 :     return $pathSetName;
46 :     }
47 :    
48 : toenail 1997 # One wrinkle here: if $override is undefined, do the global thing,
49 :     # otherwise, it's truth value determines the checkbox and the current fieldValue is not directly editable
50 : malsyned 967 sub setRowHTML {
51 :     my ($description, $fieldName, $fieldValue, $size, $override, $overrideValue) = @_;
52 :    
53 :     my $attributeHash = {type=>"text", name=>$fieldName, value=>$fieldValue};
54 :     $attributeHash->{size} = $size if defined $size;
55 :    
56 : toenail 1997 my $input = (defined $override) ? $fieldValue : CGI::input($attributeHash);
57 : toenail 2103
58 : toenail 1997 my $html = CGI::td({}, [$description, $input]);
59 :    
60 : malsyned 967 if (defined $override) {
61 :     $attributeHash->{name}="${fieldName}_override";
62 :     $attributeHash->{value}=($override ? $overrideValue : "" );
63 :    
64 :     $html .= CGI::td({}, [
65 :     CGI::checkbox({
66 :     type=>"checkbox",
67 :     name=>"override",
68 :     label=>"override with:",
69 :     value=>$fieldName,
70 :     checked=>($override ? 1 : 0)
71 :     }),
72 :     CGI::input($attributeHash)
73 :     ]);
74 :     }
75 :    
76 :     return $html;
77 :    
78 :     }
79 :    
80 : gage 860
81 : toenail 2103 # FIXME: this or something similar could get pulled up to Instructor.pm
82 :     sub recurseDirectory {
83 : gage 1295
84 : toenail 2103 my ($self, $dir, $pattern) = @_;
85 :    
86 :     my @dirs = grep {$_ ne "." and $_ ne ".." and $_ ne "Library" and $_ ne "CVS" and -d "$dir/$_"} readDirectory($dir);
87 :    
88 :     my @files = map { "$dir/$_" } $self->read_dir($dir, $pattern);
89 :    
90 :     foreach (@dirs) {
91 :     push (@files, $self->recurseDirectory("$dir/$_", $pattern));
92 :     }
93 :    
94 :     return @files;
95 :     }
96 :    
97 :    
98 :    
99 : malsyned 960 # Initialize does all of the form processing. It's extensive, and could probably be cleaned up and
100 :     # consolidated with a little abstraction.
101 : malsyned 935 sub initialize {
102 : gage 1928 my ($self) = @_;
103 :     my $r = $self->r;
104 :     my $db = $r->db;
105 :     my $ce = $r->ce;
106 :     my $authz = $r->authz;
107 : gage 1295 my $user = $r->param('user');
108 : gage 1928 #my $setName = $self->getSetName(@components);
109 :     my $setName = $r->urlpath->arg("setID");
110 : gage 1667 my $setRecord = $db->getGlobalSet($setName); #checked
111 :     die "global set $setName not found." unless $setRecord;
112 :    
113 : gage 1295 $self->{set} = $setRecord;
114 : malsyned 957 my @editForUser = $r->param('editForUser');
115 :     # some useful booleans
116 : gage 1295 my $forUsers = scalar(@editForUser);
117 :     my $forOneUser = $forUsers == 1;
118 : malsyned 935
119 : malsyned 983 # build a quick lookup table
120 : malsyned 967 my %overrides = list2hash $r->param('override');
121 : malsyned 959
122 : malsyned 1017 unless ($authz->hasPermissions($user, "modify_problem_sets")) {
123 : toenail 2091 $self->addmessage(CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify problem sets")));
124 : malsyned 1017 return;
125 :     }
126 : gage 1428
127 :     ###################################################
128 :     # The set form was submitted with the save button pressed
129 :     # Save changes to the set
130 :     ###################################################
131 : gage 1686
132 : toenail 2103 if (defined($r->param('submit_set_changes'))) {
133 :    
134 :     if (!$forUsers) {
135 :     foreach (@{SET_FIELDS()}) {
136 :     if (defined($r->param($_))) {
137 :     if (m/_date$/) {
138 :     $setRecord->$_(parseDateTime($r->param($_)));
139 :     } else {
140 :     $setRecord->$_($r->param($_));
141 :     if($_ eq 'set_header') {
142 :     # be nice and copy the default file here if it doesn't exist yet
143 :     # empty set headers lead to trouble
144 :     my $newheaderpath = $r->{ce}->{courseDirs}->{templates} . '/'. $r->param('set_header');
145 :     unless(($r->param('set_header') !~ /\S/) or -e $newheaderpath) {
146 :     my $default_header = $ce->{webworkFiles}->{screenSnippets}->{setHeader};
147 :     File::Copy::copy($default_header, $newheaderpath);
148 :     }
149 : jj 2038 }
150 :     }
151 : toenail 2103 } else {
152 :     if (m/published$/) {
153 :     $setRecord->$_(0);
154 :     }
155 : malsyned 935 }
156 :     }
157 : toenail 2097
158 :    
159 :    
160 :    
161 : toenail 2103 ###################################################
162 :     # Check that the open, due and answer dates are in increasing order.
163 :     # Bail if this is not correct.
164 :     ###################################################
165 :     if ($setRecord->open_date > $setRecord->due_date) {
166 :     $self->addmessage(CGI::div({class=>'ResultsWithError'},'Error: Due date must come after open date'));
167 :     return;
168 :     }
169 :     if ($setRecord->due_date > $setRecord->answer_date) {
170 :     $self->addmessage(CGI::div({class=>'ResultsWithError'},'Error: Answer date must come after due date'));
171 :     return;
172 :     }
173 :     ###################################################
174 :     # End date check section.
175 :     ###################################################
176 :     $self->addmessage(CGI::div({class=>'ResultsWithoutError'}, "Changes to set $setName were successfully saved."));
177 :     $db->putGlobalSet($setRecord);
178 :     } else {
179 : malsyned 959
180 : gage 1667 my $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
181 :     die "set $setName not found for $editForUser[0]." unless $userSetRecord;
182 : malsyned 959 foreach my $field (@{SET_FIELDS()}) {
183 :     if (defined $r->param("${field}_override")) {
184 :     if (exists $overrides{$field}) {
185 :     if ($field =~ m/_date$/) {
186 :     $userSetRecord->$field(parseDateTime($r->param("${field}_override")));
187 :     } else {
188 :     $userSetRecord->$field($r->param("${field}_override"));
189 :     }
190 :     } else {
191 :     $userSetRecord->$field(undef);
192 :     }
193 :     }
194 :     }
195 : gage 1686 ###################################################
196 :     # Check that the open, due and answer dates are in increasing order.
197 :     # Bail if this is not correct.
198 :     ###################################################
199 :     my $active_open_date = $userSetRecord->open_date ? $userSetRecord->open_date : $setRecord->open_date;
200 :     my $active_due_date = $userSetRecord->due_date ? $userSetRecord->due_date : $setRecord->due_date;
201 :     my $active_answer_date = $userSetRecord->answer_date ? $userSetRecord->answer_date : $setRecord->answer_date;
202 : toenail 2103 if ( $active_open_date > $active_due_date ) {
203 : toenail 2091 $self->addmessage(CGI::div({class=>'ResultsWithError'},'Error: Due date override must come after open date'));
204 :     return;
205 : gage 1686 }
206 :     if ( $active_due_date > $active_answer_date ) {
207 : toenail 2091 $self->addmessage(CGI::div({class=>'ResultsWithError'},'Error: Answer date override must come after due date'));
208 :     return;
209 : gage 1686 }
210 :     ###################################################
211 :     # End date check section.
212 :     ###################################################
213 : toenail 2097 $self->addmessage(CGI::div({class=>'ResultsWithoutError'}, "Changes to set $setName for user ",CGI::b($editForUser[0]) ,"were successfully saved."));
214 : gage 1686 $db->putUserSet($userSetRecord);
215 : malsyned 959 }
216 : gage 1686
217 : malsyned 936 }
218 : gage 1428
219 :     ###################################################
220 :     # The set form was submitted with the export button pressed
221 :     # Export the set structure to a set definition file
222 :     ###################################################
223 :    
224 :     if ( defined($r->param('export_set')) ) {
225 :     my $fileName = $r->param('export_file_name');
226 :     die "Please specify a file name for saving the set definition" unless $fileName;
227 :     $fileName .= '.def' unless $fileName =~ /\.def$/;
228 :     my $filePath = $ce->{courseDirs}->{templates}.'/'.$fileName;
229 :     # back up existing file
230 : jj 1978 if(-e $filePath) {
231 :     rename($filePath,"$filePath.bak") or
232 : gage 1428 die "Can't rename $filePath to $filePath.bak ",
233 :     "Check permissions for webserver on directories. $!";
234 : jj 1978 }
235 : gage 1428 my $openDate = formatDateTime($setRecord->open_date);
236 :     my $dueDate = formatDateTime($setRecord->due_date);
237 :     my $answerDate = formatDateTime($setRecord->answer_date);
238 :     my $setHeader = $setRecord->set_header;
239 :    
240 :     my @problemList = $db->listGlobalProblems($setName);
241 :     my $problemList = '';
242 :     foreach my $prob (sort {$a <=> $b} @problemList) {
243 : gage 1667 my $problemRecord = $db->getGlobalProblem($setName, $prob); # checked
244 :     die "global problem $prob for set $setName not found" unless defined($problemRecord);
245 : gage 1428 my $source_file = $problemRecord->source_file();
246 :     my $value = $problemRecord->value();
247 :     my $max_attempts = $problemRecord->max_attempts();
248 :     $problemList .= "$source_file, $value, $max_attempts \n";
249 :     }
250 :     my $fileContents = <<EOF;
251 :    
252 :     openDate = $openDate
253 :     dueDate = $dueDate
254 :     answerDate = $answerDate
255 :     paperHeaderFile = $setHeader
256 :     screenHeaderFile = $setHeader
257 :     problemList =
258 :    
259 :     $problemList
260 :    
261 :    
262 :    
263 :     EOF
264 :    
265 :    
266 :     $self->saveProblem($fileContents, $filePath);
267 : toenail 2091 $self->addmessage(CGI::div({class=>"ResultsWithoutError"}, CGI::p("Set definition saved to $filePath")));
268 : gage 1428
269 :     }
270 : malsyned 935 }
271 :    
272 : malsyned 947
273 : gage 860 sub body {
274 : malsyned 935 my ($self, @components) = @_;
275 : gage 1928 my $r = $self->r;
276 : gage 1930 my $urlpath = $r->urlpath;
277 : gage 1928 my $db = $r->db;
278 :     my $ce = $r->ce;
279 :     my $authz = $r->authz;
280 :     my $user = $r->param('user');
281 : gage 1930 my $courseName = $urlpath->arg("courseID");
282 :     my $setName = $urlpath->arg("setID");
283 : gage 1928 my $setRecord = $db->getGlobalSet($setName); # checked
284 : gage 1667 die "global set $setName not found." unless $setRecord;
285 : gage 1930 my @editForUser = $r->param('editForUser');
286 : malsyned 947 # some useful booleans
287 : gage 1930 my $forUsers = scalar(@editForUser);
288 :     my $forOneUser = $forUsers == 1;
289 : malsyned 1017
290 :     return CGI::em("You are not authorized to access the Instructor tools.") unless $authz->hasPermissions($user, "access_instructor_tools");
291 :    
292 : malsyned 959 ## Set Form ##
293 : malsyned 947 my $userSetRecord;
294 :     my %overrideArgs;
295 :     if ($forOneUser) {
296 : gage 1667 $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked
297 :     die "set $setName not found for user $editForUser[0]." unless $userSetRecord;
298 : malsyned 947 foreach my $field (@{SET_FIELDS()}) {
299 : malsyned 959 $overrideArgs{$field} = [defined $userSetRecord->$field, ($field =~ /_date$/ ? formatDateTime($userSetRecord->$field) : $userSetRecord->$field)];
300 : malsyned 947 }
301 :     } else {
302 :     foreach my $field (@{SET_FIELDS()}) {
303 :     $overrideArgs{$field} = [undef, undef];
304 :     }
305 :     }
306 : malsyned 1460 print CGI::h2({}, "Set Data"), "\n";
307 :     if (@editForUser) {
308 :     print CGI::p("Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
309 :     }
310 : toenail 2103
311 :     my @headers = $self->recurseDirectory($self->{ce}->{courseDirs}->{templates}, '(?i)header.*?\\.pg$');
312 :     map { s|^$self->{ce}->{courseDirs}->{templates}/?|| } @headers;
313 :     @headers = sort @headers;
314 :    
315 : malsyned 959 print CGI::start_form({method=>"post", action=>$r->uri}), "\n";
316 : malsyned 935 print CGI::table({},
317 :     CGI::Tr({}, [
318 : gage 1348 setRowHTML( "Open Date:",
319 :     "open_date",
320 :     formatDateTime($setRecord->open_date),
321 :     undef,
322 :     @{$overrideArgs{open_date}})."\n",
323 :     setRowHTML( "Due Date:",
324 :     "due_date",
325 :     formatDateTime($setRecord->due_date),
326 :     undef,
327 :     @{$overrideArgs{due_date}})."\n",
328 :     setRowHTML( "Answer Date:",
329 :     "answer_date",
330 :     formatDateTime($setRecord->answer_date),
331 :     undef,
332 :     @{$overrideArgs{answer_date}})."\n",
333 : toenail 2103 # setRowHTML( "Set Header:", "set_header",
334 :     # $setRecord->set_header,
335 :     # 32,
336 :     # @{$overrideArgs{set_header}})."\n",
337 : gage 1348 # FIXME we're not using this right at the moment as far as I know. There may someday be a use for it, so don't take this out yet.
338 :     # setRowHTML( "Problem Header:",
339 :     # "problem_header",
340 :     # $setRecord->problem_header,
341 :     # undef,
342 : toenail 2097 # @{$overrideArgs{problem_header}})."\n",
343 : toenail 2103 CGI::td({}, ["Set Header:" , ($forOneUser) ? $setRecord->set_header
344 :     : CGI::popup_menu(-name=>'set_header',
345 :     -values=>\@headers,
346 :     -default=>$setRecord->set_header)]) . "\n",
347 : malsyned 935 ])
348 :     );
349 : toenail 2103
350 : toenail 2097 if (@editForUser) {
351 :     my $publishedColor = ($setRecord->published) ? "Published" : "Unpublished";
352 :     print CGI::p("This set is currently", CGI::font({class=>$publishedColor}, (($setRecord->published) ? "Published" : "Unpublished")), CGI::br(), "(You cannot publish/unpublish a set for specific users.)");
353 :     } else {
354 :     print CGI::checkbox({type=>"checkbox", name=>"published", label=>"Published", value=>"1", checked=>(($setRecord->published) ? 1 : 0)}), CGI::br();
355 :    
356 :     }
357 :    
358 : gage 1428 print $self->hiddenEditForUserFields(@editForUser),
359 :     $self->hidden_authen_fields,
360 : jj 2038 CGI::input({type=>"submit", name=>"submit_set_changes", value=>"Save Set", style=>"{width: 13ex}"}),
361 : gage 1428 '&nbsp;';
362 : gage 1348
363 : gage 1428 #### link to edit setHeader
364 : gage 1930 my $PGProblemEditor = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
365 :     courseID => $courseName,
366 :     setID => $setName,
367 :     problemID => '0'
368 :     );
369 :     my $setHeaderEditLink = $self->systemLink($PGProblemEditor);
370 : gage 1348 if (defined($setRecord) and $setRecord->set_header) {
371 : gage 1930 print CGI::a({-href=>$setHeaderEditLink},'Edit set header: '.$setRecord->set_header);
372 : gage 1348 }
373 : gage 1428
374 :     print CGI::br(),
375 : jj 2038 CGI::submit({ name=>"export_set", label=>"Export Set", style=>"{width: 13ex}"} ),
376 : gage 1428 ' as ',
377 : jj 1979 CGI::input({type=>'text',name=>'export_file_name',value=>"set$setName.def",size=>32});
378 : gage 1428
379 : toenail 2091 print CGI::br();
380 : gage 1428
381 :    
382 :    
383 : malsyned 935 print CGI::end_form();
384 :    
385 : malsyned 1005 my $problemCount = $db->listGlobalProblems($setName);
386 :     print CGI::h2({}, "Problems"), "\n";
387 :     print CGI::p({}, "This set contains $problemCount problem" . ($problemCount == 1 ? "" : "s").".");
388 : gage 1930 #FIXME
389 :     # the code below doesn't work ---
390 :     # get message
391 :     #no type matches module WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser with args at
392 :     # /home/gage/webwork/webwork-modperl/lib/WeBWorK/URLPath.pm line 497.
393 :     # error in URLPath.pm??????
394 : gage 1934 my $problemSetListPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemList",
395 :     courseID => $courseName,
396 :     setID => $setName
397 :     );
398 :    
399 :     my $editProblemsURL = $self->systemLink($problemSetListPage,
400 :     params => ['editForUser'] # include all editForUser parameters
401 :     );
402 :     my $usersAssignedToSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
403 :     courseID => $courseName,
404 :     setID => $setName
405 :     );
406 :    
407 :     my $editUsersAssignedToSetURL = $self->systemLink($usersAssignedToSetPage,
408 :    
409 :     );
410 :     print CGI::a({href=>$editProblemsURL},
411 : gage 1928 (@editForUser) ? "Edit the list of problems in this set for ". CGI::b(join ", ", @editForUser) :
412 :     "Edit the list of problems in this set");
413 :    
414 : gage 1930 unless (@editForUser) { # this is not needed when we are editing details for a user
415 :     my $userCount = $db->listUsers;
416 :     my $usersOfSet = $db->countSetUsers($setName);
417 :     print CGI::h2({}, "Users"), "\n";
418 :     print CGI::p({}, "This set is assigned to ".$self->userCountMessage($usersOfSet, $userCount).".");
419 : gage 1934 print CGI::a({href=>$editUsersAssignedToSetURL}, "Determine who this set is assigned to");
420 : gage 1930 }
421 : malsyned 935
422 :     return "";
423 :     }
424 : gage 1428 ###########################################################################
425 :     # utility
426 :     ###########################################################################
427 :     sub saveProblem {
428 :     my $self = shift;
429 :     my ($body, $probFileName)= @_;
430 :     local(*PROBLEM);
431 :     open (PROBLEM, ">$probFileName") ||
432 :     $self->submission_error("Could not open $probFileName for writing.
433 :     Check that the permissions for this problem are 660 (-rw-rw----)");
434 :     print PROBLEM $body;
435 :     close PROBLEM;
436 :     chmod 0660, "$probFileName" ||
437 :     $self->submission_error("
438 :     CAN'T CHANGE PERMISSIONS ON FILE $probFileName");
439 :     }
440 : gage 860 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9