Parent Directory
|
Revision Log
This commit was manufactured by cvs2svn to create branch 'rel-2-3-dev'.
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/ProblemList.pm,v 1.37 2006/07/12 01:19:15 gage 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::ProblemList; 18 use base qw(WeBWorK::ContentGenerator::Instructor); 19 20 =head1 NAME 21 22 WeBWorK::ContentGenerator::Instructor::ProblemList - List and edit problems in a set 23 24 =cut 25 26 # This file is currently in an intermediate form, at best. The look 27 # for global editting has changed significantly. The look for dealing 28 # with a single student is pretty much the same as before, except for 29 # rendering the problems. 30 31 # For now, this leaves the code in a clumsy state, with the two versions 32 # given in their entirety different clauses of an if-then-else. 33 34 use strict; 35 use warnings; 36 #use CGI qw(-nosticky ); 37 use WeBWorK::CGI; 38 use WeBWorK::Utils qw(readDirectory list2hash max); 39 use WeBWorK::DB::Record::Set; 40 use WeBWorK::Utils::Tasks qw(renderProblems); 41 42 43 use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; 44 use constant PROBLEM_USER_FIELDS => [qw(problem_seed status num_correct num_incorrect)]; 45 46 sub problemElementHTML { 47 my ($fieldName, $fieldValue, $size, $override, $overrideValue) = @_; 48 my $attributeHash = {type => "text", name => $fieldName, value => $fieldValue}; 49 $attributeHash->{size} = $size if defined $size; 50 my $html; 51 # my $html = CGI::input($attributeHash); 52 53 unless (defined $override) { 54 $html = CGI::input($attributeHash); 55 } else { 56 $html = $fieldValue; 57 $attributeHash->{name} = "${fieldName}.override"; 58 $attributeHash->{value} = ($override ? $overrideValue : ""); 59 $html = "default:" . CGI::br() . $html . CGI::br() 60 . CGI::checkbox({ 61 type => "checkbox", 62 name => "override", 63 label => "override:", 64 value => $fieldName, 65 checked => ($override ? 1 : 0) 66 }) 67 . CGI::br() 68 . CGI::input($attributeHash); 69 } 70 71 return $html; 72 } 73 74 sub problem_number_popup { 75 my $num = shift; 76 my $total = shift; 77 return (CGI::popup_menu(-name => "problem_num_$num", 78 -values => [1..$total], 79 -default => $num)); 80 } 81 82 sub handle_problem_numbers { 83 my $newProblemNumbersref = shift; 84 my %newProblemNumbers = %$newProblemNumbersref; 85 my $maxNum = shift; 86 my $db = shift; 87 my $setName = shift; 88 my $force = shift || 0; 89 my @sortme=(); 90 my ($j, $val); 91 92 foreach $j (keys %newProblemNumbers) { 93 # what happens our first time on this page 94 return "" if (not defined $newProblemNumbers{"$j"}); 95 if ($newProblemNumbers{"$j"} != $j) { 96 $force = 1; 97 $val = 1000 * $newProblemNumbers{$j} - $j; 98 } else { 99 $val = 1000 * $newProblemNumbers{$j}; 100 } 101 push @sortme, [$j, $val]; 102 $newProblemNumbers{$j} = $db->getGlobalProblem($setName, $j); 103 die "global $j for set $setName not found." unless $newProblemNumbers{$j}; 104 } 105 106 return "" unless $force; 107 108 @sortme = sort {$a->[1] <=> $b->[1]} @sortme; 109 # now, for global and each user with this set, loop through problem list 110 # get all of the problem records 111 # assign new problem numbers 112 # loop - if number is new, put the problem record 113 # print "Sorted to get ". join(', ', map {$_->[0] } @sortme) ."<p>\n"; 114 115 116 # Now, three stages. First global values 117 118 for ($j = 0; $j < scalar @sortme; $j++) { 119 if($sortme[$j]->[0] == $j + 1) { 120 # do nothing 121 } elsif (not defined $newProblemNumbers{$j + 1}) { 122 $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); 123 $db->addGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); 124 } else { 125 $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); 126 $db->putGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); 127 } 128 } 129 130 my @setUsers = $db->listSetUsers($setName); 131 my (@problist, $user); 132 133 foreach $user (@setUsers) { 134 for $j (keys %newProblemNumbers) { 135 $problist[$j] = $db->getUserProblem($user, $setName, $j); 136 die " problem $j for set $setName and effective user $user not found" 137 unless $problist[$j]; 138 } 139 # ok, now we have all problem data for $user 140 for($j = 0; $j < scalar @sortme; $j++) { 141 if ($sortme[$j]->[0] == $j + 1) { 142 # do nothing 143 } elsif (not defined $newProblemNumbers{$j + 1}) { 144 $problist[$sortme[$j]->[0]]->problem_id($j + 1); 145 $db->addUserProblem($problist[$sortme[$j]->[0]]); 146 } else { 147 $problist[$sortme[$j]->[0]]->problem_id($j + 1); 148 $db->putUserProblem($problist[$sortme[$j]->[0]]); 149 } 150 } 151 } 152 153 154 foreach ($j = scalar @sortme; $j < $maxNum; $j++) { 155 if (defined $newProblemNumbers{$j + 1}) { 156 $db->deleteGlobalProblem($setName, $j+1); 157 } 158 } 159 160 return join(', ', map {$_->[0]} @sortme); 161 } 162 163 # swap index given with next bigger index 164 # leftover from when we had up/down buttons 165 # maybe we will bring them back 166 167 sub moveme { 168 my $index = shift; 169 my $db = shift; 170 my $setName = shift; 171 my (@problemList) = @_; 172 my ($prob1, $prob2, $prob); 173 174 foreach $prob (@problemList) { 175 my $problemRecord = $db->getGlobalProblem($setName, $prob); # checked 176 die "global $prob for set $setName not found." unless $problemRecord; 177 if ($problemRecord->problem_id == $index) { 178 $prob1 = $problemRecord; 179 } elsif ($problemRecord->problem_id == $index + 1) { 180 $prob2 = $problemRecord; 181 } 182 } 183 if (not defined $prob1 or not defined $prob2) { 184 die "cannot find problem $index or " . ($index + 1); 185 } 186 187 $prob1->problem_id($index + 1); 188 $prob2->problem_id($index); 189 $db->putGlobalProblem($prob1); 190 $db->putGlobalProblem($prob2); 191 192 my @setUsers = $db->listSetUsers($setName); 193 194 my $user; 195 foreach $user (@setUsers) { 196 $prob1 = $db->getUserProblem($user, $setName, $index); #checked 197 die " problem $index for set $setName and effective user $user not found" 198 unless $prob1; 199 $prob2 = $db->getUserProblem($user, $setName, $index+1); #checked 200 die " problem $index for set $setName and effective user $user not found" 201 unless $prob2; 202 $prob1->problem_id($index+1); 203 $prob2->problem_id($index); 204 $db->putUserProblem($prob1); 205 $db->putUserProblem($prob2); 206 } 207 } 208 209 210 sub initialize { 211 my ($self) = @_; 212 my $r = $self->r; 213 my $db = $r->db; 214 my $ce = $r->ce; 215 my $authz = $r->authz; 216 my $user = $r->param('user'); 217 my $setName = $r->urlpath->arg("setID"); 218 my $setRecord = $db->getGlobalSet($setName); # checked 219 die "global set $setName not found." unless $setRecord; 220 221 $self->{set} = $setRecord; 222 my @editForUser = $r->param('editForUser'); 223 # some useful booleans 224 my $forUsers = scalar(@editForUser); 225 my $forOneUser = $forUsers == 1; 226 227 # Check permissions 228 return unless ($authz->hasPermissions($user, "access_instructor_tools")); 229 return unless ($authz->hasPermissions($user, "modify_problem_sets")); 230 231 # build a quick lookup table 232 my %overrides = list2hash $r->param('override'); 233 234 my @problemList = $db->listGlobalProblems($setName); # the Problem form was submitted 235 if (defined($r->param('submit_problem_changes'))) { 236 foreach my $problem (@problemList) { 237 my $problemRecord = $db->getGlobalProblem($setName, $problem); # checked 238 die "global $problem for set $setName not found." unless $problemRecord; 239 240 foreach my $field (@{PROBLEM_FIELDS()}) { 241 my $paramName = "problem.${problem}.${field}"; 242 if (defined($r->param($paramName))) { 243 my $pvalue = $r->param($paramName); 244 if ($field eq "max_attempts") { 245 $pvalue =~ s/[^-\d]//g; 246 $pvalue = -1 if $pvalue eq ""; 247 } 248 $problemRecord->$field($pvalue); 249 } 250 } 251 $db->putGlobalProblem($problemRecord); 252 253 if ($forOneUser) { 254 my $userProblemRecord = $db->getUserProblem($editForUser[0], $setName, $problem); # checked 255 die " problem $problem for set $setName and effective user $editForUser[0] not found" unless $userProblemRecord; 256 foreach my $field (@{PROBLEM_USER_FIELDS()}) { 257 my $paramName = "problem.${problem}.${field}"; 258 if (defined($r->param($paramName))) { 259 $userProblemRecord->$field($r->param($paramName)); 260 } 261 } 262 263 foreach my $field (@{PROBLEM_FIELDS()}) { 264 my $paramName = "problem.${problem}.${field}"; 265 if (defined($r->param("${paramName}.override"))) { 266 if (exists $overrides{$paramName}) { 267 $userProblemRecord->$field($r->param("${paramName}.override")); 268 } else { 269 $userProblemRecord->$field(undef); 270 } 271 } 272 } 273 274 # the attempted field has to be computed from num correct and num incorrect 275 my $attempted = ($userProblemRecord->num_correct+$userProblemRecord->num_incorrect > 0) ? 1 : 0; 276 $userProblemRecord->attempted($attempted); 277 $db->putUserProblem($userProblemRecord); 278 } 279 } 280 281 foreach my $problem ($r->param('deleteProblem')) { 282 $db->deleteGlobalProblem($setName, $problem); 283 } 284 285 } else { 286 # Look for up and down buttons 287 my $index = 2; 288 while ($index <= scalar @problemList) { 289 if (defined $r->param("move.up.$index.x")) { 290 moveme($index-1, $db, $setName, @problemList); 291 } 292 $index++; 293 } 294 $index = 1; 295 296 while ($index < scalar @problemList) { 297 if (defined $r->param("move.down.$index.x")) { 298 moveme($index, $db, $setName, @problemList); 299 } 300 $index++; 301 } 302 } 303 } 304 305 sub title { 306 my ($self) = @_; 307 my $r = $self->r; 308 my $setName = $r->urlpath->arg("setID"); 309 310 #print "\n<!-- BEGIN " . __PACKAGE__ . "::title -->\n"; 311 print $r->urlpath->name. " for set $setName "; 312 #print "<!-- END " . __PACKAGE__ . "::title -->\n"; 313 314 return ""; 315 } 316 317 sub body { 318 my ($self) = @_; 319 my $r = $self->r; 320 my $db = $r->db; 321 my $ce = $r->ce; 322 my $authz = $r->authz; 323 my $user = $r->param('user'); 324 my $urlpath = $r->urlpath; 325 my $courseName = $urlpath->arg("courseID"); 326 my $setName = $urlpath->arg("setID"); 327 my $setRecord = $db->getGlobalSet($setName); 328 die "Global set $setName not found." unless $setRecord; 329 my @editForUser = $r->param('editForUser'); 330 331 my $problemListPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseName, setID => $setName); 332 my $problemListURL = $self->systemLink($problemListPage,authen=>0); 333 # some useful booleans 334 my $forUsers = scalar(@editForUser); 335 my $forOneUser = $forUsers == 1; 336 my $editForUserName = $editForUser[0]; 337 338 # Check permissions 339 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") 340 unless $authz->hasPermissions($r->param("user"), "access_instructor_tools"); 341 342 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.") 343 unless $authz->hasPermissions($r->param("user"), "modify_problem_sets"); 344 345 my $userCount = $db->listUsers(); 346 my $setUserCount = $db->countSetUsers($setName); 347 my $editUsersAssignedToSetURL = $self->systemLink( 348 $urlpath->newFromModule( 349 "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", 350 courseID => $courseName, setID => $setName)); 351 352 my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, 353 $self->userCountMessage($setUserCount, $userCount)); 354 355 $userCountMessage = "The set $setName is assigned to " . $userCountMessage . "."; 356 357 if (@editForUser) { 358 print CGI::p("$userCountMessage Editing user-specific overrides for ". CGI::b(join ", ", @editForUser)); 359 } else { 360 print CGI::p($userCountMessage); 361 } 362 363 ## Problems Form ## 364 my @problemList = $db->listGlobalProblems($setName); 365 print CGI::a({name=>"problems"}); 366 # print CGI::h2({}, "Problems"); 367 368 my %newProblemNumbers = (); 369 my $maxProblemNumber = -1; 370 for my $jj (@problemList) { 371 $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj); 372 $maxProblemNumber = $jj if $jj > $maxProblemNumber; 373 } 374 375 my $forceRenumber = $r->param('force_renumber') || 0; 376 #print "<p> old order: ".join(', ', @problemList); 377 #print "<p> new order: ". handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setName, $forceRenumber); 378 handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setName, $forceRenumber); 379 380 @problemList = $db->listGlobalProblems($setName); #reload them 381 382 if (scalar @problemList) { 383 # This will contain the mode list control 384 my $problemWord = 'Display Mode: '; 385 my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()}; 386 my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}}; 387 push @active_modes, 'None'; 388 my $default_mode = $r->param('mydisplaymode') || 'None'; 389 $problemWord .= CGI::popup_menu(-name => "mydisplaymode", 390 -values => \@active_modes, 391 -default => $default_mode); 392 $problemWord .= ' '. CGI::input({type => "submit", name => "refresh", value => "Refresh"}); 393 394 if($forUsers) { 395 ############### Order things differently. This is for one user 396 print CGI::start_form({method=>"POST", action=>$problemListURL.'#problems'}); 397 print CGI::start_table({border=>1, cellpadding=>4}); 398 print CGI::Tr({}, CGI::th({}, [ 399 ($forUsers ? () : ("Delete?")), 400 "Problem", 401 ($forUsers ? ("Status", "Problem Seed") : ()), 402 #"Source File", 403 $problemWord, "Max. Attempts", "Weight", 404 ($forUsers ? ("Number Correct", "Number Incorrect") : ()) 405 ])); 406 foreach my $problem (sort {$a <=> $b} @problemList) { 407 my $problemRecord = $db->getGlobalProblem($setName, $problem); # checked 408 die "global problem $problem in set $setName not found." unless $problemRecord; 409 my $problemID = $problemRecord->problem_id; 410 my $userProblemRecord; 411 my %problemOverrideArgs; 412 my @problem_html; 413 my $userSet = $db->getUserSet($editForUser[0], $setName); # checked 414 die "user homework set $setName not found." unless $userSet; 415 416 if ($forOneUser) { 417 $userProblemRecord = $db->getUserProblem($editForUser[0], $setName, $problem); # checked 418 die "problem $problem for set $setName and user $editForUser[0] not found. " unless $userProblemRecord; 419 foreach my $field (@{PROBLEM_FIELDS()}) { 420 $problemOverrideArgs{$field} = [defined $userProblemRecord->$field && $userProblemRecord->$field ne '', $userProblemRecord->$field]; 421 } 422 @problem_html = renderProblems(r=> $r, 423 user => $db->getUser($editForUser[0]), 424 displayMode=> $default_mode, 425 problem_number=> $problem, 426 this_set=> $userSet, 427 problem_seed=> $userProblemRecord->problem_seed, 428 problem_list =>[$problemRecord->source_file]); 429 430 # } elsif ($forUsers) { 431 # foreach my $field (@{PROBLEM_FIELDS()}) { 432 # $problemOverrideArgs{$field} = ["", ""]; 433 # } 434 } else { 435 foreach my $field (@{PROBLEM_FIELDS()}) { 436 $problemOverrideArgs{$field} = [undef, undef]; 437 } 438 } 439 440 print CGI::Tr({}, 441 CGI::td({}, [ 442 ($forUsers ? () : (CGI::input({type=>"checkbox", name=>"deleteProblem", value=>$problemID}))), 443 "$problemID " 444 . CGI::a({href=>$self->systemLink( $urlpath->new(type=>'problem_detail', 445 args=>{courseID =>$courseName,setID=>$setName,problemID=>$problemID} 446 ), 447 params =>{effectiveUser => $editForUserName} 448 )}, "view" 449 ) . " " 450 . CGI::a({href=>$self->systemLink( $urlpath->new(type=>'instructor_problem_editor_withset_withproblem', 451 args=>{courseID =>$courseName,setID=>$setName,problemID=>$problemID} 452 ) 453 )}, "edit" 454 ), 455 ($forUsers ? ( 456 problemElementHTML("problem.${problemID}.status", $userProblemRecord->status, "7"), 457 problemElementHTML("problem.${problemID}.problem_seed", $userProblemRecord->problem_seed, "7"), 458 ) : ()), 459 problemElementHTML("problem.${problemID}.source_file", $problemRecord->source_file, "40", @{$problemOverrideArgs{source_file}}) . 460 461 CGI::br(). CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}) 462 , 463 problemElementHTML("problem.${problemID}.max_attempts",$problemRecord->max_attempts,"7", @{$problemOverrideArgs{max_attempts}}), 464 problemElementHTML("problem.${problemID}.value",$problemRecord->value,"7", @{$problemOverrideArgs{value}}), 465 ($forUsers ? ( 466 problemElementHTML("problem.${problemID}.num_correct", $userProblemRecord->num_correct, "7"), 467 problemElementHTML("problem.${problemID}.num_incorrect", $userProblemRecord->num_incorrect, "7") 468 ) : ()) 469 ]) 470 471 ) 472 } 473 print CGI::end_table(); 474 print $self->hiddenEditForUserFields(@editForUser); 475 print $self->hidden_authen_fields; 476 print CGI::input({type=>"submit", name=>"submit_problem_changes", value=>"Save Problem Changes"}); 477 print CGI::end_form(); 478 } else { 479 ################################ Second go for global version 480 ################################ forUsers will be false 481 482 print CGI::start_form({method=>"POST", action=>$problemListURL.'#problems'}); 483 print CGI::start_table({border=>1, cellpadding=>4}); 484 print CGI::Tr({}, CGI::th({}, [ 485 "Data", 486 $problemWord 487 ])); 488 my (%shown_yet) = (); 489 foreach my $problem (sort {$a <=> $b} @problemList) { 490 my $problemRecord = $db->getGlobalProblem($setName, $problem); # checked 491 die "global problem $problem in set $setName not found." unless $problemRecord; 492 my $problemID = $problemRecord->problem_id; 493 my $userProblemRecord; 494 my %problemOverrideArgs; 495 my $have_this_one = ''; 496 if(defined($shown_yet{$problemRecord->source_file})) { 497 $have_this_one = "This problem uses the same source file as number " . $shown_yet{$problemRecord->source_file} . "."; 498 } else { 499 $shown_yet{$problemRecord->source_file} = $problemID; 500 } 501 502 my @problem_html = renderProblems(r=> $r, 503 user => $db->getUser($user), 504 displayMode=> $default_mode, 505 problem_number=> $problem, 506 problem_list =>[$problemRecord->source_file]); 507 508 509 foreach my $field (@{PROBLEM_FIELDS()}) { 510 $problemOverrideArgs{$field} = [undef, undef]; 511 } 512 513 print CGI::Tr({}, 514 CGI::td({}, [ 515 problem_number_popup($problemID, $maxProblemNumber) . 516 ' '. 517 CGI::a({href=>$self->systemLink( 518 $urlpath->new(type=>'instructor_problem_editor_withset_withproblem', 519 args=>{courseID =>$courseName,setID=>$setName,problemID=>$problemID} 520 ) 521 ), target=>"WW_Editor"}, "Edit it" ) . 522 ' '. 523 CGI::a({href=>$self->systemLink( $urlpath->new(type=>'problem_detail', 524 args=>{courseID =>$courseName,setID=>$setName,problemID=>$problemID} 525 ), 526 params =>{effectiveUser => $editForUserName} ), target=>"WW_View"}, "Try it") . 527 528 CGI::br() . 529 CGI::start_table(). 530 CGI::Tr({}, CGI::td({-align=>"right"}, "Delete?"), 531 CGI::td({-align=>"left"}, CGI::input({type=>"checkbox", 532 name=>"deleteProblem", value=>$problemID}))). 533 CGI::Tr({}, CGI::td({-align=>"right"}, 'Max Attempts:'), 534 CGI::td({-align=>"left"}, CGI::input({type=>"text", 535 name=>"problem.${problemID}.max_attempts", 536 value=> 537 (($problemRecord->max_attempts<0)? "unlim": $problemRecord->max_attempts), size=>"4"}))). 538 CGI::Tr({}, CGI::td({-align=>"right"}, 'Weight:'), 539 CGI::td({-align=>"left"}, CGI::input({type=>"text", 540 name=>"problem.${problemID}.value", 541 value=>$problemRecord->value, size=>"4"}))). 542 CGI::end_table(), 543 544 problemElementHTML("problem.${problemID}.source_file", 545 $problemRecord->source_file, "50", 546 @{$problemOverrideArgs{source_file}}) . 547 548 CGI::br(). 549 CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}) 550 . ($have_this_one ? CGI::div({class=>"ResultsWithError", 551 style=>"font-weight: bold"}, $have_this_one) : '') , 552 ]) # end of table entry 553 ) # end of table row 554 } # end of loop over problems 555 print CGI::end_table(); 556 print $self->hiddenEditForUserFields(@editForUser); 557 print $self->hidden_authen_fields; 558 print CGI::checkbox({ 559 label=> "Force problems to be numbered consecutively from one", 560 name=>"force_renumber", value=>"1"}), 561 562 CGI::br(); 563 print CGI::input({type=>"submit", name=>"submit_problem_changes", value=>"Save Problem Changes"}); 564 print CGI::p(<<HERE); 565 Any time problem numbers are intentionally changed, the problems will 566 always be renumbered consecutively, starting from one. When deleting 567 problems, gaps will be left in the numbering unless the box above is 568 checked. 569 HERE 570 print CGI::p("It is before the open date. You probably want to renumber the problems if you are deleting some from the middle.") if ($setRecord->open_date>time()); 571 print CGI::p("When changing problem numbers, we will move 572 the problem to be ", CGI::em("before"), " the chosen number."); 573 574 print CGI::end_form(); 575 576 } 577 578 } else { 579 print CGI::p("This set doesn't contain any problems yet."); 580 } 581 582 return ""; 583 } 584 585 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |