Parent Directory
|
Revision Log
Gateway bugfixes/feature additions - added test time to student progress display - corrected bugs from overtime proctored tests - corrected behavior for closed tests - added restrictions to prevent gateways from being taken as regular assignments - updated problem set lists to better deal with gateways
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.143.2.4 2004/07/30 23:00:52 sh002i 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::Problem; 18 use base qw(WeBWorK::ContentGenerator); 19 20 =head1 NAME 21 22 WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. 23 24 =cut 25 26 use strict; 27 use warnings; 28 use CGI qw(); 29 use File::Path qw(rmtree); 30 use WeBWorK::Form; 31 use WeBWorK::PG; 32 use WeBWorK::PG::ImageGenerator; 33 use WeBWorK::PG::IO; 34 use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory); 35 use WeBWorK::DB::Utils qw(global2user user2global findDefaults); 36 use WeBWorK::Timing; 37 38 use WeBWorK::Utils::Tasks qw(fake_set fake_problem); 39 40 ################################################################################ 41 # CGI param interface to this module (up-to-date as of v1.153) 42 ################################################################################ 43 44 # Standard params: 45 # 46 # user - user ID of real user 47 # key - session key 48 # effectiveUser - user ID of effective user 49 # 50 # Integration with PGProblemEditor: 51 # 52 # editMode - if set, indicates alternate problem source location. 53 # can be "temporaryFile" or "savedFile". 54 # 55 # sourceFilePath - path to file to be edited 56 # problemSeed - force problem seed to value 57 # success - success message to display 58 # failure - failure message to display 59 # 60 # Rendering options: 61 # 62 # displayMode - name of display mode to use 63 # 64 # showOldAnswers - request that last entered answer be shown (if allowed) 65 # showCorrectAnswers - request that correct answers be shown (if allowed) 66 # showHints - request that hints be shown (if allowed) 67 # showSolutions - request that solutions be shown (if allowed) 68 # 69 # Problem interaction: 70 # 71 # AnSwEr# - answer blanks in problem 72 # 73 # redisplay - name of the "Redisplay Problem" button 74 # submitAnswers - name of "Submit Answers" button 75 # checkAnswers - name of the "Check Answers" button 76 # previewAnswers - name of the "Preview Answers" button 77 78 ################################################################################ 79 # "can" methods 80 ################################################################################ 81 82 # Subroutines to determine if a user "can" perform an action. Each subroutine is 83 # called with the following arguments: 84 # 85 # ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) 86 87 # *** GatewayQuiz note: 88 # *** The "can" routines are excerpted with few changes to GatewayQuiz.pm 89 90 sub can_showOldAnswers { 91 #my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 92 93 return 1; 94 } 95 96 sub can_showCorrectAnswers { 97 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 98 my $authz = $self->r->authz; 99 100 return 101 after($Set->answer_date) 102 || 103 $authz->hasPermissions($User->user_id, "show_correct_answers_before_answer_date") 104 ; 105 } 106 107 sub can_showHints { 108 #my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 109 110 return 1; 111 } 112 113 sub can_showSolutions { 114 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 115 my $authz = $self->r->authz; 116 117 return 118 after($Set->answer_date) 119 || 120 $authz->hasPermissions($User->user_id, "show_solutions_before_answer_date") 121 ; 122 } 123 124 sub can_recordAnswers { 125 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 126 my $authz = $self->r->authz; 127 if ($User->user_id ne $EffectiveUser->user_id) { 128 return $authz->hasPermissions($User->user_id, "record_answers_when_acting_as_student"); 129 } 130 if (before($Set->open_date)) { 131 return $authz->hasPermissions($User->user_id, "record_answers_before_open_date"); 132 } elsif (between($Set->open_date, $Set->due_date)) { 133 my $max_attempts = $Problem->max_attempts; 134 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + 1; 135 if ($max_attempts == -1 or $attempts_used < $max_attempts) { 136 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_with_attempts"); 137 } else { 138 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_without_attempts"); 139 } 140 } elsif (between($Set->due_date, $Set->answer_date)) { 141 return $authz->hasPermissions($User->user_id, "record_answers_after_due_date"); 142 } elsif (after($Set->answer_date)) { 143 return $authz->hasPermissions($User->user_id, "record_answers_after_answer_date"); 144 } 145 } 146 147 sub can_checkAnswers { 148 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_; 149 my $authz = $self->r->authz; 150 151 if (before($Set->open_date)) { 152 return $authz->hasPermissions($User->user_id, "check_answers_before_open_date"); 153 } elsif (between($Set->open_date, $Set->due_date)) { 154 my $max_attempts = $Problem->max_attempts; 155 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + 1; 156 if ($max_attempts == -1 or $attempts_used < $max_attempts) { 157 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_with_attempts"); 158 } else { 159 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_without_attempts"); 160 } 161 } elsif (between($Set->due_date, $Set->answer_date)) { 162 return $authz->hasPermissions($User->user_id, "check_answers_after_due_date"); 163 } elsif (after($Set->answer_date)) { 164 return $authz->hasPermissions($User->user_id, "check_answers_after_answer_date"); 165 } 166 } 167 168 # Helper functions for calculating times 169 sub before { return time <= $_[0] } 170 sub after { return time >= $_[0] } 171 sub between { my $t = time; return $t > $_[0] && $t < $_[1] } 172 173 # *** GatewayQuiz note: 174 # *** output utilities are transcribed verbatim to GatewayQuiz.pm, though 175 # *** we may change the summary message in attemptResults at some point 176 177 ################################################################################ 178 # output utilities 179 ################################################################################ 180 181 sub attemptResults { 182 my $self = shift; 183 my $pg = shift; 184 my $showAttemptAnswers = shift; 185 my $showCorrectAnswers = shift; 186 my $showAttemptResults = $showAttemptAnswers && shift; 187 my $showSummary = shift; 188 my $showAttemptPreview = shift || 0; 189 190 my $ce = $self->r->ce; 191 192 my $problemResult = $pg->{result}; # the overall result of the problem 193 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }; 194 195 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; 196 197 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview"; 198 199 # to make grabbing these options easier, we'll pull them out now... 200 my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}}; 201 202 my $imgGen = WeBWorK::PG::ImageGenerator->new( 203 tempDir => $ce->{webworkDirs}->{tmp}, 204 latex => $ce->{externalPrograms}->{latex}, 205 dvipng => $ce->{externalPrograms}->{dvipng}, 206 useCache => 1, 207 cacheDir => $ce->{webworkDirs}->{equationCache}, 208 cacheURL => $ce->{webworkURLs}->{equationCache}, 209 cacheDB => $ce->{webworkFiles}->{equationCacheDB}, 210 dvipng_align => $imagesModeOptions{dvipng_align}, 211 dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, 212 ); 213 214 my $header; 215 #$header .= CGI::th("Part"); 216 $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; 217 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : ""; 218 $header .= $showCorrectAnswers ? CGI::th("Correct") : ""; 219 $header .= $showAttemptResults ? CGI::th("Result") : ""; 220 $header .= $showMessages ? CGI::th("Messages") : ""; 221 my @tableRows = ( $header ); 222 my $numCorrect = 0; 223 foreach my $name (@answerNames) { 224 my $answerResult = $pg->{answers}->{$name}; 225 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans 226 my $preview = ($showAttemptPreview 227 ? $self->previewAnswer($answerResult, $imgGen) 228 : ""); 229 my $correctAnswer = $answerResult->{correct_ans}; 230 my $answerScore = $answerResult->{score}; 231 my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; 232 #FIXME --Can we be sure that $answerScore is an integer-- could the problem give partial credit? 233 $numCorrect += $answerScore > 0; 234 my $resultString = $answerScore == 1 ? "correct" : "incorrect"; 235 236 # get rid of the goofy prefix on the answer names (supposedly, the format 237 # of the answer names is changeable. this only fixes it for "AnSwEr" 238 #$name =~ s/^AnSwEr//; 239 240 my $row; 241 #$row .= CGI::td($name); 242 $row .= $showAttemptAnswers ? CGI::td($self->nbsp($studentAnswer)) : ""; 243 $row .= $showAttemptPreview ? CGI::td($self->nbsp($preview)) : ""; 244 $row .= $showCorrectAnswers ? CGI::td($self->nbsp($correctAnswer)) : ""; 245 $row .= $showAttemptResults ? CGI::td($self->nbsp($resultString)) : ""; 246 $row .= $showMessages ? CGI::td($self->nbsp($answerMessage)) : ""; 247 push @tableRows, $row; 248 } 249 250 # render equation images 251 $imgGen->render(refresh => 1); 252 253 # my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; 254 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); 255 # FIXME -- I left the old code in in case we have to back out. 256 # my $summary = "On this attempt, you answered $numCorrect out of " 257 # . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; 258 my $summary = ""; 259 if (scalar @answerNames == 1) { 260 if ($numCorrect == scalar @answerNames) { 261 $summary .= CGI::div({class=>"ResultsWithoutError"},"The above answer is correct."); 262 } else { 263 $summary .= CGI::div({class=>"ResultsWithError"},"The above answer is NOT correct."); 264 } 265 } else { 266 if ($numCorrect == scalar @answerNames) { 267 $summary .= CGI::div({class=>"ResultsWithoutError"},"All of the above answers are correct."); 268 } else { 269 $summary .= CGI::div({class=>"ResultsWithError"},"At least one of the above answers is NOT correct."); 270 } 271 } 272 273 return 274 CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) 275 . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : ""); 276 } 277 278 sub viewOptions { 279 my ($self) = @_; 280 my $ce = $self->r->ce; 281 282 # don't show options if we don't have anything to show 283 return if $self->{invalidSet} or $self->{invalidProblem}; 284 return unless $self->{isOpen}; 285 286 my $displayMode = $self->{displayMode}; 287 my %must = %{ $self->{must} }; 288 my %can = %{ $self->{can} }; 289 my %will = %{ $self->{will} }; 290 291 my $optionLine; 292 $can{showOldAnswers} and $optionLine .= join "", 293 "Show: ".CGI::br(), 294 CGI::checkbox( 295 -name => "showOldAnswers", 296 -checked => $will{showOldAnswers}, 297 -label => "Saved answers", 298 ), " ".CGI::br(); 299 300 $optionLine and $optionLine .= join "", CGI::br(); 301 302 my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()}; 303 my @active_modes = grep { exists $display_modes{$_} } 304 @{$ce->{pg}->{displayModes}}; 305 my $modeLine = (scalar(@active_modes) > 1) ? 306 "View equations as: ".CGI::br(). 307 CGI::radio_group( 308 -name => "displayMode", 309 -values => \@active_modes, 310 -default => $displayMode, 311 -linebreak=>'true', 312 -labels => { 313 plainText => "plain", 314 formattedText => "formatted", 315 images => "images", 316 jsMath => "jsMath", 317 asciimath => "asciimath", 318 }, 319 ). CGI::br().CGI::hr() : ''; 320 321 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"}, 322 $modeLine, 323 $optionLine, 324 CGI::submit(-name=>"redisplay", -label=>"Apply Options"), 325 ); 326 } 327 328 sub previewAnswer { 329 my ($self, $answerResult, $imgGen) = @_; 330 my $ce = $self->r->ce; 331 my $effectiveUser = $self->{effectiveUser}; 332 my $set = $self->{set}; 333 my $problem = $self->{problem}; 334 my $displayMode = $self->{displayMode}; 335 336 # note: right now, we have to do things completely differently when we are 337 # rendering math from INSIDE the translator and from OUTSIDE the translator. 338 # so we'll just deal with each case explicitly here. there's some code 339 # duplication that can be dealt with later by abstracting out tth/dvipng/etc. 340 341 my $tex = $answerResult->{preview_latex_string}; 342 343 return "" unless defined $tex and $tex ne ""; 344 345 if ($displayMode eq "plainText") { 346 return $tex; 347 } elsif ($displayMode eq "formattedText") { 348 my $tthCommand = $ce->{externalPrograms}->{tth} 349 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n" 350 . "\\(".$tex."\\)\n" 351 . "END_OF_INPUT\n"; 352 353 # call tth 354 my $result = `$tthCommand`; 355 if ($?) { 356 return "<b>[tth failed: $? $@]</b>"; 357 } else { 358 return $result; 359 } 360 } elsif ($displayMode eq "images") { 361 $imgGen->add($tex); 362 } elsif ($displayMode eq "jsMath") { 363 return '<DIV CLASS="math">'.$tex.'</DIV>' ; 364 } 365 } 366 367 ################################################################################ 368 # Template escape implementations 369 ################################################################################ 370 371 sub pre_header_initialize { 372 my ($self) = @_; 373 my $r = $self->r; 374 my $ce = $r->ce; 375 my $db = $r->db; 376 my $authz = $r->authz; 377 my $urlpath = $r->urlpath; 378 379 my $setName = $urlpath->arg("setID"); 380 my $problemNumber = $r->urlpath->arg("problemID"); 381 my $userName = $r->param('user'); 382 my $effectiveUserName = $r->param('effectiveUser'); 383 my $key = $r->param('key'); 384 385 my $user = $db->getUser($userName); # checked 386 die "record for user $userName (real user) does not exist." 387 unless defined $user; 388 389 my $effectiveUser = $db->getUser($effectiveUserName); # checked 390 die "record for user $effectiveUserName (effective user) does not exist." 391 unless defined $effectiveUser; 392 393 my $PermissionLevel = $db->getPermissionLevel($userName); # checked 394 die "permission level record for user $userName does not exist (but the user does? odd...)" 395 unless defined $PermissionLevel; 396 my $permissionLevel = $PermissionLevel->permission; 397 398 # obtain the merged set for $effectiveUser 399 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked 400 401 # gateway check here: we want to be sure that someone isn't trying to take 402 # a GatewayQuiz through the regular problem/homework mechanism, thereby 403 # circumventing the versioning, time limits, etc. 404 $self->{invalidSet} = 'The "Problem" ContentGenerator was called for ' . 405 'a GatewayQuiz' if ( defined( $set->assignment_type() ) && 406 $set->assignment_type() =~ /gateway/ ); 407 408 # obtain the merged problem for $effectiveUser 409 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked 410 411 # this shouldn't happen, but we're happy to have a check to preent people 412 # from gaming the system 413 die "Set $setName is a gateway test. Error in ContentGenerator call." 414 if ( defined($set) && defined( $set->assignment_type ) && 415 $set->assignment_type =~ /gateway/ ); 416 417 my $editMode = $r->param("editMode"); 418 419 if ($authz->hasPermissions($userName, "modify_problem_sets")) { 420 # professors are allowed to fabricate sets and problems not 421 # assigned to them (or anyone). this allows them to use the 422 # editor to 423 424 # if that is not yet defined obtain the global set, convert 425 # it to a user set, and add fake user data 426 unless (defined $set) { 427 my $userSetClass = $db->{set_user}->{record}; 428 my $globalSet = $db->getGlobalSet($setName); # checked 429 # if the global set doesn't exist either, bail! 430 if(not defined $globalSet) { 431 $set = fake_set($db); 432 } else { 433 $set = global2user($userSetClass, $globalSet); 434 $set->psvn(0); 435 436 # FIXME: This is a temporary fix to fill in the database 437 # We want the published field to contain either 1 or 0 so if it has not been set to 0, default to 1 438 # this will fill in all the empty fields but not change anything that has been specifically set to 1 or 0 439 $globalSet->published("1") unless $globalSet->published eq "0"; 440 $db->putGlobalSet($globalSet); 441 } 442 } 443 444 # if that is not yet defined obtain the global problem, 445 # convert it to a user problem, and add fake user data 446 unless (defined $problem) { 447 my $userProblemClass = $db->{problem_user}->{record}; 448 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked 449 # if the global problem doesn't exist either, bail! 450 if(not defined $globalProblem) { 451 my $sourceFilePath = $r->param("sourceFilePath"); 452 # These are problems from setmaker. If declared invalid, they won't come up 453 $self->{invalidProblem} = $self->{invalidSet} = 1 unless defined $sourceFilePath; 454 # die "Problem $problemNumber in set $setName does not exist" unless defined $sourceFilePath; 455 $problem = fake_problem($db); 456 $problem->problem_id(1); 457 $problem->source_file($sourceFilePath); 458 $problem->user_id($effectiveUserName); 459 } else { 460 $problem = global2user($userProblemClass, $globalProblem); 461 $problem->user_id($effectiveUserName); 462 $problem->problem_seed(0); 463 $problem->status(0); 464 $problem->attempted(0); 465 $problem->last_answer(""); 466 $problem->num_correct(0); 467 $problem->num_incorrect(0); 468 } 469 } 470 471 # now we're sure we have valid UserSet and UserProblem objects 472 # yay! 473 474 # now deal with possible editor overrides: 475 476 # if the caller is asking to override the source file, and 477 # editMode calls for a temporary file, do so 478 my $sourceFilePath = $r->param("sourceFilePath"); 479 if (defined $sourceFilePath and 480 (not defined $editMode or $editMode eq "temporaryFile")) { 481 $problem->source_file($sourceFilePath); 482 } 483 484 # if the problem does not have a source file or no source file has been passed in 485 # then this is really an invalid problem (probably from a bad URL) 486 $self->{invalidProblem} = not (defined $sourceFilePath or $problem->source_file); 487 488 # if the caller is asking to override the problem seed, do so 489 my $problemSeed = $r->param("problemSeed"); 490 if (defined $problemSeed) { 491 $problem->problem_seed($problemSeed); 492 } 493 494 my $publishedClass = ($set->published) ? "Published" : "Unpublished"; 495 my $publishedText = ($set->published) ? "visible to students." : "hidden from students."; 496 $self->addmessage(CGI::p("This set is " . CGI::font({class=>$publishedClass}, $publishedText))); 497 } else { 498 499 # students can't view problems not assigned to them 500 501 # A set is valid if it exists and if it is either published or the user is privileged. 502 $self->{invalidSet} = ((grep /^$setName/, $db->listUserSets($effectiveUserName)) == 0) 503 || not defined $set 504 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")); 505 $self->{invalidProblem} = ((grep /^$problemNumber/, $db->listUserProblems($effectiveUserName, $setName)) == 0) 506 || not defined $problem 507 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")); 508 509 $self->addbadmessage(CGI::p("This problem will not count towards your grade.")) if $problem and not $problem->value and not $self->{invalidProblem}; 510 } 511 512 $self->{userName} = $userName; 513 $self->{effectiveUserName} = $effectiveUserName; 514 $self->{user} = $user; 515 $self->{effectiveUser} = $effectiveUser; 516 $self->{permissionLevel} = $permissionLevel; 517 $self->{set} = $set; 518 $self->{problem} = $problem; 519 $self->{editMode} = $editMode; 520 521 ##### form processing ##### 522 523 # set options from form fields (see comment at top of file for names) 524 my $displayMode = $r->param("displayMode") || $ce->{pg}->{options}->{displayMode}; 525 my $redisplay = $r->param("redisplay"); 526 my $submitAnswers = $r->param("submitAnswers"); 527 my $checkAnswers = $r->param("checkAnswers"); 528 my $previewAnswers = $r->param("previewAnswers"); 529 530 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; 531 532 $self->{displayMode} = $displayMode; 533 $self->{redisplay} = $redisplay; 534 $self->{submitAnswers} = $submitAnswers; 535 $self->{checkAnswers} = $checkAnswers; 536 $self->{previewAnswers} = $previewAnswers; 537 $self->{formFields} = $formFields; 538 539 # get result and send to message 540 my $success = $r->param("sucess"); 541 my $failure = $r->param("failure"); 542 $self->addbadmessage(CGI::p($failure)) if $failure; 543 $self->addgoodmessage(CGI::p($success)) if $success; 544 545 # now that we've set all the necessary variables quit out if the set or problem is invalid 546 return if $self->{invalidSet} || $self->{invalidProblem}; 547 548 ##### permissions ##### 549 550 # are we allowed to view this problem? 551 $self->{isOpen} = after($set->open_date) || $authz->hasPermissions($userName, "view_unopened_sets"); 552 return unless $self->{isOpen}; 553 554 # what does the user want to do? 555 my %want = ( 556 showOldAnswers => $r->param("showOldAnswers") || $ce->{pg}->{options}->{showOldAnswers}, 557 showCorrectAnswers => $r->param("showCorrectAnswers") || $ce->{pg}->{options}->{showCorrectAnswers}, 558 showHints => $r->param("showHints") || $ce->{pg}->{options}->{showHints}, 559 showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}->{showSolutions}, 560 recordAnswers => $submitAnswers, 561 checkAnswers => $checkAnswers, 562 ); 563 564 # are certain options enforced? 565 my %must = ( 566 showOldAnswers => 0, 567 showCorrectAnswers => 0, 568 showHints => 0, 569 showSolutions => 0, 570 recordAnswers => ! $authz->hasPermissions($userName, "avoid_recording_answers"), 571 checkAnswers => 0, 572 ); 573 574 # does the user have permission to use certain options? 575 my @args = ($user, $PermissionLevel, $effectiveUser, $set, $problem); 576 my %can = ( 577 showOldAnswers => $self->can_showOldAnswers(@args), 578 showCorrectAnswers => $self->can_showCorrectAnswers(@args), 579 showHints => $self->can_showHints(@args), 580 showSolutions => $self->can_showSolutions(@args), 581 recordAnswers => $self->can_recordAnswers(@args), 582 checkAnswers => $self->can_checkAnswers(@args), 583 ); 584 585 # # does the user have permission to use certain options? 586 # my %can = ( 587 # showOldAnswers => 1, 588 # showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date), 589 # showHints => 1, 590 # showSolutions => canShowSolutions($permissionLevel, $set->answer_date), 591 # recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date, 592 # $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1), 593 # # attempts=num_correct+num_incorrect+1, as this happens before updating $problem 594 # checkAnswers => canCheckAnswers($permissionLevel, $set->due_date), 595 # ); 596 # 597 # # more complicated logic for showing check answer button: 598 # # checkAnswers button shows up after due date -- once a student can't record anymore 599 # # checkAnswers button always shows up when an instructor or TA is acting 600 # # as someone else (the $user and $effectiveUserName aren't the same). 601 # $can{checkAnswers} = ( 602 # # $can{recordAnswers} will be false if the due date has passed OR the 603 # # student has used up all of her attempts 604 # ($can{checkAnswers} and not $can{recordAnswers}) 605 # or 606 # ( 607 # # FIXME: this is not the right way to check for this. 608 # # also, canCheckAnswers() will show this button if the permission 609 # # level is positive, which is always true when an instructor is 610 # # acting as a student 611 # defined($userName) 612 # and 613 # defined($effectiveUserName) 614 # and 615 # ($userName ne $effectiveUserName) 616 # ) 617 # ); 618 # 619 # # more complicated logic for showing "submit answer" button: 620 # # We hide the submit answer button if someone is acting as a student 621 # # This prevents errors where you accidently submit the answer for a student 622 # # Not sure whether this a feature or a bug 623 # $can{recordAnswers} = ( 624 # $can{recordAnswers} 625 # and not 626 # ( 627 # # FIXME: this is not the right way to check for this. 628 # defined($userName) 629 # and 630 # defined($effectiveUserName) 631 # and 632 # ($userName ne $effectiveUserName) 633 # ) 634 # ); 635 636 # final values for options 637 my %will; 638 foreach (keys %must) { 639 $will{$_} = $can{$_} && ($want{$_} || $must{$_}); 640 } 641 642 ##### sticky answers ##### 643 644 if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) { 645 # do this only if new answers are NOT being submitted 646 my %oldAnswers = decodeAnswers($problem->last_answer); 647 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; 648 } 649 650 ##### translation ##### 651 652 $WeBWorK::timer->continue("begin pg processing") if defined($WeBWorK::timer); 653 my $pg = WeBWorK::PG->new( 654 $ce, 655 $effectiveUser, 656 $key, 657 $set, 658 $problem, 659 $set->psvn, # FIXME: this field should be removed 660 $formFields, 661 { # translation options 662 displayMode => $displayMode, 663 showHints => $will{showHints}, 664 showSolutions => $will{showSolutions}, 665 refreshMath2img => $will{showHints} || $will{showSolutions}, 666 processAnswers => 1, 667 }, 668 ); 669 670 $WeBWorK::timer->continue("end pg processing") if defined($WeBWorK::timer); 671 672 ##### fix hint/solution options ##### 673 674 $can{showHints} &&= $pg->{flags}->{hintExists} 675 &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans}; 676 $can{showSolutions} &&= $pg->{flags}->{solutionExists}; 677 678 ##### store fields ##### 679 680 $self->{want} = \%want; 681 $self->{must} = \%must; 682 $self->{can} = \%can; 683 $self->{will} = \%will; 684 $self->{pg} = $pg; 685 } 686 687 sub if_errors($$) { 688 my ($self, $arg) = @_; 689 690 if ($self->{isOpen}) { 691 return $self->{pg}->{flags}->{error_flag} ? $arg : !$arg; 692 } else { 693 return !$arg; 694 } 695 } 696 697 sub head { 698 my ($self) = @_; 699 700 return "" unless $self->{isOpen}; 701 return $self->{pg}->{head_text} if $self->{pg}->{head_text}; 702 } 703 704 sub options { 705 my ($self) = @_; 706 707 return "" if $self->{invalidProblem}; 708 my $sourceFilePathfield = ''; 709 if($self->r->param("sourceFilePath")) { 710 $sourceFilePathfield = CGI::hidden(-name => "sourceFilePath", 711 -value => $self->r->param("sourceFilePath")); 712 } 713 714 return join("", 715 CGI::start_form("POST", $self->{r}->uri), 716 $self->hidden_authen_fields, 717 $sourceFilePathfield, 718 CGI::hr(), 719 CGI::start_div({class=>"viewOptions"}), 720 $self->viewOptions(), 721 CGI::end_div(), 722 CGI::end_form() 723 ); 724 } 725 726 sub siblings { 727 my ($self) = @_; 728 my $r = $self->r; 729 my $db = $r->db; 730 my $urlpath = $r->urlpath; 731 732 # can't show sibling problems if the set is invalid 733 return "" if $self->{invalidSet}; 734 735 my $courseID = $urlpath->arg("courseID"); 736 my $setID = $self->{set}->set_id; 737 my $eUserID = $r->param("effectiveUser"); 738 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID); 739 740 print CGI::start_ul({class=>"LinksMenu"}); 741 print CGI::start_li(); 742 print CGI::span({style=>"font-size:larger"}, "Problems"); 743 print CGI::start_ul(); 744 745 foreach my $problemID (@problemIDs) { 746 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 747 courseID => $courseID, setID => $setID, problemID => $problemID); 748 print CGI::li(CGI::a({href=>$self->systemLink($problemPage, params=>{displayMode => $self->{displayMode}})}, "Problem $problemID")); 749 } 750 751 print CGI::end_ul(); 752 print CGI::end_li(); 753 print CGI::end_ul(); 754 755 return ""; 756 } 757 758 sub nav { 759 my ($self, $args) = @_; 760 my $r = $self->r; 761 my $db = $r->db; 762 my $urlpath = $r->urlpath; 763 764 my $courseID = $urlpath->arg("courseID"); 765 my $setID = $self->{set}->set_id if !($self->{invalidSet}); 766 my $problemID = $self->{problem}->problem_id if !($self->{invalidProblem}); 767 my $eUserID = $r->param("effectiveUser"); 768 769 my ($prevID, $nextID); 770 771 if (!$self->{invalidProblem}) { 772 my @problemIDs = $db->listUserProblems($eUserID, $setID); 773 foreach my $id (@problemIDs) { 774 $prevID = $id if $id < $problemID 775 and (not defined $prevID or $id > $prevID); 776 $nextID = $id if $id > $problemID 777 and (not defined $nextID or $id < $nextID); 778 } 779 } 780 781 my @links; 782 783 if ($prevID) { 784 my $prevPage = $urlpath->newFromModule(__PACKAGE__, 785 courseID => $courseID, setID => $setID, problemID => $prevID); 786 push @links, "Previous Problem", $r->location . $prevPage->path, "navPrev"; 787 } else { 788 push @links, "Previous Problem", "", "navPrev"; 789 } 790 791 push @links, "Problem List", $r->location . $urlpath->parent->path, "navProbList"; 792 793 if ($nextID) { 794 my $nextPage = $urlpath->newFromModule(__PACKAGE__, 795 courseID => $courseID, setID => $setID, problemID => $nextID); 796 push @links, "Next Problem", $r->location . $nextPage->path, "navNext"; 797 } else { 798 push @links, "Next Problem", "", "navNext"; 799 } 800 801 my $tail = "&displayMode=".$self->{displayMode}; 802 return $self->navMacro($args, $tail, @links); 803 } 804 805 sub title { 806 my ($self) = @_; 807 808 # using the url arguments won't break if the set/problem are invalid 809 my $setID = $self->r->urlpath->arg("setID"); 810 my $problemID = $self->r->urlpath->arg("problemID"); 811 812 return "$setID : $problemID"; 813 } 814 815 sub body { 816 my $self = shift; 817 my $r = $self->r; 818 my $ce = $r->ce; 819 my $db = $r->db; 820 my $authz = $r->authz; 821 my $urlpath = $r->urlpath; 822 my $user = $r->param('user'); 823 my $effectiveUser = $r->param('effectiveUser'); 824 825 if ($self->{invalidSet}) { 826 return CGI::div({class=>"ResultsWithError"}, 827 CGI::p("The selected problem set (" . $urlpath->arg("setID") . ") is not a valid set for " . $r->param("effectiveUser") . ".")); 828 } 829 830 if ($self->{invalidProblem}) { 831 return CGI::div({class=>"ResultsWithError"}, 832 CGI::p("The selected problem (" . $urlpath->arg("problemID") . ") is not a valid problem for set " . $self->{set}->set_id . ".")); 833 } 834 835 unless ($self->{isOpen}) { 836 return CGI::div({class=>"ResultsWithError"}, 837 CGI::p("This problem is not available because the problem set that contains it is not yet open.")); 838 } 839 # unpack some useful variables 840 my $set = $self->{set}; 841 my $problem = $self->{problem}; 842 my $editMode = $self->{editMode}; 843 my $permissionLevel = $self->{permissionLevel}; 844 my $submitAnswers = $self->{submitAnswers}; 845 my $checkAnswers = $self->{checkAnswers}; 846 my $previewAnswers = $self->{previewAnswers}; 847 my %want = %{ $self->{want} }; 848 my %can = %{ $self->{can} }; 849 my %must = %{ $self->{must} }; 850 my %will = %{ $self->{will} }; 851 my $pg = $self->{pg}; 852 853 my $courseName = $urlpath->arg("courseID"); 854 855 # FIXME: move editor link to top, next to problem number. 856 # format as "[edit]" like we're doing with course info file, etc. 857 # add edit link for set as well. 858 my $editorLink = ""; 859 # if we are here without a real problem set, carry that through 860 my $forced_field = []; 861 $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if 862 ($set->set_id eq 'Undefined_Set'); 863 if ($authz->hasPermissions($user, "modify_problem_sets")) { 864 my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 865 courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); 866 my $editorURL = $self->systemLink($editorPage, params=>$forced_field); 867 $editorLink = CGI::a({href=>$editorURL}, "Edit this problem"); 868 } 869 870 ##### translation errors? ##### 871 872 if ($pg->{flags}->{error_flag}) { 873 print $self->errorOutput($pg->{errors}, $pg->{body_text}); 874 print $editorLink; 875 return ""; 876 } 877 878 ##### answer processing ##### 879 $WeBWorK::timer->continue("begin answer processing") if defined($WeBWorK::timer); 880 881 # *** GatewayQuiz note: 882 # *** this conditional through to the output section of this subroutine is 883 # *** duplicated almost verbatim in GatewayQuiz.pm, after wrapping it in a loop. 884 885 # if answers were submitted: 886 my $scoreRecordedMessage; 887 my $pureProblem; 888 if ($submitAnswers) { 889 # get a "pure" (unmerged) UserProblem to modify 890 # this will be undefined if the problem has not been assigned to this user 891 $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); # checked 892 if (defined $pureProblem) { 893 # store answers in DB for sticky answers 894 my %answersToStore; 895 my %answerHash = %{ $pg->{answers} }; 896 $answersToStore{$_} = $self->{formFields}->{$_} #$answerHash{$_}->{original_student_ans} -- this may have been modified for fields with multiple values. Don't use it!! 897 foreach (keys %answerHash); 898 899 # There may be some more answers to store -- one which are auxiliary entries to a primary answer. Evaluating 900 # matrices works in this way, only the first answer triggers an answer evaluator, the rest are just inputs 901 # however we need to store them. Fortunately they are still in the input form. 902 my @extra_answer_names = @{ $pg->{flags}->{KEPT_EXTRA_ANSWERS}}; 903 $answersToStore{$_} = $self->{formFields}->{$_} foreach (@extra_answer_names); 904 905 # Now let's encode these answers to store them -- append the extra answers to the end of answer entry order 906 my @answer_order = (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}, @extra_answer_names); 907 my $answerString = encodeAnswers(%answersToStore, 908 @answer_order); 909 910 # store last answer to database 911 $problem->last_answer($answerString); 912 $pureProblem->last_answer($answerString); 913 $db->putUserProblem($pureProblem); 914 915 # store state in DB if it makes sense 916 if ($will{recordAnswers}) { 917 $problem->status($pg->{state}->{recorded_score}); 918 $problem->attempted(1); 919 $problem->num_correct($pg->{state}->{num_of_correct_ans}); 920 $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans}); 921 $pureProblem->status($pg->{state}->{recorded_score}); 922 $pureProblem->attempted(1); 923 $pureProblem->num_correct($pg->{state}->{num_of_correct_ans}); 924 $pureProblem->num_incorrect($pg->{state}->{num_of_incorrect_ans}); 925 if ($db->putUserProblem($pureProblem)) { 926 $scoreRecordedMessage = "Your score was recorded."; 927 } else { 928 $scoreRecordedMessage = "Your score was not recorded because there was a failure in storing the problem record to the database."; 929 } 930 # write to the transaction log, just to make sure 931 writeLog($self->{ce}, "transaction", 932 $problem->problem_id."\t". 933 $problem->set_id."\t". 934 $problem->user_id."\t". 935 $problem->source_file."\t". 936 $problem->value."\t". 937 $problem->max_attempts."\t". 938 $problem->problem_seed."\t". 939 $pureProblem->status."\t". 940 $pureProblem->attempted."\t". 941 $pureProblem->last_answer."\t". 942 $pureProblem->num_correct."\t". 943 $pureProblem->num_incorrect 944 ); 945 } else { 946 if (before($set->open_date) or after($set->due_date)) { 947 $scoreRecordedMessage = "Your score was not recorded because this problem set is closed."; 948 } else { 949 $scoreRecordedMessage = "Your score was not recorded."; 950 } 951 } 952 } else { 953 $scoreRecordedMessage = "Your score was not recorded because this problem has not been assigned to you."; 954 } 955 } 956 957 # logging student answers 958 959 my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'}; 960 if ( defined($answer_log ) and defined($pureProblem)) { 961 if ($submitAnswers ) { 962 my $answerString = ""; 963 my %answerHash = %{ $pg->{answers} }; 964 # FIXME this is the line 552 error. make sure original student ans is defined. 965 # The fact that it is not defined is probably due to an error in some answer evaluator. 966 # But I think it is useful to suppress this error message in the log. 967 foreach (sort keys %answerHash) { 968 my $student_ans = $answerHash{$_}->{original_student_ans} ||''; 969 $answerString .= $student_ans."\t" 970 } 971 $answerString = '' unless defined($answerString); # insure string is defined. 972 writeCourseLog($self->{ce}, "answer_log", 973 join("", 974 '|', $problem->user_id, 975 '|', $problem->set_id, 976 '|', $problem->problem_id, 977 '|',"\t", 978 time(),"\t", 979 $answerString, 980 ), 981 ); 982 983 } 984 } 985 986 $WeBWorK::timer->continue("end answer processing") if defined($WeBWorK::timer); 987 988 ##### output ##### 989 990 print CGI::start_div({class=>"problemHeader"}); 991 992 # custom message for editor 993 if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) { 994 if ($editMode eq "temporaryFile") { 995 print CGI::p(CGI::i("Editing temporary file: ", $problem->source_file)); 996 } elsif ($editMode eq "savedFile") { 997 # taken care of in the initialization phase 998 } 999 } 1000 1001 # attempt summary 1002 #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. 1003 # until after the due date 1004 # do I need to check $will{showCorrectAnswers} to make preflight work?? 1005 if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) { 1006 # print this if user submitted answers OR requested correct answers 1007 1008 print $self->attemptResults($pg, 1, 1009 $will{showCorrectAnswers}, 1010 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); 1011 } elsif ($checkAnswers) { 1012 # print this if user previewed answers 1013 print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ",CGI::br(),"ANSWERS NOT RECORDED", CGI::br() ); 1014 print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1); 1015 # show attempt answers 1016 # show correct answers if asked 1017 # show attempt results (correctness) 1018 # show attempt previews 1019 } elsif ($previewAnswers) { 1020 # print this if user previewed answers 1021 print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1); 1022 # show attempt answers 1023 # don't show correct answers 1024 # don't show attempt results (correctness) 1025 # show attempt previews 1026 } 1027 1028 print CGI::end_div(); 1029 1030 # main form 1031 print CGI::startform("POST", $r->uri); 1032 print $self->hidden_authen_fields; 1033 1034 print CGI::start_div({class=>"problem"}); 1035 print CGI::p($pg->{body_text}); 1036 print CGI::p(CGI::b("Note: "), CGI::i($pg->{result}->{msg})) if $pg->{result}->{msg}; 1037 print CGI::end_div(); 1038 1039 print CGI::start_p(); 1040 1041 if ($can{showCorrectAnswers}) { 1042 print CGI::checkbox( 1043 -name => "showCorrectAnswers", 1044 -checked => $will{showCorrectAnswers}, 1045 -label => "Show correct answers", 1046 ); 1047 } 1048 if ($can{showHints}) { 1049 print CGI::div({style=>"color:red"}, 1050 CGI::checkbox( 1051 -name => "showHints", 1052 -checked => $will{showHints}, 1053 -label => "Show Hints", 1054 ) 1055 ); 1056 } 1057 if ($can{showSolutions}) { 1058 print CGI::checkbox( 1059 -name => "showSolutions", 1060 -checked => $will{showSolutions}, 1061 -label => "Show Solutions", 1062 ); 1063 } 1064 1065 if ($can{showCorrectAnswers} or $can{showHints} or $can{showSolutions}) { 1066 print CGI::br(); 1067 } 1068 1069 print CGI::submit(-name=>"previewAnswers", -label=>"Preview Answers"); 1070 if ($can{checkAnswers}) { 1071 print CGI::submit(-name=>"checkAnswers", -label=>"Check Answers"); 1072 } 1073 if ($can{recordAnswers}) { 1074 if ($user ne $effectiveUser) { 1075 # if acting as a student, make it clear that answer submissions will 1076 # apply to the student's records, not the professor's. 1077 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers for $effectiveUser"); 1078 } else { 1079 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers"); 1080 } 1081 } 1082 1083 print CGI::end_p(); 1084 1085 print CGI::start_div({class=>"scoreSummary"}); 1086 1087 # score summary 1088 my $attempts = $problem->num_correct + $problem->num_incorrect; 1089 my $attemptsNoun = $attempts != 1 ? "times" : "time"; 1090 my $lastScore = sprintf("%.0f%%", $problem->status * 100); # Round to whole number 1091 my ($attemptsLeft, $attemptsLeftNoun); 1092 if ($problem->max_attempts == -1) { 1093 # unlimited attempts 1094 $attemptsLeft = "unlimited"; 1095 $attemptsLeftNoun = "attempts"; 1096 } else { 1097 $attemptsLeft = $problem->max_attempts - $attempts; 1098 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts"; 1099 } 1100 1101 my $setClosed = 0; 1102 my $setClosedMessage; 1103 if (before($set->open_date) or after($set->due_date)) { 1104 $setClosed = 1; 1105 $setClosedMessage = "This problem set is closed."; 1106 if ($authz->hasPermissions($user, "view_answers")) { 1107 $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded."; 1108 } else { 1109 $setClosedMessage .= " Additional attempts will not be recorded."; 1110 } 1111 } 1112 1113 my $notCountedMessage = ($problem->value) ? "" : "(This problem will not count towards your grade.)"; 1114 print CGI::p( 1115 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "", 1116 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(), 1117 $problem->attempted 1118 ? "Your recorded score is $lastScore. $notCountedMessage" . CGI::br() 1119 : "", 1120 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining." 1121 ); 1122 print CGI::end_div(); 1123 1124 # save state for viewOptions 1125 print CGI::hidden( 1126 -name => "showOldAnswers", 1127 -value => $will{showOldAnswers} 1128 ), 1129 1130 CGI::hidden( 1131 -name => "displayMode", 1132 -value => $self->{displayMode} 1133 ); 1134 print( CGI::hidden( 1135 -name => 'editMode', 1136 -value => $self->{editMode}, 1137 ) 1138 ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile'; 1139 print( CGI::hidden( 1140 -name => 'sourceFilePath', 1141 -value => $self->{problem}->{source_file} 1142 )) if defined($self->{problem}->{source_file}); 1143 1144 print( CGI::hidden( 1145 -name => 'problemSeed', 1146 -value => $r->param("problemSeed") 1147 )) if defined($r->param("problemSeed")); 1148 1149 # end of main form 1150 print CGI::endform(); 1151 1152 print CGI::start_div({class=>"problemFooter"}); 1153 1154 ## arguments for answer inspection button 1155 #my $prof_url = $ce->{webworkURLs}->{oldProf}; 1156 #my $webworkURL = $ce->{webworkURLs}->{root}; 1157 #my $cgi_url = $prof_url; 1158 #$cgi_url=~ s|/[^/]*$||; # clip profLogin.pl 1159 #my $authen_args = $self->url_authen_args(); 1160 #my $showPastAnswersURL = "$webworkURL/$courseName/instructor/show_answers/"; 1161 1162 my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers", 1163 courseID => $courseName); 1164 my $showPastAnswersURL = $self->systemLink($pastAnswersPage, authen => 0); # no authen info for form action 1165 1166 # print answer inspection button 1167 if ($authz->hasPermissions($user, "view_answers")) { 1168 print "\n", 1169 CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n", 1170 $self->hidden_authen_fields,"\n", 1171 CGI::hidden(-name => 'courseID', -value=>$courseName), "\n", 1172 CGI::hidden(-name => 'problemID', -value=>$problem->problem_id), "\n", 1173 CGI::hidden(-name => 'setID', -value=>$problem->set_id), "\n", 1174 CGI::hidden(-name => 'studentUser', -value=>$problem->user_id), "\n", 1175 CGI::p( {-align=>"left"}, 1176 CGI::submit(-name => 'action', -value=>'Show Past Answers') 1177 ), "\n", 1178 CGI::endform(); 1179 } 1180 1181 # feedback form url 1182 my $feedbackPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Feedback", 1183 courseID => $courseName); 1184 my $feedbackURL = $self->systemLink($feedbackPage, authen => 0); # no authen info for form action 1185 1186 #print feedback form 1187 print 1188 CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n", 1189 $self->hidden_authen_fields,"\n", 1190 CGI::hidden("module", __PACKAGE__),"\n", 1191 CGI::hidden("set", $set->set_id),"\n", 1192 CGI::hidden("problem", $problem->problem_id),"\n", 1193 CGI::hidden("displayMode", $self->{displayMode}),"\n", 1194 CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", 1195 CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", 1196 CGI::hidden("showHints", $will{showHints}),"\n", 1197 CGI::hidden("showSolutions", $will{showSolutions}),"\n", 1198 CGI::p({-align=>"left"}, 1199 CGI::submit(-name=>"feedbackForm", -label=>"Email instructor") 1200 ), 1201 CGI::endform(),"\n"; 1202 1203 # FIXME print editor link 1204 print $editorLink; #empty unless it is appropriate to have an editor link. 1205 1206 print CGI::end_div(); 1207 1208 # debugging stuff 1209 if (0) { 1210 print 1211 CGI::hr(), 1212 CGI::h2("debugging information"), 1213 CGI::h3("form fields"), 1214 ref2string($self->{formFields}), 1215 CGI::h3("user object"), 1216 ref2string($self->{user}), 1217 CGI::h3("set object"), 1218 ref2string($set), 1219 CGI::h3("problem object"), 1220 ref2string($problem), 1221 CGI::h3("PG object"), 1222 ref2string($pg, {'WeBWorK::PG::Translator' => 1}); 1223 } 1224 1225 return ""; 1226 } 1227 1228 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |