| … | |
… | |
| 9 | =head1 NAME |
9 | =head1 NAME |
| 10 | |
10 | |
| 11 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
11 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
| 12 | |
12 | |
| 13 | =cut |
13 | =cut |
| 14 | my $timer0_ON=0; # times pg translation phase |
14 | my $timer0_ON=1; # times pg translation phase |
| 15 | use strict; |
15 | use strict; |
| 16 | use warnings; |
16 | use warnings; |
| 17 | use CGI qw(); |
17 | use CGI qw(); |
| 18 | use File::Path qw(rmtree); |
18 | use File::Path qw(rmtree); |
| 19 | use WeBWorK::Form; |
19 | use WeBWorK::Form; |
| 20 | use WeBWorK::PG; |
20 | use WeBWorK::PG; |
|
|
21 | use WeBWorK::PG::ImageGenerator; |
| 21 | use WeBWorK::PG::IO; |
22 | use WeBWorK::PG::IO; |
| 22 | use WeBWorK::Utils qw(writeLog encodeAnswers decodeAnswers ref2string makeTempDirectory); |
23 | use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory); |
| 23 | use WeBWorK::DB::Utils qw(global2user user2global findDefaults); |
24 | use WeBWorK::DB::Utils qw(global2user user2global findDefaults); |
| 24 | use WeBWorK::Timing; |
25 | use WeBWorK::Timing; |
| 25 | |
26 | |
| 26 | ############################################################ |
27 | ############################################################ |
| 27 | # |
28 | # |
| … | |
… | |
| 213 | $will{$_} = $can{$_} && ($want{$_} || $must{$_}); |
214 | $will{$_} = $can{$_} && ($want{$_} || $must{$_}); |
| 214 | } |
215 | } |
| 215 | |
216 | |
| 216 | ##### sticky answers ##### |
217 | ##### sticky answers ##### |
| 217 | |
218 | |
| 218 | if (not $submitAnswers and $will{showOldAnswers}) { |
219 | if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) { |
| 219 | # do this only if new answers are NOT being submitted |
220 | # do this only if new answers are NOT being submitted |
| 220 | my %oldAnswers = decodeAnswers($problem->last_answer); |
221 | my %oldAnswers = decodeAnswers($problem->last_answer); |
| 221 | $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; |
222 | $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; |
| 222 | } |
223 | } |
| 223 | |
224 | |
| … | |
… | |
| 317 | print CGI::strong("Problems"), CGI::br(); |
318 | print CGI::strong("Problems"), CGI::br(); |
| 318 | |
319 | |
| 319 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
320 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
| 320 | my @problemIDs = $db->listUserProblems($effectiveUser, $setName); |
321 | my @problemIDs = $db->listUserProblems($effectiveUser, $setName); |
| 321 | foreach my $problem (sort { $a <=> $b } @problemIDs) { |
322 | foreach my $problem (sort { $a <=> $b } @problemIDs) { |
| 322 | print CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?" |
323 | print ' '.CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?" |
| 323 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
324 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
| 324 | "Problem ".$problem), CGI::br(); |
325 | "Problem ".$problem), CGI::br(); |
| 325 | } |
326 | } |
| 326 | |
327 | |
| 327 | return ""; |
328 | return ""; |
| … | |
… | |
| 464 | $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you."; |
465 | $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you."; |
| 465 | } |
466 | } |
| 466 | } |
467 | } |
| 467 | |
468 | |
| 468 | # logging student answers |
469 | # logging student answers |
| 469 | my $pastAnswerLog = undef; |
470 | |
| 470 | if (defined( $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'} )) { |
471 | my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'}; |
| 471 | $pastAnswerLog = $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'}; |
472 | if ( defined($answer_log )) { |
| 472 | if ($submitAnswers and defined $pastAnswerLog) { |
473 | if ($submitAnswers ) { |
| 473 | my $answerString = ""; |
474 | my $answerString = ""; |
| 474 | my %answerHash = %{ $pg->{answers} }; |
475 | my %answerHash = %{ $pg->{answers} }; |
| 475 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
476 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
| 476 | foreach (sort keys %answerHash); |
477 | foreach (sort keys %answerHash); |
| 477 | $answerString = '' unless defined($answerString); # insure string is defined. |
478 | $answerString = '' unless defined($answerString); # insure string is defined. |
| 478 | writeLog($self->{ce}, "pastAnswerList", |
479 | writeCourseLog($self->{ce}, "answer_log", |
|
|
480 | join("", |
| 479 | '|'.$problem->user_id. |
481 | '|', $problem->user_id, |
| 480 | '|'.$problem->set_id. |
482 | '|', $problem->set_id, |
| 481 | '|'.$problem->problem_id.'|'."\t". |
483 | '|', $problem->problem_id, |
|
|
484 | '|',"\t", |
| 482 | time()."\t". |
485 | time(),"\t", |
| 483 | $answerString, |
486 | $answerString, |
|
|
487 | ), |
| 484 | ); |
488 | ); |
|
|
489 | |
| 485 | } |
490 | } |
| 486 | } |
491 | } |
| 487 | |
492 | |
| 488 | $WeBWorK::timer0->continue("end answer processing") if $timer0_ON; |
493 | $WeBWorK::timer0->continue("end answer processing") if $timer0_ON; |
| 489 | |
494 | |
| … | |
… | |
| 702 | my $showAttemptAnswers = shift; |
707 | my $showAttemptAnswers = shift; |
| 703 | my $showCorrectAnswers = shift; |
708 | my $showCorrectAnswers = shift; |
| 704 | my $showAttemptResults = $showAttemptAnswers && shift; |
709 | my $showAttemptResults = $showAttemptAnswers && shift; |
| 705 | my $showSummary = shift; |
710 | my $showSummary = shift; |
| 706 | my $showAttemptPreview = shift || 0; |
711 | my $showAttemptPreview = shift || 0; |
|
|
712 | my $ce = $self->{ce}; |
| 707 | my $problemResult = $pg->{result}; # the overall result of the problem |
713 | my $problemResult = $pg->{result}; # the overall result of the problem |
| 708 | my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }; |
714 | my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }; |
| 709 | |
715 | |
| 710 | my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; |
716 | my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; |
|
|
717 | |
|
|
718 | my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview"; |
|
|
719 | my $imgGen = WeBWorK::PG::ImageGenerator->new( |
|
|
720 | tempDir => $ce->{webworkDirs}->{tmp}, |
|
|
721 | dir => $ce->{courseDirs}->{html_temp}, |
|
|
722 | url => $ce->{courseURLs}->{html_temp}, |
|
|
723 | basename => $basename, |
|
|
724 | latex => $ce->{externalPrograms}->{latex}, |
|
|
725 | dvipng => $ce->{externalPrograms}->{dvipng}, |
|
|
726 | ); |
| 711 | |
727 | |
| 712 | my $header; |
728 | my $header; |
| 713 | #$header .= CGI::th("Part"); |
729 | #$header .= CGI::th("Part"); |
| 714 | $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; |
730 | $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; |
| 715 | $header .= $showAttemptPreview ? CGI::th("Answer Preview") : ""; |
731 | $header .= $showAttemptPreview ? CGI::th("Answer Preview") : ""; |
| … | |
… | |
| 720 | my $numCorrect; |
736 | my $numCorrect; |
| 721 | foreach my $name (@answerNames) { |
737 | foreach my $name (@answerNames) { |
| 722 | my $answerResult = $pg->{answers}->{$name}; |
738 | my $answerResult = $pg->{answers}->{$name}; |
| 723 | my $studentAnswer = $answerResult->{student_ans}; # original_student_ans |
739 | my $studentAnswer = $answerResult->{student_ans}; # original_student_ans |
| 724 | my $preview = ($showAttemptPreview |
740 | my $preview = ($showAttemptPreview |
| 725 | ? $self->previewAnswer($answerResult) |
741 | ? $self->previewAnswer($answerResult, $imgGen) |
| 726 | : ""); |
742 | : ""); |
| 727 | my $correctAnswer = $answerResult->{correct_ans}; |
743 | my $correctAnswer = $answerResult->{correct_ans}; |
| 728 | my $answerScore = $answerResult->{score}; |
744 | my $answerScore = $answerResult->{score}; |
| 729 | my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; |
745 | my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; |
| 730 | |
746 | |
| 731 | $numCorrect += $answerScore > 0; |
747 | $numCorrect += $answerScore > 0; |
| … | |
… | |
| 743 | $row .= $showAttemptResults ? CGI::td(nbsp($resultString)) : ""; |
759 | $row .= $showAttemptResults ? CGI::td(nbsp($resultString)) : ""; |
| 744 | $row .= $answerMessage ? CGI::td(nbsp($answerMessage)) : ""; |
760 | $row .= $answerMessage ? CGI::td(nbsp($answerMessage)) : ""; |
| 745 | push @tableRows, $row; |
761 | push @tableRows, $row; |
| 746 | } |
762 | } |
| 747 | |
763 | |
|
|
764 | # render equation images |
|
|
765 | $imgGen->render(refresh => 1); |
|
|
766 | |
| 748 | my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; |
767 | my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; |
| 749 | my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); |
768 | my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); |
| 750 | my $summary = "On this attempt, you answered $numCorrect out of " |
769 | my $summary = "On this attempt, you answered $numCorrect out of " |
| 751 | . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; |
770 | . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; |
| 752 | return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p($summary) : ""); |
771 | return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : ""); |
| 753 | } |
772 | } |
| 754 | sub nbsp { |
773 | sub nbsp { |
| 755 | my $str = shift; |
774 | my $str = shift; |
| 756 | ($str) ? $str : ' '; # returns non-breaking space for empty strings |
775 | ($str =~/\S/) ? $str : ' ' ; # returns non-breaking space for empty strings |
|
|
776 | # tricky cases: $str =0; |
|
|
777 | # $str is a complex number |
| 757 | } |
778 | } |
| 758 | sub viewOptions($) { |
779 | sub viewOptions($) { |
| 759 | my $self = shift; |
780 | my $self = shift; |
| 760 | my $displayMode = $self->{displayMode}; |
781 | my $displayMode = $self->{displayMode}; |
| 761 | my %must = %{ $self->{must} }; |
782 | my %must = %{ $self->{must} }; |
| … | |
… | |
| 807 | CGI::submit(-name=>"redisplay", -label=>"Save Options"), |
828 | CGI::submit(-name=>"redisplay", -label=>"Save Options"), |
| 808 | ); |
829 | ); |
| 809 | } |
830 | } |
| 810 | |
831 | |
| 811 | sub previewAnswer($$) { |
832 | sub previewAnswer($$) { |
| 812 | my ($self, $answerResult) = @_; |
833 | my ($self, $answerResult, $imgGen) = @_; |
| 813 | my $ce = $self->{ce}; |
834 | my $ce = $self->{ce}; |
| 814 | my $effectiveUser = $self->{effectiveUser}; |
835 | my $effectiveUser = $self->{effectiveUser}; |
| 815 | my $set = $self->{set}; |
836 | my $set = $self->{set}; |
| 816 | my $problem = $self->{problem}; |
837 | my $problem = $self->{problem}; |
| 817 | my $displayMode = $self->{displayMode}; |
838 | my $displayMode = $self->{displayMode}; |
| … | |
… | |
| 838 | if ($?) { |
859 | if ($?) { |
| 839 | return "<b>[tth failed: $? $@]</b>"; |
860 | return "<b>[tth failed: $? $@]</b>"; |
| 840 | } |
861 | } |
| 841 | return $result; |
862 | return $result; |
| 842 | } elsif ($displayMode eq "images") { |
863 | } elsif ($displayMode eq "images") { |
| 843 | # how are we going to name this? |
864 | ## how are we going to name this? |
| 844 | my $targetPathCommon = "/m2i/" |
865 | #my $targetPathCommon = "/m2i/" |
| 845 | . $effectiveUser->user_id . "." |
866 | # . $effectiveUser->user_id . "." |
| 846 | . $set->set_id . "." |
867 | # . $set->set_id . "." |
| 847 | . $problem->problem_id . "." |
868 | # . $problem->problem_id . "." |
| 848 | . $answerResult->{ans_name} . ".png"; |
869 | # . $answerResult->{ans_name} . ".png"; |
| 849 | |
870 | # |
| 850 | # figure out where to put things |
871 | ## figure out where to put things |
| 851 | my $wd = makeTempDirectory($ce->{courseDirs}->{html_temp}, "webwork-dvipng"); |
872 | #my $wd = makeTempDirectory($ce->{courseDirs}->{html_temp}, "webwork-dvipng"); |
| 852 | my $latex = $ce->{externalPrograms}->{latex}; |
873 | #my $latex = $ce->{externalPrograms}->{latex}; |
| 853 | my $dvipng = $ce->{externalPrograms}->{dvipng}; |
874 | #my $dvipng = $ce->{externalPrograms}->{dvipng}; |
| 854 | my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon; |
875 | #my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon; |
| 855 | # should use surePathToTmpFile, but we have to |
876 | # # should use surePathToTmpFile, but we have to |
| 856 | # isolate it from the problem enivronment first |
877 | # # isolate it from the problem enivronment first |
| 857 | my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon; |
878 | #my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon; |
| 858 | |
879 | # |
| 859 | # call dvipng to generate a preview |
880 | ## call dvipng to generate a preview |
| 860 | dvipng($wd, $latex, $dvipng, $tex, $targetPath); |
881 | #dvipng($wd, $latex, $dvipng, $tex, $targetPath); |
| 861 | rmtree($wd, 0, 0); |
882 | #rmtree($wd, 0, 0); |
| 862 | if (-e $targetPath) { |
883 | #if (-e $targetPath) { |
| 863 | return "<img src=\"$targetURL\" alt=\"$tex\" />"; |
884 | # return "<img src=\"$targetURL\" alt=\"$tex\" />"; |
| 864 | } else { |
885 | #} else { |
| 865 | return "<b>[math2img failed]</b>"; |
886 | # return "<b>[math2img failed]</b>"; |
| 866 | } |
887 | #} |
|
|
888 | $imgGen->add($answerResult->{preview_latex_string}); |
|
|
889 | |
| 867 | } |
890 | } |
| 868 | } |
891 | } |
| 869 | |
892 | |
| 870 | ##### logging subroutine #### |
893 | ##### logging subroutine #### |
| 871 | |
894 | |