Parent Directory
|
Revision Log
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.225 2010/05/28 21:29:48 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::Problem; 18 use base qw(WeBWorK); 19 use base qw(WeBWorK::ContentGenerator); 20 use base qw(WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil); 21 22 =head1 NAME 23 24 WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. 25 26 =cut 27 28 use strict; 29 use warnings; 30 #use CGI qw(-nosticky ); 31 use WeBWorK::CGI; 32 use File::Path qw(rmtree); 33 use WeBWorK::Debug; 34 use WeBWorK::Form; 35 use WeBWorK::PG; 36 use WeBWorK::PG::ImageGenerator; 37 use WeBWorK::PG::IO; 38 use WeBWorK::Utils qw(readFile writeLog writeCourseLog encodeAnswers decodeAnswers 39 ref2string makeTempDirectory path_is_subdir sortByName before after between); 40 use WeBWorK::DB::Utils qw(global2user user2global); 41 use URI::Escape; 42 43 use WeBWorK::Utils::Tasks qw(fake_set fake_problem); 44 45 ################################################################################ 46 # CGI param interface to this module (up-to-date as of v1.153) 47 ################################################################################ 48 49 # Standard params: 50 # 51 # user - user ID of real user 52 # key - session key 53 # effectiveUser - user ID of effective user 54 # 55 # Integration with PGProblemEditor: 56 # 57 # editMode - if set, indicates alternate problem source location. 58 # can be "temporaryFile" or "savedFile". 59 # 60 # sourceFilePath - path to file to be edited 61 # problemSeed - force problem seed to value 62 # success - success message to display 63 # failure - failure message to display 64 # 65 # Rendering options: 66 # 67 # displayMode - name of display mode to use 68 # 69 # showOldAnswers - request that last entered answer be shown (if allowed) 70 # showCorrectAnswers - request that correct answers be shown (if allowed) 71 # showHints - request that hints be shown (if allowed) 72 # showSolutions - request that solutions be shown (if allowed) 73 # 74 # Problem interaction: 75 # 76 # AnSwEr# - answer blanks in problem 77 # 78 # redisplay - name of the "Redisplay Problem" button 79 # submitAnswers - name of "Submit Answers" button 80 # checkAnswers - name of the "Check Answers" button 81 # previewAnswers - name of the "Preview Answers" button 82 83 ################################################################################ 84 # "can" methods 85 ################################################################################ 86 87 # Subroutines to determine if a user "can" perform an action. Each subroutine is 88 # called with the following arguments: 89 # 90 # ($self, $User, $EffectiveUser, $Set, $Problem) 91 92 # Note that significant parts of the "can" methods are lifted into the 93 # GatewayQuiz module. It isn't direct, however, because of the necessity 94 # of dealing with versioning there. 95 96 sub can_showOldAnswers { 97 #my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; 98 99 return 1; 100 } 101 102 sub can_showCorrectAnswers { 103 my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; 104 my $authz = $self->r->authz; 105 106 return 107 after($Set->answer_date) 108 || 109 $authz->hasPermissions($User->user_id, "show_correct_answers_before_answer_date") 110 ; 111 } 112 113 sub can_showHints { 114 #my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; 115 116 return 1; 117 } 118 119 sub can_showSolutions { 120 my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; 121 my $authz = $self->r->authz; 122 123 return 124 after($Set->answer_date) 125 || 126 $authz->hasPermissions($User->user_id, "show_solutions_before_answer_date") 127 ; 128 } 129 130 sub can_recordAnswers { 131 my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; 132 my $authz = $self->r->authz; 133 my $thisAttempt = $submitAnswers ? 1 : 0; 134 if ($User->user_id ne $EffectiveUser->user_id) { 135 return $authz->hasPermissions($User->user_id, "record_answers_when_acting_as_student"); 136 } 137 if (before($Set->open_date)) { 138 return $authz->hasPermissions($User->user_id, "record_answers_before_open_date"); 139 } elsif (between($Set->open_date, $Set->due_date)) { 140 my $max_attempts = $Problem->max_attempts; 141 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt; 142 if ($max_attempts == -1 or $attempts_used < $max_attempts) { 143 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_with_attempts"); 144 } else { 145 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_without_attempts"); 146 } 147 } elsif (between($Set->due_date, $Set->answer_date)) { 148 return $authz->hasPermissions($User->user_id, "record_answers_after_due_date"); 149 } elsif (after($Set->answer_date)) { 150 return $authz->hasPermissions($User->user_id, "record_answers_after_answer_date"); 151 } 152 } 153 154 sub can_checkAnswers { 155 my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; 156 my $authz = $self->r->authz; 157 my $thisAttempt = $submitAnswers ? 1 : 0; 158 159 if (before($Set->open_date)) { 160 return $authz->hasPermissions($User->user_id, "check_answers_before_open_date"); 161 } elsif (between($Set->open_date, $Set->due_date)) { 162 my $max_attempts = $Problem->max_attempts; 163 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt; 164 if ($max_attempts == -1 or $attempts_used < $max_attempts) { 165 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_with_attempts"); 166 } else { 167 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_without_attempts"); 168 } 169 } elsif (between($Set->due_date, $Set->answer_date)) { 170 return $authz->hasPermissions($User->user_id, "check_answers_after_due_date"); 171 } elsif (after($Set->answer_date)) { 172 return $authz->hasPermissions($User->user_id, "check_answers_after_answer_date"); 173 } 174 } 175 176 # Reset the default in some cases 177 sub set_showOldAnswers_default { 178 my ($self, $ce, $userName, $authz, $set) = @_; 179 # these people always use the system/course default, so don't 180 # override the value of ...->{showOldAnswers} 181 return if $authz->hasPermissions($userName, "can_always_use_show_old_answers_default"); 182 # this person should always default to 0 183 $ce->{pg}->{options}->{showOldAnswers} = 0 184 unless ($authz->hasPermissions($userName, "can_show_old_answers_by_default")); 185 # we are after the due date, so default to not showing it 186 $ce->{pg}->{options}->{showOldAnswers} = 0 if $set->{due_date} && after($set->{due_date}); 187 } 188 189 ################################################################################ 190 # output utilities 191 ################################################################################ 192 193 # Note: the substance of attemptResults is lifted into GatewayQuiz.pm, 194 # with some changes to the output format 195 196 sub attemptResults { 197 my $self = shift; 198 my $pg = shift; 199 my $showAttemptAnswers = shift; 200 my $showCorrectAnswers = shift; 201 my $showAttemptResults = $showAttemptAnswers && shift; 202 my $showSummary = shift; 203 my $showAttemptPreview = shift || 0; 204 205 my $ce = $self->r->ce; 206 207 # for color coding the responses. 208 my @correct_ids = (); 209 my @incorrect_ids = (); 210 211 212 my $problemResult = $pg->{result}; # the overall result of the problem 213 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }; 214 215 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; 216 217 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview"; 218 219 # to make grabbing these options easier, we'll pull them out now... 220 my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}}; 221 222 my $imgGen = WeBWorK::PG::ImageGenerator->new( 223 tempDir => $ce->{webworkDirs}->{tmp}, 224 latex => $ce->{externalPrograms}->{latex}, 225 dvipng => $ce->{externalPrograms}->{dvipng}, 226 useCache => 1, 227 cacheDir => $ce->{webworkDirs}->{equationCache}, 228 cacheURL => $ce->{webworkURLs}->{equationCache}, 229 cacheDB => $ce->{webworkFiles}->{equationCacheDB}, 230 dvipng_align => $imagesModeOptions{dvipng_align}, 231 dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, 232 ); 233 234 my $header; 235 #$header .= CGI::th("Part"); 236 #$header .= $showAttemptAnswers ? CGI::th("Entered") : ""; 237 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : ""; 238 $header .= $showCorrectAnswers ? CGI::th("Correct") : ""; 239 $header .= $showAttemptResults ? CGI::th("Result") : ""; 240 $header .= $showMessages ? CGI::th("Messages") : ""; 241 my $fully = ''; 242 my @tableRows = ( $header ); 243 my $numCorrect = 0; 244 my $numBlanks =0; 245 my $tthPreambleCache; 246 foreach my $name (@answerNames) { 247 my $answerResult = $pg->{answers}->{$name}; 248 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans 249 my $preview = ($showAttemptPreview 250 ? $self->previewAnswer($answerResult, $imgGen, \$tthPreambleCache) 251 : ""); 252 my $correctAnswerPreview = $self->previewCorrectAnswer($answerResult, $imgGen, \$tthPreambleCache); 253 my $correctAnswer = $answerResult->{correct_ans}; 254 my $answerScore = $answerResult->{score}; 255 my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; 256 $answerMessage =~ s/\n/<BR>/g; 257 $numCorrect += $answerScore >= 1; 258 $numBlanks++ unless $studentAnswer =~/\S/ || $answerScore >= 1; # unless student answer contains entry 259 my $resultString = $answerScore >= 1 ? CGI::span({class=>"ResultsWithoutError"}, "correct") : 260 $answerScore > 0 ? int($answerScore*100)."% correct" : 261 CGI::span({class=>"ResultsWithError"}, "incorrect"); 262 $fully = 'completely ' if $answerScore >0 and $answerScore < 1; 263 264 push @correct_ids, $name if $answerScore == 1; 265 push @incorrect_ids, $name if $answerScore < 1; 266 267 # get rid of the goofy prefix on the answer names (supposedly, the format 268 # of the answer names is changeable. this only fixes it for "AnSwEr" 269 #$name =~ s/^AnSwEr//; 270 271 my $row; 272 #$row .= CGI::td($name); 273 #$row .= $showAttemptAnswers ? CGI::td($self->nbsp($studentAnswer)) : ""; 274 $row .= $showAttemptPreview ? CGI::td({onmouseover=>qq!Tip('$studentAnswer',SHADOW, true, 275 DELAY, 1000, FADEIN, 300, FADEOUT, 300, STICKY, 1, OFFSETX, -20, CLOSEBTN, true, CLICKCLOSE, false, 276 BGCOLOR, '#F4FF91', TITLE, 'Entered:',TITLEBGCOLOR, '#F4FF91', TITLEFONTCOLOR, '#000000')!}, 277 $self->nbsp($preview)) : ""; 278 $row .= $showCorrectAnswers ? CGI::td({onmouseover=> qq!Tip('$correctAnswer',SHADOW, true, 279 DELAY, 1000, FADEIN, 300, FADEOUT, 300, STICKY, 1, OFFSETX, -20, CLOSEBTN, true, CLICKCLOSE, false, 280 BGCOLOR, '#F4FF91', TITLE, 'Entered:',TITLEBGCOLOR, '#F4FF91', TITLEFONTCOLOR, '#000000')!}, 281 $self->nbsp($correctAnswerPreview)) : ""; 282 $row .= $showAttemptResults ? CGI::td($self->nbsp($resultString)) : ""; 283 $row .= $showMessages ? CGI::td({-class=>"Message"},$self->nbsp($answerMessage)) : ""; 284 push @tableRows, $row; 285 } 286 287 # render equation images 288 $imgGen->render(refresh => 1); 289 290 # my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; 291 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); 292 # FIXME -- I left the old code in in case we have to back out. 293 # my $summary = "On this attempt, you answered $numCorrect out of " 294 # . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; 295 my $summary = ""; 296 unless (defined($problemResult->{summary}) and $problemResult->{summary} =~ /\S/) { 297 if (scalar @answerNames == 1) { #default messages 298 if ($numCorrect == scalar @answerNames) { 299 $summary .= CGI::div({class=>"ResultsWithoutError"},"The answer above is correct."); 300 } else { 301 $summary .= CGI::div({class=>"ResultsWithError"},"The answer above is NOT ${fully}correct."); 302 } 303 } else { 304 if ($numCorrect == scalar @answerNames) { 305 $summary .= CGI::div({class=>"ResultsWithoutError"},"All of the answers above are correct."); 306 } 307 unless ($numCorrect + $numBlanks == scalar( @answerNames)) { 308 $summary .= CGI::div({class=>"ResultsWithError"},"At least one of the answers above is NOT ${fully}correct."); 309 } 310 if ($numBlanks) { 311 my $s = ($numBlanks>1)?'':'s'; 312 $summary .= CGI::div({class=>"ResultsAlert"},"$numBlanks of the questions remain$s unanswered."); 313 } 314 } 315 } else { 316 $summary = $problemResult->{summary}; # summary has been defined by grader 317 } 318 319 $self->{correct_ids}=[@correct_ids] if @correct_ids; 320 $self->{incorrect_ids} = [@incorrect_ids] if @incorrect_ids; 321 322 return 323 CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) 324 . ($showSummary ? CGI::p({class=>'attemptResultsSummary'},$summary) : ""); 325 } 326 327 328 # Note: previewAnswer is lifted into GatewayQuiz.pm 329 330 sub previewAnswer { 331 my ($self, $answerResult, $imgGen, $tthPreambleCache) = @_; 332 my $ce = $self->r->ce; 333 my $effectiveUser = $self->{effectiveUser}; 334 my $set = $self->{set}; 335 my $problem = $self->{problem}; 336 my $displayMode = $self->{displayMode}; 337 338 # note: right now, we have to do things completely differently when we are 339 # rendering math from INSIDE the translator and from OUTSIDE the translator. 340 # so we'll just deal with each case explicitly here. there's some code 341 # duplication that can be dealt with later by abstracting out tth/dvipng/etc. 342 343 my $tex = $answerResult->{preview_latex_string}; 344 345 return "" unless defined $tex and $tex ne ""; 346 347 if ($displayMode eq "plainText") { 348 return $tex; 349 } elsif ($displayMode eq "formattedText") { 350 351 # read the TTH preamble, or use the cached copy passed in from the caller 352 my $tthPreamble=''; 353 if (defined $$tthPreambleCache) { 354 $tthPreamble = $$tthPreambleCache; 355 } else { 356 my $tthPreambleFile = $ce->{courseDirs}->{templates} . "/tthPreamble.tex"; 357 if (-r $tthPreambleFile) { 358 $tthPreamble = readFile($tthPreambleFile); 359 # thanks to Jim Martino. each line in the definition file should end with 360 #a % to prevent adding supurious paragraphs to output: 361 $tthPreamble =~ s/(.)\n/$1%\n/g; 362 # solves the problem if the file doesn't end with a return: 363 $tthPreamble .="%\n"; 364 # store preamble in cache: 365 $$tthPreambleCache = $tthPreamble; 366 } else { 367 } 368 } 369 370 # construct TTH command line 371 my $tthCommand = $ce->{externalPrograms}->{tth} 372 . " -L -f5 -u -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n" 373 . $tthPreamble . "\\[" . $tex . "\\]\n" 374 . "END_OF_INPUT\n"; 375 376 # call tth 377 my $result = `$tthCommand`; 378 if ($?) { 379 return "<b>[tth failed: $? $@]</b>"; 380 } else { 381 # avoid border problems in tables and remove unneeded initial <br> 382 $result =~ s/(<table [^>]*)>/$1 CLASS="ArrayLayout">/gi; 383 $result =~ s!\s*<br clear="all" />!!; 384 return $result; 385 } 386 387 } elsif ($displayMode eq "images") { 388 $imgGen->add($tex); 389 } elsif ($displayMode eq "MathJax") { 390 return '<span class="MathJax_Preview">[math]</span><script type="math/tex; mode=display">'.$tex.'</script>'; 391 } elsif ($displayMode eq "jsMath") { 392 $tex =~ s/&/&/g; $tex =~ s/</</g; $tex =~ s/>/>/g; 393 return '<SPAN CLASS="math">\\displaystyle{'.$tex.'}</SPAN>'; 394 } 395 } 396 sub previewCorrectAnswer { 397 my ($self, $answerResult, $imgGen, $tthPreambleCache) = @_; 398 my $ce = $self->r->ce; 399 my $effectiveUser = $self->{effectiveUser}; 400 my $set = $self->{set}; 401 my $problem = $self->{problem}; 402 my $displayMode = $self->{displayMode}; 403 404 # note: right now, we have to do things completely differently when we are 405 # rendering math from INSIDE the translator and from OUTSIDE the translator. 406 # so we'll just deal with each case explicitly here. there's some code 407 # duplication that can be dealt with later by abstracting out tth/dvipng/etc. 408 409 my $tex = $answerResult->{correct_ans_latex_string}; 410 return $answerResult->{correct_ans} unless defined $tex and $tex=~/\S/; # some answers don't have latex strings defined 411 # return "" unless defined $tex and $tex ne ""; 412 413 if ($displayMode eq "plainText") { 414 return $tex; 415 } elsif ($displayMode eq "formattedText") { 416 417 # read the TTH preamble, or use the cached copy passed in from the caller 418 my $tthPreamble=''; 419 if (defined $$tthPreambleCache) { 420 $tthPreamble = $$tthPreambleCache; 421 } else { 422 my $tthPreambleFile = $ce->{courseDirs}->{templates} . "/tthPreamble.tex"; 423 if (-r $tthPreambleFile) { 424 $tthPreamble = readFile($tthPreambleFile); 425 # thanks to Jim Martino. each line in the definition file should end with 426 #a % to prevent adding supurious paragraphs to output: 427 $tthPreamble =~ s/(.)\n/$1%\n/g; 428 # solves the problem if the file doesn't end with a return: 429 $tthPreamble .="%\n"; 430 # store preamble in cache: 431 $$tthPreambleCache = $tthPreamble; 432 } else { 433 } 434 } 435 436 # construct TTH command line 437 my $tthCommand = $ce->{externalPrograms}->{tth} 438 . " -L -f5 -u -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n" 439 . $tthPreamble . "\\[" . $tex . "\\]\n" 440 . "END_OF_INPUT\n"; 441 442 # call tth 443 my $result = `$tthCommand`; 444 if ($?) { 445 return "<b>[tth failed: $? $@]</b>"; 446 } else { 447 # avoid border problems in tables and remove unneeded initial <br> 448 $result =~ s/(<table [^>]*)>/$1 CLASS="ArrayLayout">/gi; 449 $result =~ s!\s*<br clear="all" />!!; 450 return $result; 451 } 452 453 } elsif ($displayMode eq "images") { 454 $imgGen->add($tex); 455 } elsif ($displayMode eq "MathJax") { 456 return '<span class="MathJax_Preview">[math]</span><script type="math/tex; mode=display">'.$tex.'</script>'; 457 } elsif ($displayMode eq "jsMath") { 458 $tex =~ s/&/&/g; $tex =~ s/</</g; $tex =~ s/>/>/g; 459 return '<SPAN CLASS="math">\\displaystyle{'.$tex.'}</SPAN>'; 460 } 461 } 462 463 ################################################################################ 464 # Template escape implementations 465 ################################################################################ 466 467 sub pre_header_initialize { 468 my ($self) = @_; 469 my $r = $self->r; 470 my $ce = $r->ce; 471 my $db = $r->db; 472 my $authz = $r->authz; 473 my $urlpath = $r->urlpath; 474 475 my $setName = $urlpath->arg("setID"); 476 my $problemNumber = $r->urlpath->arg("problemID"); 477 my $userName = $r->param('user'); 478 my $effectiveUserName = $r->param('effectiveUser'); 479 my $key = $r->param('key'); 480 my $editMode = $r->param("editMode"); 481 482 my $user = $db->getUser($userName); # checked 483 die "record for user $userName (real user) does not exist." 484 unless defined $user; 485 486 my $effectiveUser = $db->getUser($effectiveUserName); # checked 487 die "record for user $effectiveUserName (effective user) does not exist." 488 unless defined $effectiveUser; 489 490 # obtain the merged set for $effectiveUser 491 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked 492 493 $self->set_showOldAnswers_default($ce, $userName, $authz, $set); 494 495 # Database fix (in case of undefined visiblity state values) 496 # this is only necessary because some people keep holding to ww1.9 which did not have a visible field 497 # make sure visible is set to 0 or 1 498 if ( $set and $set->visible ne "0" and $set->visible ne "1") { 499 my $globalSet = $db->getGlobalSet($set->set_id); 500 $globalSet->visible("1"); # defaults to visible 501 $db->putGlobalSet($globalSet); 502 $set = $db->getMergedSet($effectiveUserName, $setName); 503 } else { 504 # don't do anything just yet, maybe we're a professor and we're 505 # fabricating a set or haven't assigned it to ourselves just yet 506 } 507 # When a set is created enable_reduced_scoring is null, so we have to set it 508 if ( $set and $set->enable_reduced_scoring ne "0" and $set->enable_reduced_scoring ne "1") { 509 my $globalSet = $db->getGlobalSet($set->set_id); 510 $globalSet->enable_reduced_scoring("0"); # defaults to disabled 511 $db->putGlobalSet($globalSet); 512 $set = $db->getMergedSet($effectiveUserName, $setName); 513 } 514 515 516 # obtain the merged problem for $effectiveUser 517 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked 518 519 if ($authz->hasPermissions($userName, "modify_problem_sets")) { 520 # professors are allowed to fabricate sets and problems not 521 # assigned to them (or anyone). this allows them to use the 522 # editor to 523 524 # if a User Set does not exist for this user and this set 525 # then we check the Global Set 526 # if that does not exist we create a fake set 527 # if it does, we add fake user data 528 unless (defined $set) { 529 my $userSetClass = $db->{set_user}->{record}; 530 my $globalSet = $db->getGlobalSet($setName); # checked 531 532 if (not defined $globalSet) { 533 $set = fake_set($db); 534 } else { 535 $set = global2user($userSetClass, $globalSet); 536 $set->psvn(0); 537 } 538 } 539 540 # if that is not yet defined obtain the global problem, 541 # convert it to a user problem, and add fake user data 542 unless (defined $problem) { 543 my $userProblemClass = $db->{problem_user}->{record}; 544 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked 545 # if the global problem doesn't exist either, bail! 546 if(not defined $globalProblem) { 547 my $sourceFilePath = $r->param("sourceFilePath"); 548 die "sourceFilePath is unsafe!" unless path_is_subdir($sourceFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir 549 # These are problems from setmaker. If declared invalid, they won't come up 550 $self->{invalidProblem} = $self->{invalidSet} = 1 unless defined $sourceFilePath; 551 # die "Problem $problemNumber in set $setName does not exist" unless defined $sourceFilePath; 552 $problem = fake_problem($db); 553 $problem->problem_id(1); 554 $problem->source_file($sourceFilePath); 555 $problem->user_id($effectiveUserName); 556 } else { 557 $problem = global2user($userProblemClass, $globalProblem); 558 $problem->user_id($effectiveUserName); 559 $problem->problem_seed(0); 560 $problem->status(0); 561 $problem->attempted(0); 562 $problem->last_answer(""); 563 $problem->num_correct(0); 564 $problem->num_incorrect(0); 565 } 566 } 567 568 # now we're sure we have valid UserSet and UserProblem objects 569 # yay! 570 571 # now deal with possible editor overrides: 572 573 # if the caller is asking to override the source file, and 574 # editMode calls for a temporary file, do so 575 my $sourceFilePath = $r->param("sourceFilePath"); 576 if (defined $editMode and $editMode eq "temporaryFile" and defined $sourceFilePath) { 577 die "sourceFilePath is unsafe!" unless path_is_subdir($sourceFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir 578 $problem->source_file($sourceFilePath); 579 } 580 581 # if the problem does not have a source file or no source file has been passed in 582 # then this is really an invalid problem (probably from a bad URL) 583 $self->{invalidProblem} = not (defined $sourceFilePath or $problem->source_file); 584 585 # if the caller is asking to override the problem seed, do so 586 my $problemSeed = $r->param("problemSeed"); 587 if (defined $problemSeed) { 588 $problem->problem_seed($problemSeed); 589 } 590 591 my $visiblityStateClass = ($set->visible) ? "visible" : "hidden"; 592 my $visiblityStateText = ($set->visible) ? "visible to students." : "hidden from students."; 593 $self->addmessage(CGI::span("This set is " . CGI::font({class=>$visiblityStateClass}, $visiblityStateText))); 594 595 # test for additional problem validity if it's not already invalid 596 } else { 597 $self->{invalidProblem} = !(defined $problem and ($set->visible || $authz->hasPermissions($userName, "view_hidden_sets"))); 598 599 $self->addbadmessage(CGI::p("This problem will not count towards your grade.")) if $problem and not $problem->value and not $self->{invalidProblem}; 600 } 601 602 $self->{userName} = $userName; 603 $self->{effectiveUserName} = $effectiveUserName; 604 $self->{user} = $user; 605 $self->{effectiveUser} = $effectiveUser; 606 $self->{set} = $set; 607 $self->{problem} = $problem; 608 $self->{editMode} = $editMode; 609 610 ##### form processing ##### 611 612 # set options from form fields (see comment at top of file for names) 613 my $displayMode = $r->param("displayMode") || $ce->{pg}->{options}->{displayMode}; 614 my $redisplay = $r->param("redisplay"); 615 my $submitAnswers = $r->param("submitAnswers"); 616 my $checkAnswers = $r->param("checkAnswers"); 617 my $previewAnswers = $r->param("previewAnswers"); 618 619 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; 620 621 $self->{displayMode} = $displayMode; 622 $self->{redisplay} = $redisplay; 623 $self->{submitAnswers} = $submitAnswers; 624 $self->{checkAnswers} = $checkAnswers; 625 $self->{previewAnswers} = $previewAnswers; 626 $self->{formFields} = $formFields; 627 628 # get result and send to message 629 my $status_message = $r->param("status_message"); 630 $self->addmessage(CGI::p("$status_message")) if $status_message; 631 632 # now that we've set all the necessary variables quit out if the set or problem is invalid 633 return if $self->{invalidSet} || $self->{invalidProblem}; 634 635 ##### permissions ##### 636 637 # what does the user want to do? 638 #FIXME There is a problem with checkboxes -- if they are not checked they are invisible. Hence if the default mode in $ce is 1 639 # there is no way to override this. Probably this is ok for the last three options, but it was definitely not ok for showing 640 # saved answers which is normally on, but you want to be able to turn it off! This section should be moved to ContentGenerator 641 # so that you can set these options anywhere. We also need mechanisms for making them sticky. 642 # Note: ProblemSet and ProblemSets might set showOldAnswers to '', which 643 # needs to be treated as if it is not set. 644 my %want = ( 645 showOldAnswers => (defined($r->param("showOldAnswers")) and $r->param("showOldAnswers") ne '') ? $r->param("showOldAnswers") : $ce->{pg}->{options}->{showOldAnswers}, 646 showCorrectAnswers => $r->param("showCorrectAnswers") || $ce->{pg}->{options}->{showCorrectAnswers}, 647 showHints => $r->param("showHints") || $ce->{pg}->{options}->{showHints}, 648 showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}->{showSolutions}, 649 recordAnswers => $submitAnswers, 650 checkAnswers => $checkAnswers, 651 getSubmitButton => 1, 652 ); 653 654 # are certain options enforced? 655 my %must = ( 656 showOldAnswers => 0, 657 showCorrectAnswers => 0, 658 showHints => 0, 659 showSolutions => 0, 660 recordAnswers => ! $authz->hasPermissions($userName, "avoid_recording_answers"), 661 checkAnswers => 0, 662 getSubmitButton => 0, 663 ); 664 665 # does the user have permission to use certain options? 666 my @args = ($user, $effectiveUser, $set, $problem); 667 my %can = ( 668 showOldAnswers => $self->can_showOldAnswers(@args), 669 showCorrectAnswers => $self->can_showCorrectAnswers(@args), 670 showHints => $self->can_showHints(@args), 671 showSolutions => $self->can_showSolutions(@args), 672 recordAnswers => $self->can_recordAnswers(@args, 0), 673 checkAnswers => $self->can_checkAnswers(@args, $submitAnswers), 674 getSubmitButton => $self->can_recordAnswers(@args, $submitAnswers), 675 ); 676 677 # final values for options 678 my %will; 679 foreach (keys %must) { 680 $will{$_} = $can{$_} && ($want{$_} || $must{$_}); 681 #warn "final values for options $_ is can $can{$_}, want $want{$_}, must $must{$_}, will $will{$_}"; 682 } 683 684 ##### sticky answers ##### 685 686 if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) { 687 # do this only if new answers are NOT being submitted 688 my %oldAnswers = decodeAnswers($problem->last_answer); 689 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; 690 } 691 692 ##### translation ##### 693 694 debug("begin pg processing"); 695 my $pg = WeBWorK::PG->new( 696 $ce, 697 $effectiveUser, 698 $key, 699 $set, 700 $problem, 701 $set->psvn, # FIXME: this field should be removed 702 $formFields, 703 { # translation options 704 displayMode => $displayMode, 705 showHints => $will{showHints}, 706 showSolutions => $will{showSolutions}, 707 refreshMath2img => $will{showHints} || $will{showSolutions}, 708 processAnswers => 1, 709 permissionLevel => $db->getPermissionLevel($userName)->permission, 710 effectivePermissionLevel => $db->getPermissionLevel($effectiveUserName)->permission, 711 }, 712 ); 713 714 debug("end pg processing"); 715 716 ##### fix hint/solution options ##### 717 718 $can{showHints} &&= $pg->{flags}->{hintExists} 719 &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans}; 720 $can{showSolutions} &&= $pg->{flags}->{solutionExists}; 721 722 ##### store fields ##### 723 724 $self->{want} = \%want; 725 $self->{must} = \%must; 726 $self->{can} = \%can; 727 $self->{will} = \%will; 728 $self->{pg} = $pg; 729 } 730 731 sub if_errors($$) { 732 my ($self, $arg) = @_; 733 734 if ($self->{isOpen}) { 735 return $self->{pg}->{flags}->{error_flag} ? $arg : !$arg; 736 } else { 737 return !$arg; 738 } 739 } 740 741 sub head { 742 my ($self) = @_; 743 744 return "" if ( $self->{invalidSet} ); 745 return $self->{pg}->{head_text} if $self->{pg}->{head_text}; 746 } 747 748 sub options { 749 my ($self) = @_; 750 #warn "doing options in Problem"; 751 752 # don't show options if we don't have anything to show 753 return "" if $self->{invalidSet} or $self->{invalidProblem}; 754 755 my $displayMode = $self->{displayMode}; 756 my %can = %{ $self->{can} }; 757 758 my @options_to_show = "displayMode"; 759 push @options_to_show, "showOldAnswers" if $can{showOldAnswers}; 760 push @options_to_show, "showHints" if $can{showHints}; 761 push @options_to_show, "showSolutions" if $can{showSolutions}; 762 763 return $self->optionsMacro( 764 options_to_show => \@options_to_show, 765 extra_params => ["editMode", "sourceFilePath"], 766 ); 767 } 768 769 sub siblings { 770 my ($self) = @_; 771 my $r = $self->r; 772 my $db = $r->db; 773 my $urlpath = $r->urlpath; 774 775 # can't show sibling problems if the set is invalid 776 return "" if $self->{invalidSet}; 777 778 my $courseID = $urlpath->arg("courseID"); 779 my $setID = $self->{set}->set_id; 780 my $eUserID = $r->param("effectiveUser"); 781 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID); 782 783 print CGI::start_div({class=>"info-box", id=>"fisheye"}); 784 print CGI::h2("Problems"); 785 #print CGI::start_ul({class=>"LinksMenu"}); 786 #print CGI::start_li(); 787 #print CGI::span({style=>"font-size:larger"}, "Problems"); 788 print CGI::start_ul(); 789 790 foreach my $problemID (@problemIDs) { 791 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 792 courseID => $courseID, setID => $setID, problemID => $problemID); 793 print CGI::li(CGI::a( {href=>$self->systemLink($problemPage, 794 params=>{ displayMode => $self->{displayMode}, 795 showOldAnswers => $self->{will}->{showOldAnswers} 796 })}, "Problem $problemID") 797 ); 798 } 799 800 print CGI::end_ul(); 801 #print CGI::end_li(); 802 #print CGI::end_ul(); 803 print CGI::end_div(); 804 805 return ""; 806 } 807 808 sub nav { 809 my ($self, $args) = @_; 810 my $r = $self->r; 811 my $db = $r->db; 812 my $urlpath = $r->urlpath; 813 814 return "" if ( $self->{invalidSet} ); 815 816 my $courseID = $urlpath->arg("courseID"); 817 my $setID = $self->{set}->set_id if !($self->{invalidSet}); 818 my $problemID = $self->{problem}->problem_id if !($self->{invalidProblem}); 819 my $eUserID = $r->param("effectiveUser"); 820 821 my ($prevID, $nextID); 822 823 if (!$self->{invalidProblem}) { 824 my @problemIDs = $db->listUserProblems($eUserID, $setID); 825 foreach my $id (@problemIDs) { 826 $prevID = $id if $id < $problemID 827 and (not defined $prevID or $id > $prevID); 828 $nextID = $id if $id > $problemID 829 and (not defined $nextID or $id < $nextID); 830 } 831 } 832 833 my @links; 834 835 if ($prevID) { 836 my $prevPage = $urlpath->newFromModule(__PACKAGE__, 837 courseID => $courseID, setID => $setID, problemID => $prevID); 838 push @links, "Previous Problem", $r->location . $prevPage->path, "navPrev"; 839 } else { 840 push @links, "Previous Problem", "", "navPrevGrey"; 841 } 842 843 if (defined($setID) && $setID ne 'Undefined_Set') { 844 push @links, "Problem List", $r->location . $urlpath->parent->path, "navProbList"; 845 } else { 846 push @links, "Problem List", "", "navProbListGrey"; 847 } 848 849 if ($nextID) { 850 my $nextPage = $urlpath->newFromModule(__PACKAGE__, 851 courseID => $courseID, setID => $setID, problemID => $nextID); 852 push @links, "Next Problem", $r->location . $nextPage->path, "navNext"; 853 } else { 854 push @links, "Next Problem", "", "navNextGrey"; 855 } 856 857 my $tail = ""; 858 859 $tail .= "&displayMode=".$self->{displayMode} if defined $self->{displayMode}; 860 $tail .= "&showOldAnswers=".$self->{will}->{showOldAnswers} 861 if defined $self->{will}->{showOldAnswers}; 862 return $self->navMacro($args, $tail, @links); 863 } 864 865 sub title { 866 my ($self) = @_; 867 868 # using the url arguments won't break if the set/problem are invalid 869 my $setID = WeBWorK::ContentGenerator::underscore2nbsp($self->r->urlpath->arg("setID")); 870 my $problemID = $self->r->urlpath->arg("problemID"); 871 872 return "$setID: Problem $problemID"; 873 } 874 875 876 # now altered to outsource most output operations to the template, main functions now are simply error checking and answer processing - ghe3 877 sub body { 878 my $self = shift; 879 my $set = $self->{set}; 880 my $problem = $self->{problem}; 881 my $pg = $self->{pg}; 882 883 my $valid = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::check_invalid($self); 884 unless($valid eq "valid"){ 885 return $valid; 886 } 887 888 # my $editorLink = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::process_editorLink($self); 889 # if($editorLink eq "permission_error"){ 890 # return ""; 891 # } 892 893 ##### answer processing ##### 894 debug("begin answer processing"); 895 # if answers were submitted: 896 my $scoreRecordedMessage = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::process_and_log_answer($self); 897 debug("end answer processing"); 898 899 ##### javaScripts ############# 900 # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_JS($self); 901 902 ##### output ##### 903 # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_summary($self); 904 905 ########################### 906 # print style sheet for correct and incorrect answers 907 ########################### 908 909 # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_CSS($self); 910 911 ########################### 912 # main form 913 ########################### 914 915 # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_main_form($self,$editorLink); 916 917 # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_footer($self); 918 919 # debugging stuff 920 if (0) { 921 print 922 CGI::hr(), 923 CGI::h2("debugging information"), 924 CGI::h3("form fields"), 925 ref2string($self->{formFields}), 926 CGI::h3("user object"), 927 ref2string($self->{user}), 928 CGI::h3("set object"), 929 ref2string($set), 930 CGI::h3("problem object"), 931 ref2string($problem), 932 CGI::h3("PG object"), 933 ref2string($pg, {'WeBWorK::PG::Translator' => 1}); 934 } 935 debug("leaving body of Problem.pm"); 936 return ""; 937 } 938 939 # output_form_start subroutine 940 941 # prints out the beginning of the main form, and the necessary hidden authentication fields 942 943 sub output_form_start{ 944 my $self = shift; 945 my $r = $self->r; 946 print CGI::start_form(-method=>"POST", -action=> $r->uri,-name=>"problemMainForm", onsubmit=>"submitAction()"); 947 print $self->hidden_authen_fields; 948 return ""; 949 } 950 951 # output_problem_body subroutine 952 953 # prints out the body of the current problem 954 955 sub output_problem_body{ 956 my $self = shift; 957 my $pg = $self->{pg}; 958 959 print "\n"; 960 print CGI::p($pg->{body_text}); 961 return ""; 962 } 963 964 # output_message subroutine 965 966 # prints out a message about the problem 967 968 sub output_message{ 969 my $self = shift; 970 my $pg = $self->{pg}; 971 972 print CGI::p(CGI::b("Note: "). CGI::i($pg->{result}->{msg})) if $pg->{result}->{msg}; 973 return ""; 974 } 975 976 # output_editorLink subroutine 977 978 # processes and prints out the correct link to the editor of the current problem 979 980 sub output_editorLink{ 981 982 my $self = shift; 983 984 my $set = $self->{set}; 985 my $problem = $self->{problem}; 986 my $pg = $self->{pg}; 987 988 my $r = $self->r; 989 990 my $authz = $r->authz; 991 my $urlpath = $r->urlpath; 992 my $user = $r->param('user'); 993 994 my $courseName = $urlpath->arg("courseID"); 995 996 # FIXME: move editor link to top, next to problem number. 997 # format as "[edit]" like we're doing with course info file, etc. 998 # add edit link for set as well. 999 my $editorLink = ""; 1000 # if we are here without a real homework set, carry that through 1001 my $forced_field = []; 1002 $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if 1003 ($set->set_id eq 'Undefined_Set'); 1004 if ($authz->hasPermissions($user, "modify_problem_sets")) { 1005 my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 1006 courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); 1007 my $editorURL = $self->systemLink($editorPage, params=>$forced_field); 1008 $editorLink = CGI::p(CGI::a({href=>$editorURL,target =>'WW_Editor'}, "Edit this problem")); 1009 } 1010 1011 ##### translation errors? ##### 1012 1013 if ($pg->{flags}->{error_flag}) { 1014 if ($authz->hasPermissions($user, "view_problem_debugging_info")) { 1015 print $self->errorOutput($pg->{errors}, $pg->{body_text}); 1016 } else { 1017 print $self->errorOutput($pg->{errors}, "You do not have permission to view the details of this error."); 1018 } 1019 print ""; 1020 } 1021 else{ 1022 print $editorLink; 1023 } 1024 return ""; 1025 } 1026 1027 # output_checkboxes subroutine 1028 1029 # prints out the checkbox input elements that are available for the current problem 1030 1031 sub output_checkboxes{ 1032 my $self = shift; 1033 my %can = %{ $self->{can} }; 1034 my %will = %{ $self->{will} }; 1035 1036 if ($can{showCorrectAnswers}) { 1037 print WeBWorK::CGI_labeled_input( 1038 -type => "checkbox", 1039 -id => "showCorrectAnswers_id", 1040 -label_text => "Show correct answers", 1041 -input_attr => $will{showCorrectAnswers} ? 1042 { 1043 -name => "showCorrectAnswers", 1044 -checked => "checked", 1045 -value => 1, 1046 } 1047 : 1048 { 1049 -name => "showCorrectAnswers", 1050 -value => 1, 1051 } 1052 ); 1053 } 1054 if ($can{showHints}) { 1055 print CGI::div({style=>"color:red"}, 1056 WeBWorK::CGI_labeled_input( 1057 -type => "checkbox", 1058 -id => "showHints_id", 1059 -label_text => "Show Hints", 1060 -input_attr => $will{showHints} ? 1061 { 1062 -name => "showHints", 1063 -checked => "checked", 1064 -value => 1, 1065 } 1066 : 1067 { 1068 -name => "showCorrectAnswers", 1069 -value => 1, 1070 } 1071 ) 1072 ); 1073 } 1074 if ($can{showSolutions}) { 1075 print WeBWorK::CGI_labeled_input( 1076 -type => "checkbox", 1077 -id => "showSolutions_id", 1078 -label_text => "Show Solutions", 1079 -input_attr => $will{showSolutions} ? 1080 { 1081 -name => "showSolutions", 1082 -checked => "checked", 1083 -value => 1, 1084 } 1085 : 1086 { 1087 -name => "showCorrectAnswers", 1088 -value => 1, 1089 } 1090 ); 1091 } 1092 1093 if ($can{showCorrectAnswers} or $can{showHints} or $can{showSolutions}) { 1094 print CGI::br(); 1095 } 1096 1097 return ""; 1098 } 1099 1100 # output_submit_buttons 1101 1102 # prints out the submit button input elements that are available for the current problem 1103 1104 sub output_submit_buttons{ 1105 my $self = shift; 1106 my $r = $self->r; 1107 my %can = %{ $self->{can} }; 1108 1109 my $user = $r->param('user'); 1110 my $effectiveUser = $r->param('effectiveUser'); 1111 1112 print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"previewAnswers_id", -input_attr=>{-name=>"previewAnswers", -value=>"Preview Answers"}); 1113 if ($can{checkAnswers}) { 1114 print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"checkAnswers_id", -input_attr=>{-name=>"checkAnswers", -value=>"Check Answers"}); 1115 } 1116 if ($can{getSubmitButton}) { 1117 if ($user ne $effectiveUser) { 1118 # if acting as a student, make it clear that answer submissions will 1119 # apply to the student's records, not the professor's. 1120 print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"submitAnswers_id", -input_attr=>{-name=>"submitAnswers", -value=>"Submit Answers for $effectiveUser"}); 1121 } else { 1122 #print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers", -onclick=>"alert('submit button clicked')"); 1123 print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"submitAnswers_id", -input_attr=>{-name=>"submitAnswers", -label=>"Submit Answers", -onclick=>""}); 1124 # FIXME for unknown reasons the -onclick label seems to have to be there in order to allow the forms onsubmit to trigger 1125 # WFT??? 1126 } 1127 } 1128 1129 return ""; 1130 } 1131 1132 # output_score_summary subroutine 1133 1134 # prints out a summary of the student's current progress and status on the current problem 1135 1136 sub output_score_summary{ 1137 my $self = shift; 1138 my $problem = $self->{problem}; 1139 my $set = $self->{set}; 1140 my $pg = $self->{pg}; 1141 my $scoreRecordedMessage = ""; 1142 unless(defined $self->{scoreRecordedMessage}){ 1143 $scoreRecordedMessage = $self->{scoreRecordedMessage}; 1144 } 1145 my $submitAnswers = $self->{submitAnswers}; 1146 1147 # score summary 1148 my $attempts = $problem->num_correct + $problem->num_incorrect; 1149 my $attemptsNoun = $attempts != 1 ? "times" : "time"; 1150 my $problem_status = $problem->status || 0; 1151 my $lastScore = sprintf("%.0f%%", $problem_status * 100); # Round to whole number 1152 my ($attemptsLeft, $attemptsLeftNoun); 1153 if ($problem->max_attempts == -1) { 1154 # unlimited attempts 1155 $attemptsLeft = "unlimited"; 1156 $attemptsLeftNoun = "attempts"; 1157 } else { 1158 $attemptsLeft = $problem->max_attempts - $attempts; 1159 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts"; 1160 } 1161 1162 my $setClosed = 0; 1163 my $setClosedMessage; 1164 if (before($set->open_date) or after($set->due_date)) { 1165 $setClosed = 1; 1166 if (before($set->open_date)) { 1167 $setClosedMessage = "This homework set is not yet open."; 1168 } elsif (after($set->due_date)) { 1169 $setClosedMessage = "This homework set is closed."; 1170 } 1171 } 1172 #if (before($set->open_date) or after($set->due_date)) { 1173 # $setClosed = 1; 1174 # $setClosedMessage = "This homework set is closed."; 1175 # if ($authz->hasPermissions($user, "view_answers")) { 1176 # $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded."; 1177 # } else { 1178 # $setClosedMessage .= " Additional attempts will not be recorded."; 1179 # } 1180 #} 1181 unless (defined( $pg->{state}->{state_summary_msg}) and $pg->{state}->{state_summary_msg}=~/\S/) { 1182 my $notCountedMessage = ($problem->value) ? "" : "(This problem will not count towards your grade.)"; 1183 print CGI::p(join("", 1184 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "", 1185 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(), 1186 $submitAnswers ?"You received a score of ".sprintf("%.0f%%", $pg->{result}->{score} * 100)." for this attempt.".CGI::br():'', 1187 $problem->attempted 1188 ? "Your overall recorded score is $lastScore. $notCountedMessage" . CGI::br() 1189 : "", 1190 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining." 1191 )); 1192 }else { 1193 print CGI::p($pg->{state}->{state_summary_msg}); 1194 } 1195 1196 return ""; 1197 } 1198 1199 # output_misc subroutine 1200 1201 # prints out other necessary elements 1202 1203 sub output_misc{ 1204 1205 my $self = shift; 1206 my $r = $self->r; 1207 my $ce = $r->ce; 1208 my $db = $r->db; 1209 my $pg = $self->{pg}; 1210 my %will = %{ $self->{will} }; 1211 my $user = $r->param('user'); 1212 1213 print CGI::start_div(); 1214 1215 my $pgdebug = join(CGI::br(), @{$pg->{pgcore}->{flags}->{DEBUG_messages}} ); 1216 my $pgwarning = join(CGI::br(), @{$pg->{pgcore}->{flags}->{WARNING_messages}} ); 1217 my $pginternalerrors = join(CGI::br(), @{$pg->{pgcore}->get_internal_debug_messages} ); 1218 my $pgerrordiv = $pgdebug||$pgwarning||$pginternalerrors; # is 1 if any of these are non-empty 1219 1220 print CGI::p({style=>"color:red;"}, "Checking additional error messages") if $pgerrordiv ; 1221 print CGI::p("pg debug<br/> $pgdebug" ) if $pgdebug ; 1222 print CGI::p("pg warning<br/>$pgwarning" ) if $pgwarning ; 1223 print CGI::p("pg internal errors<br/> $pginternalerrors") if $pginternalerrors; 1224 print CGI::end_div() if $pgerrordiv ; 1225 1226 # save state for viewOptions 1227 print CGI::hidden( 1228 -name => "showOldAnswers", 1229 -value => $will{showOldAnswers} 1230 ), 1231 1232 CGI::hidden( 1233 -name => "displayMode", 1234 -value => $self->{displayMode} 1235 ); 1236 print( CGI::hidden( 1237 -name => 'editMode', 1238 -value => $self->{editMode}, 1239 ) 1240 ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile'; 1241 1242 # this is a security risk -- students can use this to find the source code for the problem 1243 1244 my $permissionLevel = $db->getPermissionLevel($user)->permission; 1245 my $professorPermissionLevel = $ce->{userRoles}->{professor}; 1246 print( CGI::hidden( 1247 -name => 'sourceFilePath', 1248 -value => $self->{problem}->{source_file} 1249 )) if defined($self->{problem}->{source_file}) and $permissionLevel>= $professorPermissionLevel; # only allow this for professors 1250 1251 print( CGI::hidden( 1252 -name => 'problemSeed', 1253 -value => $r->param("problemSeed") 1254 )) if defined($r->param("problemSeed")) and $permissionLevel>= $professorPermissionLevel; # only allow this for professors 1255 1256 return ""; 1257 } 1258 1259 # output_summary subroutine 1260 1261 # prints out the summary of the questions that the student has answered for the current problem, along with available information about correctness 1262 1263 sub output_summary{ 1264 1265 my $self = shift; 1266 1267 my $editMode = $self->{editMode}; 1268 my $problem = $self->{problem}; 1269 my $pg = $self->{pg}; 1270 my $submitAnswers = $self->{submitAnswers}; 1271 my %will = %{ $self->{will} }; 1272 my $checkAnswers = $self->{checkAnswers}; 1273 my $previewAnswers = $self->{previewAnswers}; 1274 1275 my $r = $self->r; 1276 1277 my $authz = $r->authz; 1278 my $user = $r->param('user'); 1279 1280 # attempt summary 1281 #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. 1282 # until after the due date 1283 # do I need to check $will{showCorrectAnswers} to make preflight work?? 1284 if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) { 1285 # print this if user submitted answers OR requested correct answers 1286 1287 print $self->attemptResults($pg, 1, 1288 $will{showCorrectAnswers}, 1289 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); 1290 } elsif ($checkAnswers) { 1291 # print this if user previewed answers 1292 print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED"), CGI::br(); 1293 print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1); 1294 # show attempt answers 1295 # show correct answers if asked 1296 # show attempt results (correctness) 1297 # show attempt previews 1298 } elsif ($previewAnswers) { 1299 # print this if user previewed answers 1300 print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- ANSWERS NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1); 1301 # show attempt answers 1302 # don't show correct answers 1303 # don't show attempt results (correctness) 1304 # show attempt previews 1305 } 1306 1307 return ""; 1308 } 1309 1310 # output_custom_edit_message 1311 1312 # prints out a custom edit message 1313 1314 sub output_custom_edit_message{ 1315 my $self = shift; 1316 my $r = $self->r; 1317 my $authz = $r->authz; 1318 my $user = $r->param('user'); 1319 my $editMode = $self->{editMode}; 1320 my $problem = $self->{problem}; 1321 1322 # custom message for editor 1323 if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) { 1324 if ($editMode eq "temporaryFile") { 1325 print CGI::p(CGI::div({class=>'temporaryFile'}, "Viewing temporary file: ", $problem->source_file)); 1326 } elsif ($editMode eq "savedFile") { 1327 # taken care of in the initialization phase 1328 } 1329 } 1330 1331 return ""; 1332 } 1333 1334 # output_JS subroutine 1335 1336 # prints out the wz_tooltip.js script for the current site. 1337 1338 sub output_wztooltip_JS{ 1339 1340 my $self = shift; 1341 my $r = $self->r; 1342 my $ce = $r->ce; 1343 1344 my $site_url = $ce->{webworkURLs}->{htdocs}; 1345 1346 print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/wz_tooltip.js"}), CGI::end_script(); 1347 return ""; 1348 } 1349 1350 # output_CSS subroutine 1351 1352 # prints the CSS scripts to page. Does some PERL trickery to form the styles for the correct answers and the incorrect answers (which may be substituted with JS sometime in the future). 1353 1354 sub output_CSS{ 1355 1356 my $self = shift; 1357 my $r = $self->r; 1358 my $ce = $r->ce; 1359 my $pg = $self->{pg}; 1360 1361 # always show colors for checkAnswers 1362 # show colors for submit answer if 1363 if (($self->{checkAnswers}) or ($self->{submitAnswers} and $pg->{flags}->{showPartialCorrectAnswers}) ) { 1364 print CGI::start_style({type=>"text/css"}); 1365 print '#'.join(', #', @{ $self->{correct_ids} }), $ce->{pg}{options}{correct_answer} if ref( $self->{correct_ids} )=~/ARRAY/; #correct green 1366 print '#'.join(', #', @{ $self->{incorrect_ids} }), $ce->{pg}{options}{incorrect_answer} if ref( $self->{incorrect_ids})=~/ARRAY/; #incorrect reddish 1367 print CGI::end_style(); 1368 } 1369 1370 return ""; 1371 } 1372 1373 # output_past_answer_button 1374 1375 # prints out the "Show Past Answers" button 1376 1377 sub output_past_answer_button{ 1378 my $self = shift; 1379 my $r = $self->r; 1380 my $problem = $self->{problem}; 1381 1382 my $authz = $r->authz; 1383 my $urlpath = $r->urlpath; 1384 my $user = $r->param('user'); 1385 1386 my $courseName = $urlpath->arg("courseID"); 1387 1388 my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers", 1389 courseID => $courseName); 1390 my $showPastAnswersURL = $self->systemLink($pastAnswersPage, authen => 0); # no authen info for form action 1391 1392 # print answer inspection button 1393 if ($authz->hasPermissions($user, "view_answers")) { 1394 print "\n", 1395 CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"WW_Info"),"\n", 1396 $self->hidden_authen_fields,"\n", 1397 CGI::hidden(-name => 'courseID', -value=>$courseName), "\n", 1398 CGI::hidden(-name => 'problemID', -value=>$problem->problem_id), "\n", 1399 CGI::hidden(-name => 'setID', -value=>$problem->set_id), "\n", 1400 CGI::hidden(-name => 'studentUser', -value=>$problem->user_id), "\n", 1401 CGI::p( {-align=>"left"}, 1402 CGI::submit(-name => 'action', -value=>'Show Past Answers') 1403 ), "\n", 1404 CGI::endform(); 1405 } 1406 1407 return ""; 1408 } 1409 1410 # output_email_instructor subroutine 1411 1412 # prints out the "Email Instructor" button 1413 1414 sub output_email_instructor{ 1415 my $self = shift; 1416 my $problem = $self->{problem}; 1417 my %will = %{ $self->{will} }; 1418 my $pg = $self->{pg}; 1419 1420 print $self->feedbackMacro( 1421 module => __PACKAGE__, 1422 set => $self->{set}->set_id, 1423 problem => $problem->problem_id, 1424 displayMode => $self->{displayMode}, 1425 showOldAnswers => $will{showOldAnswers}, 1426 showCorrectAnswers => $will{showCorrectAnswers}, 1427 showHints => $will{showHints}, 1428 showSolutions => $will{showSolutions}, 1429 pg_object => $pg, 1430 ); 1431 1432 return ""; 1433 } 1434 1435 # output_hidden_info subroutine 1436 1437 # outputs the hidden fields required for the form 1438 1439 sub output_hidden_info{ 1440 my $self = shift; 1441 1442 if(defined $self->{correct_ids}){ 1443 my $correctRef = $self->{correct_ids}; 1444 my @correct = @$correctRef; 1445 foreach(@correct){ 1446 print CGI::hidden(-name=>"correct_ids", -value=>$_."_val"); 1447 } 1448 } 1449 if(defined $self->{incorrect_ids}){ 1450 my $incorrectRef = $self->{incorrect_ids}; 1451 my @incorrect = @$incorrectRef; 1452 foreach(@incorrect){ 1453 print CGI::hidden(-name=>"incorrect_ids", -value=>$_."_val"); 1454 } 1455 } 1456 1457 return ""; 1458 } 1459 1460 # output_JS subroutine 1461 1462 # outputs all of the Javascript needed for this page. 1463 1464 sub output_JS{ 1465 my $self = shift; 1466 my $r = $self->r; 1467 my $ce = $r->ce; 1468 1469 my $site_url = $ce->{webworkURLs}->{htdocs}; 1470 print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/color.js"}), CGI::end_script(); 1471 return ""; 1472 } 1473 1474 # Simply here to indicate to the template that this page has body part methods which can be called 1475 1476 sub can_body_parts{ 1477 return ""; 1478 } 1479 1480 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |