[system] / branches / gage_dev / webwork2 / lib / WeBWorK / ContentGenerator / Problem.pm Repository:
ViewVC logotype

View of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 7040 - (download) (as text) (annotate)
Fri Sep 9 22:43:26 2011 UTC (21 months, 1 week ago) by gage
File size: 53783 byte(s)
fixed problem in Show solutions 

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9