Parent Directory
|
Revision Log
update copyright date range -- 2000-2006. this is probably overkill, since there are some files that were created after 2000 and some files that were last modified before 2006.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetEditor.pm,v 1.65 2005/07/14 13:15:26 glarose 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 17 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 use File::Copy; 30 use WeBWorK::DB::Record::Problem; 31 use WeBWorK::Utils qw(readFile list2hash listFilesRecursive max); 32 33 our $rowheight = 20; #controls the length of the popup menus. 34 our $libraryName; #library directory name 35 36 # added gateway fields here: everything after published 37 use constant SET_FIELDS => [qw(open_date due_date answer_date set_header hardcopy_header published assignment_type attempts_per_version version_time_limit versions_per_interval time_interval problem_randorder)]; 38 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; 39 use constant PROBLEM_USER_FIELDS => [qw(problem_seed status num_correct num_incorrect)]; 40 41 # This defines allowed values for the assignment_type field in the set 42 # definition. Ideally we should probably have this imported from some 43 # global file (global.conf?) 44 use constant ASSIGNMENT_TYPES => [ qw(default gateway proctored_gateway) ]; 45 46 sub getSetName { 47 my ($self, $pathSetName) = @_; 48 if (ref $pathSetName eq "HASH") { 49 $pathSetName = undef; 50 } 51 return $pathSetName; 52 } 53 54 # One wrinkle here: if $override is undefined, do the global thing, 55 # otherwise, it's truth value determines the checkbox and the current fieldValue is not directly editable 56 sub setRowHTML { 57 my ($description, $fieldName, $fieldValue, $size, $override, $overrideValue) = @_; 58 59 my $attributeHash = {type=>"text", name=>$fieldName, value=>$fieldValue}; 60 $attributeHash->{size} = $size if defined $size; 61 62 my $input = (defined $override) ? $fieldValue : CGI::input($attributeHash); 63 64 my $html = CGI::td({}, [$description, $input]); 65 66 if (defined $override) { 67 $attributeHash->{name}="${fieldName}_override"; 68 $attributeHash->{value}=($override ? $overrideValue : "" ); 69 70 $html .= CGI::td({}, [ 71 CGI::checkbox({ 72 type=>"checkbox", 73 name=>"override", 74 label=>"override with:", 75 value=>$fieldName, 76 checked=>($override ? 1 : 0) 77 }), 78 CGI::input($attributeHash) 79 ]); 80 } 81 82 return $html; 83 84 } 85 86 # Initialize does all of the form processing. It's extensive, and could probably be cleaned up and 87 # consolidated with a little abstraction. 88 sub initialize { 89 my ($self) = @_; 90 my $r = $self->r; 91 my $db = $r->db; 92 my $ce = $r->ce; 93 my $authz = $r->authz; 94 my $user = $r->param('user'); 95 #my $setName = $self->getSetName(@components); 96 my $setName = $r->urlpath->arg("setID"); 97 my $setRecord = $db->getGlobalSet($setName); #checked 98 die "global set $setName not found." unless $setRecord; 99 100 $self->{set} = $setRecord; 101 my @editForUser = $r->param('editForUser'); 102 # some useful booleans 103 my $forUsers = scalar(@editForUser); 104 my $forOneUser = $forUsers == 1; 105 106 # build a quick lookup table 107 my %overrides = list2hash $r->param('override'); 108 109 # Check permissions 110 return unless ($authz->hasPermissions($user, "access_instructor_tools")); 111 return unless ($authz->hasPermissions($user, "modify_problem_sets")); 112 113 ################################################### 114 # The set form was submitted with the save button pressed 115 # Save changes to the set 116 ################################################### 117 118 if (defined($r->param('submit_set_changes'))) { 119 120 if (!$forUsers) { 121 foreach (@{SET_FIELDS()}) { 122 # this is an unnecessary logical division: we deal with gateway 123 # fields separately from the rest, for no particular reason other 124 # than it makes life somewhat easier for those who don't care 125 # about gateways 126 if ( /(assignment_type)|(attempts_per_version)|(version_time_limit)|(versions_per_interval)|(time_interval)|(problem_randorder)/ ) { 127 if (defined($r->param($_))) { 128 if ( /assignment_type/ && 129 $r->param($_) =~ /default/i ) { 130 $setRecord->$_(undef); 131 } else { 132 133 if ( m/time/ ) { 134 # times are input as minutes, not seconds, so multiply by 60 135 $setRecord->$_( 60*($r->param($_)) ); 136 } else { 137 $setRecord->$_( $r->param($_) ); 138 } 139 } 140 141 } elsif ( m/assignment_type/ ) { 142 $setRecord->$_(undef); 143 } 144 145 # we now return you to your regularly scheduled programming 146 } else { 147 if (defined($r->param($_))) { 148 if (m/_date$/) { 149 $setRecord->$_($self->parseDateTime($r->param($_))); 150 } else { 151 $setRecord->$_($r->param($_)) unless ($_ eq 'set_header' and $r->param($_) eq "Use System Default"); 152 153 if($_ eq 'set_header') { 154 # be nice and copy the default file here if it doesn't exist yet 155 # empty set headers lead to trouble 156 my $set_header = ($r->param($_) eq "Use System Default") ? $setRecord->set_header : $r->param($_); 157 158 my $newheaderpath = $r->{ce}->{courseDirs}->{templates} . '/'. $set_header; 159 unless(($set_header !~ /\S/) or -e $newheaderpath) { 160 my $default_header = $ce->{webworkFiles}->{screenSnippets}->{setHeader}; 161 File::Copy::copy($default_header, $newheaderpath); 162 } 163 } 164 } 165 } else { 166 if (m/published$/) { 167 $setRecord->$_(0); 168 } 169 } 170 } 171 } 172 173 174 175 176 ################################################### 177 # Check that the open, due and answer dates are in increasing order. 178 # Bail if this is not correct. 179 ################################################### 180 if ($setRecord->open_date > $setRecord->due_date) { 181 $self->addbadmessage('Error: Due date must come after open date'); 182 return; 183 } 184 if ($setRecord->due_date > $setRecord->answer_date) { 185 $self->addbadmessage('Error: Answer date must come after due date'); 186 return; 187 } 188 ################################################### 189 # End date check section. 190 ################################################### 191 $self->addgoodmessage("Changes to set $setName were successfully saved."); 192 $db->putGlobalSet($setRecord); 193 } else { 194 195 my $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked 196 die "set $setName not found for $editForUser[0]." unless $userSetRecord; 197 foreach my $field (@{SET_FIELDS()}) { 198 if (defined $r->param("${field}_override")) { 199 if (exists $overrides{$field}) { 200 if ($field =~ m/_date$/) { 201 $userSetRecord->$field($self->parseDateTime($r->param("${field}_override"))); 202 } else { 203 $userSetRecord->$field($r->param("${field}_override")); 204 } 205 } else { 206 $userSetRecord->$field(undef); 207 } 208 } 209 } 210 ################################################### 211 # Check that the open, due and answer dates are in increasing order. 212 # Bail if this is not correct. 213 ################################################### 214 my $active_open_date = $userSetRecord->open_date ? $userSetRecord->open_date : $setRecord->open_date; 215 my $active_due_date = $userSetRecord->due_date ? $userSetRecord->due_date : $setRecord->due_date; 216 my $active_answer_date = $userSetRecord->answer_date ? $userSetRecord->answer_date : $setRecord->answer_date; 217 if ( $active_open_date > $active_due_date ) { 218 $self->addbadmessage('Error: Due date override must come after open date'); 219 return; 220 } 221 if ( $active_due_date > $active_answer_date ) { 222 $self->addbadmessage('Error: Answer date override must come after due date'); 223 return; 224 } 225 ################################################### 226 # End date check section. 227 ################################################### 228 $self->addgoodmessage("Changes to set $setName for user ", CGI::b($editForUser[0]), "were successfully saved."); 229 $db->putUserSet($userSetRecord); 230 } 231 232 } 233 234 ################################################### 235 # The set form was submitted with the export button pressed 236 # Export the set structure to a set definition file 237 ################################################### 238 239 if ( defined($r->param('export_set')) ) { 240 my $fileName = $r->param('export_file_name'); 241 die "Please specify a file name for saving the set definition" unless $fileName; 242 $fileName .= '.def' unless $fileName =~ /\.def$/; 243 my $filePath = $ce->{courseDirs}->{templates}.'/'.$fileName; 244 # back up existing file 245 if(-e $filePath) { 246 rename($filePath,"$filePath.bak") or 247 die "Can't rename $filePath to $filePath.bak ", 248 "Check permissions for webserver on directories. $!"; 249 } 250 my $openDate = $self->formatDateTime($setRecord->open_date); 251 my $dueDate = $self->formatDateTime($setRecord->due_date); 252 my $answerDate = $self->formatDateTime($setRecord->answer_date); 253 my $setHeader = $setRecord->set_header; 254 255 my @problemList = $db->listGlobalProblems($setName); 256 my $problemList = ''; 257 foreach my $prob (sort {$a <=> $b} @problemList) { 258 my $problemRecord = $db->getGlobalProblem($setName, $prob); # checked 259 die "global problem $prob for set $setName not found" unless defined($problemRecord); 260 my $source_file = $problemRecord->source_file(); 261 my $value = $problemRecord->value(); 262 my $max_attempts = $problemRecord->max_attempts(); 263 $problemList .= "$source_file, $value, $max_attempts \n"; 264 } 265 my $fileContents = <<EOF; 266 267 openDate = $openDate 268 dueDate = $dueDate 269 answerDate = $answerDate 270 paperHeaderFile = $setHeader 271 screenHeaderFile = $setHeader 272 problemList = 273 274 $problemList 275 276 277 278 EOF 279 280 281 $self->saveProblem($fileContents, $filePath); 282 $self->addgoodmessage(CGI::p("Set definition saved to $filePath")); 283 284 } 285 } 286 287 288 sub body { 289 my ($self, @components) = @_; 290 my $r = $self->r; 291 my $urlpath = $r->urlpath; 292 my $db = $r->db; 293 my $ce = $r->ce; 294 my $authz = $r->authz; 295 my $user = $r->param('user'); 296 my $courseName = $urlpath->arg("courseID"); 297 my $setName = $urlpath->arg("setID"); 298 my $setRecord = $db->getGlobalSet($setName); # checked 299 die "global set $setName not found." unless $setRecord; 300 my @editForUser = $r->param('editForUser'); 301 # some useful booleans 302 my $forUsers = scalar(@editForUser); 303 my $forOneUser = $forUsers == 1; 304 305 # Check permissions 306 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") 307 unless $authz->hasPermissions($r->param("user"), "access_instructor_tools"); 308 309 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify homework sets.") 310 unless $authz->hasPermissions($r->param("user"), "modify_problem_sets"); 311 312 313 ## Set Form ## 314 my $userSetRecord; 315 my %overrideArgs; 316 if ($forOneUser) { 317 $userSetRecord = $db->getUserSet($editForUser[0], $setName); #checked 318 die "set $setName not found for user $editForUser[0]." unless $userSetRecord; 319 foreach my $field (@{SET_FIELDS()}) { 320 $overrideArgs{$field} = [defined $userSetRecord->$field && $userSetRecord->$field ne "", ($field =~ /_date$/ ? $self->formatDateTime($userSetRecord->$field) : $userSetRecord->$field)]; 321 } 322 } else { 323 foreach my $field (@{SET_FIELDS()}) { 324 $overrideArgs{$field} = [undef, undef]; 325 } 326 } 327 print CGI::h2({}, "Set Data"), "\n"; 328 if (@editForUser) { 329 print CGI::p("Editing user-specific overrides for ". CGI::b(join ", ", @editForUser)); 330 } 331 332 my $templates_dir = $r->ce->{courseDirs}->{templates}; 333 my %probLibs = %{ $r->ce->{courseFiles}->{problibs} }; 334 my $exempt_dirs = join("|", keys %probLibs); 335 my @headers = listFilesRecursive( 336 $templates_dir, 337 qr/header.*\.pg$/i, # match these files 338 qr/^(?:$exempt_dirs|CVS)$/, # prune these directories 339 0, # match against file name only 340 1, # prune against path relative to $templates_dir 341 ); 342 343 @headers = sort @headers; 344 unshift (@headers, "Use System Default"); 345 346 print CGI::start_form({method=>"post", action=>$r->uri}), "\n"; 347 print CGI::table({}, 348 CGI::Tr({}, [ 349 setRowHTML( "Open Date:", 350 "open_date", 351 $self->formatDateTime($setRecord->open_date), 352 undef, 353 @{$overrideArgs{open_date}})."\n", 354 setRowHTML( "Due Date:", 355 "due_date", 356 $self->formatDateTime($setRecord->due_date), 357 undef, 358 @{$overrideArgs{due_date}})."\n", 359 setRowHTML( "Answer Date:", 360 "answer_date", 361 $self->formatDateTime($setRecord->answer_date), 362 undef, 363 @{$overrideArgs{answer_date}})."\n", 364 # setRowHTML( "Set Header:", "set_header", 365 # $setRecord->set_header, 366 # 32, 367 # @{$overrideArgs{set_header}})."\n", 368 # 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. 369 # setRowHTML( "Problem Header:", 370 # "hardcopy_header", 371 # $setRecord->hardcopy_header, 372 # undef, 373 # @{$overrideArgs{hardcopy_header}})."\n", 374 CGI::td({}, [ "Set Header:" , 375 ($forOneUser) 376 ? $setRecord->set_header || "None selected." 377 : CGI::popup_menu( 378 -name=>'set_header', 379 -values=>\@headers, 380 -default=>0) . 381 "(currently: " . ($setRecord->set_header || "None selected.") . ")" . "\n", 382 # 383 # assignment type added for gateway compatibility 384 CGI::td({}, [ "Assignment Type:", 385 ($forOneUser) ? 386 $setRecord->assignment_type || "Default." : 387 CGI::popup_menu( -name=>'assignment_type', 388 -values=>ASSIGNMENT_TYPES, 389 -default=>($setRecord->assignment_type || "default.") ) . 390 " (currently: " . 391 ( $setRecord->assignment_type || "default." ) . 392 ")\n" ]) . "\n", 393 ]) 394 ]) 395 ); 396 397 # add input fields for gateway tests, if we're dealing with that type of assignment 398 if ( defined($setRecord->assignment_type) && 399 $setRecord->assignment_type =~ /gateway/ ) { 400 print "Gateway parameters:", CGI::br(), "\n"; 401 my $versionTimeLimit = ( defined( $setRecord->version_time_limit ) && 402 $setRecord->version_time_limit ) ? 403 int(($setRecord->version_time_limit() + 0.5)/60) : 404 0; 405 my $timeInterval = ( defined( $setRecord->time_interval ) && 406 $setRecord->time_interval ne '' ) ? 407 int(($setRecord->time_interval() + 0.5)/60) : 408 720; # default is 12 hours 409 print CGI::table( {}, 410 CGI::Tr( {}, [ 411 CGI::td( {}, " ", 412 setRowHTML( "Attempts per test version", 413 "attempts_per_version", 414 $setRecord->attempts_per_version ? 415 $setRecord->attempts_per_version : 1, 416 3, 417 @{$overrideArgs{attempts_per_version}}) . 418 "\n" ), 419 CGI::td( {}, " ", 420 setRowHTML( "Time limit for test (min)", 421 "version_time_limit", 422 $versionTimeLimit, 3, 423 @{$overrideArgs{version_time_limit}}) . 424 "\n" ), 425 CGI::td( {}, " ", 426 setRowHTML( "Versions per time interval (0=infty)", 427 "versions_per_interval", 428 $setRecord->versions_per_interval ne '' ? 429 $setRecord->versions_per_interval : 1, 430 3, 431 @{$overrideArgs{versions_per_interval}}). 432 "\n" ), 433 CGI::td( {}, " ", 434 setRowHTML( "Time interval (min)", 435 "time_interval", $timeInterval, 4, 436 @{$overrideArgs{time_interval}}) . 437 "\n" ), 438 CGI::td( {}, " ", 439 setRowHTML( "Order problems randomly in set (0|1)", 440 "problem_randorder", 441 $setRecord->problem_randorder ne '' ? 442 $setRecord->problem_randorder : 1, 443 3, 444 @{$overrideArgs{problem_randorder}}) . 445 "\n" ) 446 ] ) 447 ), "\n"; 448 } 449 450 451 if (@editForUser) { 452 my $publishedClass = ($setRecord->published) ? "Published" : "Unpublished"; 453 my $publishedText = ($setRecord->published) ? "visible to students" : "hidden from students"; 454 print CGI::p("This set is currently", CGI::font({class=>$publishedClass}, $publishedText), 455 CGI::br(), "(You cannot hide or make a set visible for specific users.)"); 456 } else { 457 print CGI::checkbox({type=>"checkbox", name=>"published", label=>"Visible to students", value=>"1", checked=>(($setRecord->published) ? 1 : 0)}), CGI::br(); 458 459 } 460 461 print $self->hiddenEditForUserFields(@editForUser), 462 $self->hidden_authen_fields, 463 CGI::input({type=>"submit", name=>"submit_set_changes", value=>"Save Set", style=>"{width: 13ex}"}), 464 ' '; 465 466 #### link to edit setHeader 467 my $PGProblemEditor = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 468 courseID => $courseName, 469 setID => $setName, 470 problemID => '0' 471 ); 472 my $setHeaderEditLink = $self->systemLink($PGProblemEditor); 473 if (defined($setRecord) and $setRecord->set_header) { 474 print CGI::a({-href=>$setHeaderEditLink},'Edit set header: '.$setRecord->set_header); 475 } 476 477 print CGI::br(), 478 CGI::submit({ name=>"export_set", label=>"Export Set", style=>"{width: 13ex}"} ), 479 ' as ', 480 CGI::input({type=>'text',name=>'export_file_name',value=>"set$setName.def",size=>32}); 481 482 print CGI::br(); 483 484 485 486 print CGI::end_form(); 487 488 my $problemCount = $db->listGlobalProblems($setName); 489 print CGI::h2({}, "Problems"), "\n"; 490 print CGI::p({}, "This set contains $problemCount problem" . ($problemCount == 1 ? "" : "s")."."); 491 #FIXME 492 # the code below doesn't work --- 493 # get message 494 #no type matches module WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser with args at 495 # /home/gage/webwork/webwork-modperl/lib/WeBWorK/URLPath.pm line 497. 496 # error in URLPath.pm?????? 497 my $problemSetListPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ProblemList", 498 courseID => $courseName, 499 setID => $setName 500 ); 501 502 my $editProblemsURL = $self->systemLink($problemSetListPage, 503 params => ['editForUser'] # include all editForUser parameters 504 ); 505 my $usersAssignedToSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", 506 courseID => $courseName, 507 setID => $setName 508 ); 509 510 my $editUsersAssignedToSetURL = $self->systemLink($usersAssignedToSetPage, 511 512 ); 513 print CGI::a({href=>$editProblemsURL}, 514 (@editForUser) ? "Edit the list of problems in this set for ". CGI::b(join ", ", @editForUser) : 515 "Edit the list of problems in this set"); 516 517 unless (@editForUser) { # this is not needed when we are editing details for a user 518 my $userCount = $db->listUsers; 519 my $usersOfSet = $db->countSetUsers($setName); 520 print CGI::h2({}, "Users"), "\n"; 521 print CGI::p({}, "This set is assigned to ".$self->userCountMessage($usersOfSet, $userCount)."."); 522 print CGI::a({href=>$editUsersAssignedToSetURL}, "Determine who this set is assigned to"); 523 } 524 525 return ""; 526 } 527 ########################################################################### 528 # utility 529 ########################################################################### 530 sub saveProblem { 531 my $self = shift; 532 my ($body, $probFileName)= @_; 533 local(*PROBLEM); 534 open (PROBLEM, ">$probFileName") || 535 $self->addbadmessage(CGI::p("Could not open $probFileName for writing. Check that the permissions for this problem are 660 (-rw-rw----)")); 536 print PROBLEM $body; 537 close PROBLEM; 538 chmod 0660, "$probFileName" || 539 $self->addbadmessage(CGI::p("CAN'T CHANGE PERMISSIONS ON FILE $probFileName")); 540 } 541 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |