| 1 | ################################################################################ |
1 | ################################################################################ |
| 2 | # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project |
2 | # WeBWorK Online Homework Delivery System |
| 3 | # $Id$ |
3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
|
|
4 | # $CVSHeader$ |
|
|
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. |
| 4 | ################################################################################ |
15 | ################################################################################ |
| 5 | |
16 | |
| 6 | package WeBWorK::ContentGenerator::Problem; |
17 | package WeBWorK::ContentGenerator::Problem; |
| 7 | use base qw(WeBWorK::ContentGenerator); |
18 | use base qw(WeBWorK::ContentGenerator); |
| 8 | |
19 | |
| 9 | =head1 NAME |
20 | =head1 NAME |
| 10 | |
21 | |
| 11 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
22 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
| 12 | |
23 | |
| 13 | =cut |
24 | =cut |
| 14 | my $timer0_ON=1; # times pg translation phase |
25 | |
| 15 | use strict; |
26 | use strict; |
| 16 | use warnings; |
27 | use warnings; |
| 17 | use CGI qw(); |
28 | use CGI qw(); |
| 18 | use File::Path qw(rmtree); |
29 | use File::Path qw(rmtree); |
| 19 | use WeBWorK::Form; |
30 | use WeBWorK::Form; |
| 20 | use WeBWorK::PG; |
31 | use WeBWorK::PG; |
| 21 | use WeBWorK::PG::ImageGenerator; |
32 | use WeBWorK::PG::ImageGenerator; |
| 22 | use WeBWorK::PG::IO; |
33 | use WeBWorK::PG::IO; |
| 23 | use WeBWorK::Utils qw(writeLog encodeAnswers decodeAnswers ref2string makeTempDirectory); |
34 | use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory); |
| 24 | use WeBWorK::DB::Utils qw(global2user user2global findDefaults); |
35 | use WeBWorK::DB::Utils qw(global2user user2global findDefaults); |
| 25 | use WeBWorK::Timing; |
36 | use WeBWorK::Timing; |
|
|
37 | |
|
|
38 | my $timer0_ON=0; # times pg translation phase |
| 26 | |
39 | |
| 27 | ############################################################ |
40 | ############################################################ |
| 28 | # |
41 | # |
| 29 | # user |
42 | # user |
| 30 | # effectiveUser |
43 | # effectiveUser |
| … | |
… | |
| 40 | # |
53 | # |
| 41 | # redisplay - name of the "Redisplay Problem" button |
54 | # redisplay - name of the "Redisplay Problem" button |
| 42 | # submitAnswers - name of "Submit Answers" button |
55 | # submitAnswers - name of "Submit Answers" button |
| 43 | # checkAnswers - name of the "Check Answers" button |
56 | # checkAnswers - name of the "Check Answers" button |
| 44 | # previewAnswers - name of the "Preview Answers" button |
57 | # previewAnswers - name of the "Preview Answers" button |
|
|
58 | # |
|
|
59 | # FIXME: this table is heinously out of date |
| 45 | # |
60 | # |
| 46 | ############################################################ |
61 | ############################################################ |
|
|
62 | |
|
|
63 | # FIXME: what is this? |
|
|
64 | sub templateName { |
|
|
65 | "problem"; |
|
|
66 | } |
| 47 | |
67 | |
| 48 | sub pre_header_initialize { |
68 | sub pre_header_initialize { |
| 49 | my ($self, $setName, $problemNumber) = @_; |
69 | my ($self, $setName, $problemNumber) = @_; |
| 50 | my $r = $self->{r}; |
70 | my $r = $self->{r}; |
| 51 | my $courseEnv = $self->{ce}; |
71 | my $courseEnv = $self->{ce}; |
| 52 | my $db = $self->{db}; |
72 | my $db = $self->{db}; |
| 53 | my $userName = $r->param('user'); |
73 | my $userName = $r->param('user'); |
| 54 | my $effectiveUserName = $r->param('effectiveUser'); |
74 | my $effectiveUserName = $r->param('effectiveUser'); |
| 55 | my $key = $r->param('key'); |
75 | my $key = $r->param('key'); |
| 56 | my $user = $db->getUser($userName); |
76 | |
|
|
77 | my $user = $db->getUser($userName); # checked |
|
|
78 | die "record for user $userName (real user) does not exist." |
|
|
79 | unless defined $user; |
|
|
80 | |
| 57 | my $effectiveUser = $db->getUser($effectiveUserName); |
81 | my $effectiveUser = $db->getUser($effectiveUserName); # checked |
|
|
82 | die "record for user $effectiveUserName (effective user) does not exist." |
|
|
83 | unless defined $effectiveUser; |
|
|
84 | |
|
|
85 | my $PermissionLevel = $db->getPermissionLevel($userName); # checked |
|
|
86 | die "permission level record for user $userName does not exist (but the user does? odd...)" |
|
|
87 | unless defined $PermissionLevel; |
| 58 | my $permissionLevel = $db->getPermissionLevel($userName)->permission(); |
88 | my $permissionLevel = $PermissionLevel->permission; |
| 59 | |
89 | |
| 60 | # obtain the merged set for $effectiveUser |
90 | # obtain the merged set for $effectiveUser |
| 61 | my $set = $db->getMergedSet($effectiveUserName, $setName); |
91 | my $set = $db->getMergedSet($effectiveUserName, $setName); # checked |
| 62 | |
92 | |
| 63 | # obtain the merged problem for $effectiveUser |
93 | # obtain the merged problem for $effectiveUser |
| 64 | my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); |
94 | my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked |
| 65 | |
95 | |
| 66 | my $editMode = $r->param("editMode"); |
96 | my $editMode = $r->param("editMode"); |
| 67 | |
97 | |
| 68 | if ($permissionLevel > 0 and defined $editMode) { |
98 | if ($permissionLevel > 0 and defined $editMode) { |
| 69 | # professors are allowed to fabricate sets and problems not |
99 | # professors are allowed to fabricate sets and problems not |
| … | |
… | |
| 72 | |
102 | |
| 73 | # if that is not yet defined obtain the global set, convert |
103 | # if that is not yet defined obtain the global set, convert |
| 74 | # it to a user set, and add fake user data |
104 | # it to a user set, and add fake user data |
| 75 | unless (defined $set) { |
105 | unless (defined $set) { |
| 76 | my $userSetClass = $db->{set_user}->{record}; |
106 | my $userSetClass = $db->{set_user}->{record}; |
| 77 | $set = global2user($userSetClass, |
107 | my $globalSet = $db->getGlobalSet($setName); # checked |
| 78 | $db->getGlobalSet($setName)); |
108 | # if the global set doesn't exist either, bail! |
| 79 | die "Set $setName does not exist" |
109 | die "Set $setName does not exist" |
| 80 | unless defined $set; |
110 | unless defined $set; |
|
|
111 | $set = global2user($userSetClass, $globalSet); |
| 81 | $set->psvn(0); |
112 | $set->psvn(0); |
| 82 | } |
113 | } |
| 83 | |
114 | |
| 84 | # if that is not yet defined obtain the global problem, |
115 | # if that is not yet defined obtain the global problem, |
| 85 | # convert it to a user problem, and add fake user data |
116 | # convert it to a user problem, and add fake user data |
| 86 | unless (defined $problem) { |
117 | unless (defined $problem) { |
| 87 | my $userProblemClass = $db->{problem_user}->{record}; |
118 | my $userProblemClass = $db->{problem_user}->{record}; |
| 88 | $problem = global2user($userProblemClass, |
|
|
| 89 | $db->getGlobalProblem($setName,$problemNumber)); |
119 | my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked |
|
|
120 | # if the global problem doesn't exist either, bail! |
| 90 | die "Problem $problemNumber in set $setName does not exist" |
121 | die "Problem $problemNumber in set $setName does not exist" |
| 91 | unless defined $problem; |
122 | unless defined $problem; |
|
|
123 | $problem = global2user($userProblemClass, $globalProblem); |
| 92 | $problem->user_id($effectiveUserName); |
124 | $problem->user_id($effectiveUserName); |
| 93 | $problem->problem_seed(0); |
125 | $problem->problem_seed(0); |
| 94 | $problem->status(0); |
126 | $problem->status(0); |
| 95 | $problem->attempted(0); |
127 | $problem->attempted(0); |
| 96 | $problem->last_answer(""); |
128 | $problem->last_answer(""); |
| … | |
… | |
| 160 | #$self->{edit_mode} = $editMode; |
192 | #$self->{edit_mode} = $editMode; |
| 161 | #$self->{current_problem_source} = (defined($override_problem_source) ) ? |
193 | #$self->{current_problem_source} = (defined($override_problem_source) ) ? |
| 162 | |
194 | |
| 163 | # coerce form fields into CGI::Vars format |
195 | # coerce form fields into CGI::Vars format |
| 164 | my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; |
196 | my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; |
|
|
197 | |
| 165 | |
198 | |
| 166 | $self->{displayMode} = $displayMode; |
199 | $self->{displayMode} = $displayMode; |
| 167 | $self->{redisplay} = $redisplay; |
200 | $self->{redisplay} = $redisplay; |
| 168 | $self->{submitAnswers} = $submitAnswers; |
201 | $self->{submitAnswers} = $submitAnswers; |
| 169 | $self->{checkAnswers} = $checkAnswers; |
202 | $self->{checkAnswers} = $checkAnswers; |
| … | |
… | |
| 205 | recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date, |
238 | recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date, |
| 206 | $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1), |
239 | $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1), |
| 207 | # attempts=num_correct+num_incorrect+1, as this happens before updating $problem |
240 | # attempts=num_correct+num_incorrect+1, as this happens before updating $problem |
| 208 | checkAnswers => canCheckAnswers($permissionLevel, $set->answer_date), |
241 | checkAnswers => canCheckAnswers($permissionLevel, $set->answer_date), |
| 209 | ); |
242 | ); |
|
|
243 | ######################################################### |
|
|
244 | # more complicated logic for showing check answer button: |
|
|
245 | ######################################################### |
|
|
246 | # checkAnswers button shows up after due date -- once a student can't record anymore |
|
|
247 | # checkAnswers button always shows up when an instructor or TA is acting |
|
|
248 | # as someone else (the $user and $effectiveUserName aren't the same). |
|
|
249 | $can{checkAnswers} = ($can{checkAnswers} && not $can{recordAnswers} ) || |
|
|
250 | ( defined($userName) and defined($effectiveUserName) and |
|
|
251 | ($userName ne $effectiveUserName) |
|
|
252 | ); |
|
|
253 | ######################################################### |
|
|
254 | # more complicated logif for showing "submit answer" button |
|
|
255 | ######################################################### |
|
|
256 | # We hide the submit answer button if someone is acting as a student |
|
|
257 | # This prevents errors where you accidently submit the answer for a student |
|
|
258 | # Not sure whether this a feature or a bug |
| 210 | |
259 | |
|
|
260 | $can{recordAnswers} = ($can{recordAnswers} and not |
|
|
261 | ( defined($userName) and defined($effectiveUserName) and |
|
|
262 | ($userName ne $effectiveUserName) |
|
|
263 | ) |
|
|
264 | ); |
| 211 | # final values for options |
265 | # final values for options |
| 212 | my %will; |
266 | my %will; |
| 213 | foreach (keys %must) { |
267 | foreach (keys %must) { |
| 214 | $will{$_} = $can{$_} && ($want{$_} || $must{$_}); |
268 | $will{$_} = $can{$_} && ($want{$_} || $must{$_}); |
| 215 | } |
269 | } |
| … | |
… | |
| 243 | ); |
297 | ); |
| 244 | |
298 | |
| 245 | $WeBWorK::timer0->continue("end pg processing") if $timer0_ON; |
299 | $WeBWorK::timer0->continue("end pg processing") if $timer0_ON; |
| 246 | ##### fix hint/solution options ##### |
300 | ##### fix hint/solution options ##### |
| 247 | |
301 | |
| 248 | $can{showHints} &&= $pg->{flags}->{hintExists}; |
302 | $can{showHints} &&= $pg->{flags}->{hintExists} |
|
|
303 | &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans}; |
| 249 | $can{showSolutions} &&= $pg->{flags}->{solutionExists}; |
304 | $can{showSolutions} &&= $pg->{flags}->{solutionExists}; |
| 250 | |
305 | |
| 251 | ##### store fields ##### |
306 | ##### store fields ##### |
| 252 | |
307 | |
| 253 | $self->{want} = \%want; |
308 | $self->{want} = \%want; |
| 254 | $self->{must} = \%must; |
309 | $self->{must} = \%must; |
| 255 | $self->{can} = \%can; |
310 | $self->{can} = \%can; |
| 256 | $self->{will} = \%will; |
311 | $self->{will} = \%will; |
| 257 | |
|
|
| 258 | $self->{pg} = $pg; |
312 | $self->{pg} = $pg; |
| 259 | } |
313 | } |
| 260 | |
314 | |
| 261 | #sub if_warnings($$) { |
315 | #sub if_warnings($$) { |
| 262 | # my ($self, $arg) = @_; |
316 | # my ($self, $arg) = @_; |
| … | |
… | |
| 318 | print CGI::strong("Problems"), CGI::br(); |
372 | print CGI::strong("Problems"), CGI::br(); |
| 319 | |
373 | |
| 320 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
374 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
| 321 | my @problemIDs = $db->listUserProblems($effectiveUser, $setName); |
375 | my @problemIDs = $db->listUserProblems($effectiveUser, $setName); |
| 322 | foreach my $problem (sort { $a <=> $b } @problemIDs) { |
376 | foreach my $problem (sort { $a <=> $b } @problemIDs) { |
| 323 | print CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?" |
377 | print ' '.CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?" |
| 324 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
378 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
| 325 | "Problem ".$problem), CGI::br(); |
379 | "Problem ".$problem), CGI::br(); |
| 326 | } |
380 | } |
| 327 | |
381 | |
| 328 | return ""; |
382 | return ""; |
| … | |
… | |
| 381 | unless $self->{isOpen}; |
435 | unless $self->{isOpen}; |
| 382 | |
436 | |
| 383 | # unpack some useful variables |
437 | # unpack some useful variables |
| 384 | my $r = $self->{r}; |
438 | my $r = $self->{r}; |
| 385 | my $db = $self->{db}; |
439 | my $db = $self->{db}; |
|
|
440 | my $ce = $self->{ce}; |
|
|
441 | my $root = $ce->{webworkURLs}->{root}; |
|
|
442 | my $courseName = $ce->{courseName}; |
| 386 | my $set = $self->{set}; |
443 | my $set = $self->{set}; |
| 387 | my $problem = $self->{problem}; |
444 | my $problem = $self->{problem}; |
| 388 | my $editMode = $self->{editMode}; |
445 | my $editMode = $self->{editMode}; |
| 389 | my $permissionLevel = $self->{permissionLevel}; |
446 | my $permissionLevel = $self->{permissionLevel}; |
| 390 | my $submitAnswers = $self->{submitAnswers}; |
447 | my $submitAnswers = $self->{submitAnswers}; |
| … | |
… | |
| 394 | my %can = %{ $self->{can} }; |
451 | my %can = %{ $self->{can} }; |
| 395 | my %must = %{ $self->{must} }; |
452 | my %must = %{ $self->{must} }; |
| 396 | my %will = %{ $self->{will} }; |
453 | my %will = %{ $self->{will} }; |
| 397 | my $pg = $self->{pg}; |
454 | my $pg = $self->{pg}; |
| 398 | |
455 | |
|
|
456 | |
|
|
457 | |
|
|
458 | #####create Editor link ##### |
|
|
459 | # print editor link if the user is an instructor AND the file is not in temporary editing mode |
|
|
460 | my $editorLinkMessage = ''; |
|
|
461 | # and ( (not defined($self->{editMode})) or $self->{editMode} eq 'savedFile') # FIXME is this needed? |
|
|
462 | if ($self->{permissionLevel}>=10 ) { |
|
|
463 | $editorLinkMessage = CGI::a({-href=>$ce->{webworkURLs}->{root}."/$courseName/instructor/pgProblemEditor/". |
|
|
464 | $set->set_id.'/'.$problem->problem_id.'?'.$self->url_authen_args},'Edit this problem'); |
|
|
465 | } |
| 399 | ##### translation errors? ##### |
466 | ##### translation errors? ##### |
| 400 | |
467 | |
| 401 | if ($pg->{flags}->{error_flag}) { |
468 | if ($pg->{flags}->{error_flag}) { |
| 402 | return $self->errorOutput($pg->{errors}, $pg->{body_text}); |
469 | return $self->errorOutput($pg->{errors}, $pg->{body_text}.CGI::p($editorLinkMessage)); |
| 403 | } |
470 | } |
| 404 | |
471 | |
| 405 | ##### answer processing ##### |
472 | ##### answer processing ##### |
| 406 | $WeBWorK::timer0->continue("begin answer processing") if $timer0_ON; |
473 | $WeBWorK::timer0->continue("begin answer processing") if $timer0_ON; |
| 407 | # if answers were submitted: |
474 | # if answers were submitted: |
| 408 | my $scoreRecordedMessage; |
475 | my $scoreRecordedMessage; |
| 409 | if ($submitAnswers) { |
476 | if ($submitAnswers) { |
| 410 | # get a "pure" (unmerged) UserProblem to modify |
477 | # get a "pure" (unmerged) UserProblem to modify |
| 411 | # this will be undefined if the problem has not been assigned to this user |
478 | # this will be undefined if the problem has not been assigned to this user |
| 412 | my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); |
479 | my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); # checked |
| 413 | if (defined $pureProblem) { |
480 | if (defined $pureProblem) { |
| 414 | # store answers in DB for sticky answers |
481 | # store answers in DB for sticky answers |
| 415 | my %answersToStore; |
482 | my %answersToStore; |
| 416 | my %answerHash = %{ $pg->{answers} }; |
483 | my %answerHash = %{ $pg->{answers} }; |
| 417 | $answersToStore{$_} = $answerHash{$_}->{original_student_ans} |
484 | $answersToStore{$_} = $self->{formFields}->{$_} #$answerHash{$_}->{original_student_ans} -- this may have been modified for fields with multiple values. Don't use it!! |
| 418 | foreach (keys %answerHash); |
485 | foreach (keys %answerHash); |
|
|
486 | # There may be some more answers to store -- one which are auxiliary entries to a primary answer. Evaluating |
|
|
487 | # matrices works in this way, only the first answer triggers an answer evaluator, the rest are just inputs |
|
|
488 | # however we need to store them. Fortunately they are still in the input form. |
|
|
489 | my @extra_answer_names = @{ $pg->{flags}->{KEPT_EXTRA_ANSWERS}}; |
|
|
490 | |
|
|
491 | $answersToStore{$_} = $self->{formFields}->{$_} foreach (@extra_answer_names); |
|
|
492 | |
|
|
493 | # Now let's encode these answers to store them -- append the extra answers to the end of answer entry order |
|
|
494 | my @answer_order = (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}, @extra_answer_names); |
| 419 | my $answerString = encodeAnswers(%answersToStore, |
495 | my $answerString = encodeAnswers(%answersToStore, |
| 420 | @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }); |
496 | @answer_order); |
| 421 | |
497 | |
| 422 | # store last answer to database |
498 | # store last answer to database |
| 423 | $problem->last_answer($answerString); |
499 | $problem->last_answer($answerString); |
| 424 | $pureProblem->last_answer($answerString); |
500 | $pureProblem->last_answer($answerString); |
| 425 | $db->putUserProblem($pureProblem); |
501 | $db->putUserProblem($pureProblem); |
| … | |
… | |
| 465 | $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you."; |
541 | $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you."; |
| 466 | } |
542 | } |
| 467 | } |
543 | } |
| 468 | |
544 | |
| 469 | # logging student answers |
545 | # logging student answers |
| 470 | my $pastAnswerLog = undef; |
546 | |
| 471 | if (defined( $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'} )) { |
547 | my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'}; |
| 472 | $pastAnswerLog = $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'}; |
548 | if ( defined($answer_log )) { |
| 473 | if ($submitAnswers and defined $pastAnswerLog) { |
549 | if ($submitAnswers ) { |
| 474 | my $answerString = ""; |
550 | my $answerString = ""; |
| 475 | my %answerHash = %{ $pg->{answers} }; |
551 | my %answerHash = %{ $pg->{answers} }; |
| 476 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
552 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
| 477 | foreach (sort keys %answerHash); |
553 | foreach (sort keys %answerHash); |
| 478 | $answerString = '' unless defined($answerString); # insure string is defined. |
554 | $answerString = '' unless defined($answerString); # insure string is defined. |
| 479 | writeLog($self->{ce}, "pastAnswerList", |
555 | writeCourseLog($self->{ce}, "answer_log", |
|
|
556 | join("", |
| 480 | '|'.$problem->user_id. |
557 | '|', $problem->user_id, |
| 481 | '|'.$problem->set_id. |
558 | '|', $problem->set_id, |
| 482 | '|'.$problem->problem_id.'|'."\t". |
559 | '|', $problem->problem_id, |
|
|
560 | '|',"\t", |
| 483 | time()."\t". |
561 | time(),"\t", |
| 484 | $answerString, |
562 | $answerString, |
|
|
563 | ), |
| 485 | ); |
564 | ); |
|
|
565 | |
| 486 | } |
566 | } |
| 487 | } |
567 | } |
| 488 | |
568 | |
| 489 | $WeBWorK::timer0->continue("end answer processing") if $timer0_ON; |
569 | $WeBWorK::timer0->continue("end answer processing") if $timer0_ON; |
| 490 | |
570 | |
| … | |
… | |
| 500 | print CGI::p(CGI::i("Problem saved to: ", $problem->source_file)); |
580 | print CGI::p(CGI::i("Problem saved to: ", $problem->source_file)); |
| 501 | } |
581 | } |
| 502 | } |
582 | } |
| 503 | |
583 | |
| 504 | # attempt summary |
584 | # attempt summary |
| 505 | if ($submitAnswers or $will{showCorrectAnswers}) { |
585 | #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. |
|
|
586 | # until after the due date |
|
|
587 | # do I need to check $wills{howCorrectAnswers} to make preflight work?? |
|
|
588 | if (($pg->{flags}->{showPartialCorrectAnswers}>= 0 and $submitAnswers) ) { |
| 506 | # print this if user submitted answers OR requested correct answers |
589 | # print this if user submitted answers OR requested correct answers |
|
|
590 | |
| 507 | print $self->attemptResults($pg, $submitAnswers, |
591 | print $self->attemptResults($pg, 1, |
| 508 | $will{showCorrectAnswers}, |
592 | $will{showCorrectAnswers}, |
| 509 | $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); |
593 | $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); |
| 510 | } elsif ($checkAnswers) { |
594 | } elsif ($checkAnswers) { |
| 511 | # print this if user previewed answers |
595 | # print this if user previewed answers |
|
|
596 | print "ANSWERS ONLY CHECKED -- ",CGI::br(),"ANSWERS NOT RECORDED", CGI::br(); |
| 512 | print $self->attemptResults($pg, 1, 0, 1, 1, 1); |
597 | print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1); |
| 513 | # show attempt answers |
598 | # show attempt answers |
| 514 | # don't show correct answers |
599 | # show correct answers if asked |
| 515 | # show attempt results (correctness) |
600 | # show attempt results (correctness) |
| 516 | # don't show attempt previews |
601 | # show attempt previews |
| 517 | } elsif ($previewAnswers) { |
602 | } elsif ($previewAnswers) { |
| 518 | # print this if user previewed answers |
603 | # print this if user previewed answers |
| 519 | print $self->attemptResults($pg, 1, 0, 0, 0, 1); |
604 | print "PREVIEW ONLY -- NOT RECORDED",CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1); |
| 520 | # show attempt answers |
605 | # show attempt answers |
| 521 | # don't show correct answers |
606 | # don't show correct answers |
| 522 | # don't show attempt results (correctness) |
607 | # don't show attempt results (correctness) |
| 523 | # show attempt previews |
608 | # show attempt previews |
| 524 | } |
609 | } |
| … | |
… | |
| 532 | CGI::startform("POST", $r->uri), |
617 | CGI::startform("POST", $r->uri), |
| 533 | $self->hidden_authen_fields, |
618 | $self->hidden_authen_fields, |
| 534 | CGI::p($pg->{body_text}), |
619 | CGI::p($pg->{body_text}), |
| 535 | CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})), |
620 | CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})), |
| 536 | CGI::p( |
621 | CGI::p( |
|
|
622 | ($can{showCorrectAnswers} |
|
|
623 | ? CGI::checkbox( |
|
|
624 | -name => "showCorrectAnswers", |
|
|
625 | -checked => $will{showCorrectAnswers}, |
|
|
626 | -label => "Show correct answers", |
|
|
627 | ) ." " |
|
|
628 | : "" ), |
|
|
629 | ($can{showHints} |
|
|
630 | ? '<div style="color:red">'. CGI::checkbox( |
|
|
631 | -name => "showHints", |
|
|
632 | -checked => $will{showHints}, |
|
|
633 | -label => "Show Hints", |
|
|
634 | ) . "</div> " |
|
|
635 | : " " ), |
|
|
636 | ($can{showSolutions} |
|
|
637 | ? CGI::checkbox( |
|
|
638 | -name => "showSolutions", |
|
|
639 | -checked => $will{showSolutions}, |
|
|
640 | -label => "Show Solutions", |
|
|
641 | ) . " " |
|
|
642 | : " " ),CGI::br(), |
|
|
643 | CGI::submit(-name=>"previewAnswers", |
|
|
644 | -label=>"Preview Answers"), |
| 537 | ($can{recordAnswers} |
645 | ($can{recordAnswers} |
| 538 | ? CGI::submit(-name=>"submitAnswers", |
646 | ? CGI::submit(-name=>"submitAnswers", |
| 539 | -label=>"Submit Answers") |
647 | -label=>"Submit Answers") |
| 540 | : ""), |
648 | : ""), |
| 541 | ($can{checkAnswers} |
649 | ( $can{checkAnswers} |
| 542 | ? CGI::submit(-name=>"checkAnswers", |
650 | ? CGI::submit(-name=>"checkAnswers", |
| 543 | -label=>"Check Answers") |
651 | -label=>"Check Answers") |
| 544 | : ""), |
652 | : ""), |
| 545 | CGI::submit(-name=>"previewAnswers", |
|
|
| 546 | -label=>"Preview Answers"), |
|
|
| 547 | ); |
653 | ); |
| 548 | print CGI::end_div(); |
654 | print CGI::end_div(); |
| 549 | |
655 | |
| 550 | print CGI::start_div({class=>"scoreSummary"}); |
656 | print CGI::start_div({class=>"scoreSummary"}); |
| 551 | |
657 | |
| … | |
… | |
| 583 | $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining." |
689 | $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining." |
| 584 | ); |
690 | ); |
| 585 | print CGI::end_div(); |
691 | print CGI::end_div(); |
| 586 | |
692 | |
| 587 | # save state for viewOptions |
693 | # save state for viewOptions |
| 588 | print CGI::hidden( |
694 | print CGI::hidden( |
| 589 | -name => "showOldAnswers", |
695 | -name => "showOldAnswers", |
| 590 | -value => $will{showOldAnswers} |
696 | -value => $will{showOldAnswers} |
| 591 | ), |
697 | ), |
|
|
698 | |
| 592 | CGI::hidden( |
699 | CGI::hidden( |
| 593 | -name => "showCorrectAnswers", |
|
|
| 594 | -value => $will{showCorrectAnswers} |
|
|
| 595 | ), |
|
|
| 596 | CGI::hidden( |
|
|
| 597 | -name => "showHints", |
|
|
| 598 | -value => $will{showHints}), |
|
|
| 599 | CGI::hidden( |
|
|
| 600 | -name => "showSolutions", |
|
|
| 601 | -value => $will{showSolutions}, |
|
|
| 602 | ), |
|
|
| 603 | CGI::hidden( |
|
|
| 604 | -name => "displayMode", |
700 | -name => "displayMode", |
| 605 | -value => $self->{displayMode} |
701 | -value => $self->{displayMode} |
| 606 | ); |
702 | ); |
| 607 | |
703 | print( CGI::hidden( |
|
|
704 | -name => 'editMode', |
|
|
705 | -value => $self->{editMode}, |
|
|
706 | ) |
|
|
707 | ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile'; |
|
|
708 | print( CGI::hidden( |
|
|
709 | -name => 'sourceFilePath', |
|
|
710 | -value => $self->{problem}->{source_file} |
|
|
711 | )) if defined($self->{problem}->{source_file}); |
|
|
712 | |
| 608 | # end of main form |
713 | # end of main form |
| 609 | print CGI::endform(); |
714 | print CGI::endform(); |
| 610 | |
715 | |
| 611 | # stuff we need below (pull these out at the beginning?) |
|
|
| 612 | my $ce = $self->{ce}; |
|
|
| 613 | my $root = $ce->{webworkURLs}->{root}; |
|
|
| 614 | my $courseName = $ce->{courseName}; |
|
|
| 615 | |
716 | |
| 616 | print CGI::start_div({class=>"problemFooter"}); |
717 | print CGI::start_div({class=>"problemFooter"}); |
| 617 | |
718 | |
| 618 | # arguments for answer inspection button |
719 | # arguments for answer inspection button |
| 619 | my $prof_url = $ce->{webworkURLs}->{oldProf}; |
720 | my $prof_url = $ce->{webworkURLs}->{oldProf}; |
|
|
721 | my $webworkURL = $ce->{webworkURLs}->{root}; |
| 620 | my $cgi_url = $prof_url; |
722 | my $cgi_url = $prof_url; |
| 621 | $cgi_url=~ s|/[^/]*$||; # clip profLogin.pl |
723 | $cgi_url=~ s|/[^/]*$||; # clip profLogin.pl |
| 622 | my $authen_args = $self->url_authen_args(); |
724 | my $authen_args = $self->url_authen_args(); |
| 623 | my $showPastAnswersURL = "$cgi_url/showPastAnswers.pl"; |
725 | my $showPastAnswersURL = "$webworkURL/$courseName/instructor/show_answers/"; |
| 624 | |
726 | |
| 625 | # print answer inspection button |
727 | # print answer inspection button |
| 626 | if ($self->{permissionLevel} > 0) { |
728 | if ($self->{permissionLevel} > 0) { |
| 627 | print "\n", |
729 | print "\n", |
| 628 | CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n", |
730 | CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n", |
| 629 | $self->hidden_authen_fields,"\n", |
731 | $self->hidden_authen_fields,"\n", |
| 630 | CGI::hidden(-name => 'course', -value=>$courseName), "\n", |
732 | CGI::hidden(-name => 'course', -value=>$courseName), "\n", |
| 631 | CGI::hidden(-name => 'probNum', -value=>$problem->problem_id), "\n", |
733 | CGI::hidden(-name => 'problemNumber', -value=>$problem->problem_id), "\n", |
| 632 | CGI::hidden(-name => 'setNum', -value=>$problem->set_id), "\n", |
734 | CGI::hidden(-name => 'setName', -value=>$problem->set_id), "\n", |
| 633 | CGI::hidden(-name => 'User', -value=>$problem->user_id), "\n", |
735 | CGI::hidden(-name => 'studentUser', -value=>$problem->user_id), "\n", |
| 634 | CGI::p( {-align=>"left"}, |
736 | CGI::p( {-align=>"left"}, |
| 635 | CGI::submit(-name => 'action', -value=>'Show Past Answers') |
737 | CGI::submit(-name => 'action', -value=>'Show Past Answers') |
| 636 | ), "\n", |
738 | ), "\n", |
| 637 | CGI::endform(); |
739 | CGI::endform(); |
| 638 | } |
740 | } |
| … | |
… | |
| 655 | CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", |
757 | CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", |
| 656 | CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", |
758 | CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", |
| 657 | CGI::hidden("showHints", $will{showHints}),"\n", |
759 | CGI::hidden("showHints", $will{showHints}),"\n", |
| 658 | CGI::hidden("showSolutions", $will{showSolutions}),"\n", |
760 | CGI::hidden("showSolutions", $will{showSolutions}),"\n", |
| 659 | CGI::p({-align=>"left"}, |
761 | CGI::p({-align=>"left"}, |
| 660 | CGI::submit(-name=>"feedbackForm", -label=>"Contact instructor") |
762 | CGI::submit(-name=>"feedbackForm", -label=>"Email instructor") |
| 661 | ), |
763 | ), |
| 662 | CGI::endform(),"\n"; |
764 | CGI::endform(),"\n"; |
| 663 | |
765 | |
| 664 | # FIXME print editor link |
766 | # FIXME print editor link |
| 665 | # print editor link if the user is an instructor AND the file is not in temporary editing mode |
767 | print $editorLinkMessage; #empty unless it is appropriate to have an editor link. |
| 666 | if ($self->{permissionLevel}>=10 and ( (not defined($self->{edit_mode})) or $self->{edit_mode} eq 'savedFile') ) { |
|
|
| 667 | print CGI::a({-href=>$ce->{webworkURLs}->{root}."/$courseName/instructor/pgProblemEditor/".$set->set_id. |
|
|
| 668 | '/'.$problem->problem_id.'?'.$self->url_authen_args},'Edit this problem'); |
|
|
| 669 | } |
|
|
| 670 | |
768 | |
| 671 | print CGI::end_div(); |
769 | print CGI::end_div(); |
| 672 | |
770 | |
| 673 | # warning output |
771 | # warning output |
| 674 | #if ($pg->{warnings} ne "") { |
772 | #if ($pg->{warnings} ne "") { |
| … | |
… | |
| 712 | my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; |
810 | my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; |
| 713 | |
811 | |
| 714 | my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview"; |
812 | my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview"; |
| 715 | my $imgGen = WeBWorK::PG::ImageGenerator->new( |
813 | my $imgGen = WeBWorK::PG::ImageGenerator->new( |
| 716 | tempDir => $ce->{webworkDirs}->{tmp}, |
814 | tempDir => $ce->{webworkDirs}->{tmp}, |
| 717 | dir => $ce->{courseDirs}->{html_temp}, |
|
|
| 718 | url => $ce->{courseURLs}->{html_temp}, |
|
|
| 719 | basename => $basename, |
|
|
| 720 | latex => $ce->{externalPrograms}->{latex}, |
815 | latex => $ce->{externalPrograms}->{latex}, |
| 721 | dvipng => $ce->{externalPrograms}->{dvipng}, |
816 | dvipng => $ce->{externalPrograms}->{dvipng}, |
|
|
817 | useCache => 1, |
|
|
818 | cacheDir => $ce->{webworkDirs}->{equationCache}, |
|
|
819 | cacheURL => $ce->{webworkURLs}->{equationCache}, |
|
|
820 | cacheDB => $ce->{webworkFiles}->{equationCacheDB}, |
| 722 | ); |
821 | ); |
| 723 | |
822 | |
| 724 | my $header; |
823 | my $header; |
| 725 | #$header .= CGI::th("Part"); |
824 | #$header .= CGI::th("Part"); |
| 726 | $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; |
825 | $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; |
| … | |
… | |
| 737 | ? $self->previewAnswer($answerResult, $imgGen) |
836 | ? $self->previewAnswer($answerResult, $imgGen) |
| 738 | : ""); |
837 | : ""); |
| 739 | my $correctAnswer = $answerResult->{correct_ans}; |
838 | my $correctAnswer = $answerResult->{correct_ans}; |
| 740 | my $answerScore = $answerResult->{score}; |
839 | my $answerScore = $answerResult->{score}; |
| 741 | my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; |
840 | my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; |
| 742 | |
841 | #FIXME --Can we be sure that $answerScore is an integer-- could the problem give partial credit? |
| 743 | $numCorrect += $answerScore > 0; |
842 | $numCorrect += $answerScore > 0; |
| 744 | my $resultString = $answerScore ? "correct" : "incorrect"; |
843 | my $resultString = $answerScore ? "correct" : "incorrect"; |
| 745 | |
844 | |
| 746 | # get rid of the goofy prefix on the answer names (supposedly, the format |
845 | # get rid of the goofy prefix on the answer names (supposedly, the format |
| 747 | # of the answer names is changeable. this only fixes it for "AnSwEr" |
846 | # of the answer names is changeable. this only fixes it for "AnSwEr" |
| 748 | $name =~ s/^AnSwEr//; |
847 | #$name =~ s/^AnSwEr//; |
| 749 | |
848 | |
| 750 | my $row; |
849 | my $row; |
| 751 | #$row .= CGI::td($name); |
850 | #$row .= CGI::td($name); |
| 752 | $row .= $showAttemptAnswers ? CGI::td(nbsp($studentAnswer)) : ""; |
851 | $row .= $showAttemptAnswers ? CGI::td(nbsp($studentAnswer)) : ""; |
| 753 | $row .= $showAttemptPreview ? CGI::td(nbsp($preview)) : ""; |
852 | $row .= $showAttemptPreview ? CGI::td(nbsp($preview)) : ""; |
| … | |
… | |
| 758 | } |
857 | } |
| 759 | |
858 | |
| 760 | # render equation images |
859 | # render equation images |
| 761 | $imgGen->render(refresh => 1); |
860 | $imgGen->render(refresh => 1); |
| 762 | |
861 | |
| 763 | my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; |
862 | # my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; |
| 764 | my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); |
863 | my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); |
|
|
864 | # FIXME -- I left the old code in in case we have to back out. |
| 765 | my $summary = "On this attempt, you answered $numCorrect out of " |
865 | # my $summary = "On this attempt, you answered $numCorrect out of " |
| 766 | . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; |
866 | # . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; |
| 767 | return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : ""); |
867 | my $summary = ""; |
|
|
868 | if (scalar @answerNames == 1) { |
|
|
869 | if ($numCorrect == scalar @answerNames) { |
|
|
870 | $summary .= "The above answer is correct."; |
|
|
871 | } else { |
|
|
872 | $summary .= "The above answer is NOT correct."; |
|
|
873 | } |
|
|
874 | } else { |
|
|
875 | if ($numCorrect == scalar @answerNames) { |
|
|
876 | $summary .= "All of the above answers are correct."; |
|
|
877 | } else { |
|
|
878 | $summary .= "At least one of the above answers is NOT correct."; |
|
|
879 | } |
|
|
880 | } |
|
|
881 | #FIXME there must be a better way to force refresh. |
|
|
882 | #my $refresh_warning = 'Hold down shift and click "refresh" or "reload" to update answer preview images.'; |
|
|
883 | #return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . |
|
|
884 | #CGI::div({style=>'color:red; font-size:10pt'},$refresh_warning) . |
|
|
885 | #($showSummary ? CGI::p({class=>'emphasis'},$summary) : ""); |
|
|
886 | # ... this has been fixed by equation caching. |
|
|
887 | return |
|
|
888 | CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) |
|
|
889 | . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : ""); |
| 768 | } |
890 | } |
| 769 | sub nbsp { |
891 | sub nbsp { |
| 770 | my $str = shift; |
892 | my $str = shift; |
| 771 | ($str eq '') ? ' ' : $str ; # returns non-breaking space for empty strings |
893 | ($str =~/\S/) ? $str : ' ' ; # returns non-breaking space for empty strings |
|
|
894 | # tricky cases: $str =0; |
|
|
895 | # $str is a complex number |
| 772 | } |
896 | } |
| 773 | sub viewOptions($) { |
897 | sub viewOptions($) { |
| 774 | my $self = shift; |
898 | my $self = shift; |
| 775 | my $displayMode = $self->{displayMode}; |
899 | my $displayMode = $self->{displayMode}; |
| 776 | my %must = %{ $self->{must} }; |
900 | my %must = %{ $self->{must} }; |
| … | |
… | |
| 783 | CGI::checkbox( |
907 | CGI::checkbox( |
| 784 | -name => "showOldAnswers", |
908 | -name => "showOldAnswers", |
| 785 | -checked => $will{showOldAnswers}, |
909 | -checked => $will{showOldAnswers}, |
| 786 | -label => "Saved answers", |
910 | -label => "Saved answers", |
| 787 | ), " ".CGI::br(); |
911 | ), " ".CGI::br(); |
| 788 | $can{showCorrectAnswers} and $optionLine .= join "", |
912 | |
| 789 | CGI::checkbox( |
|
|
| 790 | -name => "showCorrectAnswers", |
|
|
| 791 | -checked => $will{showCorrectAnswers}, |
|
|
| 792 | -label => "Correct answers", |
|
|
| 793 | ), " ".CGI::br(); |
|
|
| 794 | $can{showHints} and $optionLine .= join "", |
|
|
| 795 | CGI::checkbox( |
|
|
| 796 | -name => "showHints", |
|
|
| 797 | -checked => $will{showHints}, |
|
|
| 798 | -label => "Hints", |
|
|
| 799 | ), " ".CGI::br(); |
|
|
| 800 | $can{showSolutions} and $optionLine .= join "", |
|
|
| 801 | CGI::checkbox( |
|
|
| 802 | -name => "showSolutions", |
|
|
| 803 | -checked => $will{showSolutions}, |
|
|
| 804 | -label => "Solutions", |
|
|
| 805 | ), " ".CGI::br(); |
|
|
| 806 | $optionLine and $optionLine .= join "", CGI::br(); |
913 | $optionLine and $optionLine .= join "", CGI::br(); |
| 807 | |
914 | |
| 808 | return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"}, |
915 | return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"}, |
| 809 | "View equations as: ".CGI::br(), |
916 | "View equations as: ".CGI::br(), |
| 810 | CGI::radio_group( |
917 | CGI::radio_group( |