[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 6985 - (download) (as text) (annotate)
Thu Jul 21 01:51:23 2011 UTC (22 months ago) by gage
File size: 53055 byte(s)
bring gage_dev and ghe3_dev into sync


    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) ? "visible" : "hidden";
  599     my $visiblityStateText = ($set->visible) ? "visible to students." : "hidden from students.";
  600     $self->addmessage(CGI::span("This set is " . 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 options {
  756   my ($self) = @_;
  757   #warn "doing options in Problem";
  758 
  759   # don't show options if we don't have anything to show
  760   return "" if $self->{invalidSet} or $self->{invalidProblem};
  761 
  762   my $displayMode = $self->{displayMode};
  763   my %can = %{ $self->{can} };
  764 
  765   my @options_to_show = "displayMode";
  766   push @options_to_show, "showOldAnswers" if $can{showOldAnswers};
  767   push @options_to_show, "showHints" if $can{showHints};
  768   push @options_to_show, "showSolutions" if $can{showSolutions};
  769 
  770   return $self->optionsMacro(
  771     options_to_show => \@options_to_show,
  772     extra_params => ["editMode", "sourceFilePath"],
  773   );
  774 }
  775 
  776 sub siblings {
  777   my ($self) = @_;
  778   my $r = $self->r;
  779   my $db = $r->db;
  780   my $urlpath = $r->urlpath;
  781 
  782   # can't show sibling problems if the set is invalid
  783   return "" if $self->{invalidSet};
  784 
  785   my $courseID = $urlpath->arg("courseID");
  786   my $setID = $self->{set}->set_id;
  787   my $eUserID = $r->param("effectiveUser");
  788   my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID);
  789 
  790   print CGI::start_div({class=>"info-box", id=>"fisheye"});
  791   print CGI::h2($r->maketext("Problems"));
  792   #print CGI::start_ul({class=>"LinksMenu"});
  793   #print CGI::start_li();
  794   #print CGI::span({style=>"font-size:larger"}, "Problems");
  795   print CGI::start_ul();
  796 
  797   foreach my $problemID (@problemIDs) {
  798     my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", $r,
  799       courseID => $courseID, setID => $setID, problemID => $problemID);
  800     print CGI::li(CGI::a( {href=>$self->systemLink($problemPage,
  801                           params=>{  displayMode => $self->{displayMode},
  802                                  showOldAnswers => $self->{will}->{showOldAnswers}
  803                               })},  $r->maketext("Problem [_1]",$problemID))
  804      );
  805   }
  806 
  807   print CGI::end_ul();
  808   #print CGI::end_li();
  809   #print CGI::end_ul();
  810   print CGI::end_div();
  811 
  812   return "";
  813 }
  814 
  815 sub nav {
  816   my ($self, $args) = @_;
  817   my $r = $self->r;
  818   my $db = $r->db;
  819   my $urlpath = $r->urlpath;
  820 
  821   return "" if ( $self->{invalidSet} );
  822 
  823   my $courseID = $urlpath->arg("courseID");
  824   my $setID = $self->{set}->set_id if !($self->{invalidSet});
  825   my $problemID = $self->{problem}->problem_id if !($self->{invalidProblem});
  826   my $eUserID = $r->param("effectiveUser");
  827 
  828   my ($prevID, $nextID);
  829 
  830   if (!$self->{invalidProblem}) {
  831     my @problemIDs = $db->listUserProblems($eUserID, $setID);
  832     foreach my $id (@problemIDs) {
  833       $prevID = $id if $id < $problemID
  834         and (not defined $prevID or $id > $prevID);
  835       $nextID = $id if $id > $problemID
  836         and (not defined $nextID or $id < $nextID);
  837     }
  838   }
  839 
  840   my @links;
  841 
  842   if ($prevID) {
  843     my $prevPage = $urlpath->newFromModule(__PACKAGE__, $r,
  844       courseID => $courseID, setID => $setID, problemID => $prevID);
  845     push @links, $r->maketext("Previous Problem"), $r->location . $prevPage->path, $r->maketext("navPrev");
  846   } else {
  847     push @links, $r->maketext("Previous Problem"), "", $r->maketext("navPrevGrey");
  848   }
  849 
  850   if (defined($setID) && $setID ne 'Undefined_Set') {
  851     push @links, $r->maketext("Problem List"), $r->location . $urlpath->parent->path, $r->maketext("navProbList");
  852   } else {
  853     push @links, $r->maketext("Problem List"), "", $r->maketext("navProbListGrey");
  854   }
  855 
  856   if ($nextID) {
  857     my $nextPage = $urlpath->newFromModule(__PACKAGE__, $r,
  858       courseID => $courseID, setID => $setID, problemID => $nextID);
  859     push @links, $r->maketext("Next Problem"), $r->location . $nextPage->path, $r->maketext("navNext");
  860   } else {
  861     push @links, $r->maketext("Next Problem"), "", $r->maketext("navNextGrey");
  862   }
  863 
  864   my $tail = "";
  865 
  866   $tail .= "&displayMode=".$self->{displayMode} if defined $self->{displayMode};
  867   $tail .= "&showOldAnswers=".$self->{will}->{showOldAnswers}
  868     if defined $self->{will}->{showOldAnswers};
  869   return $self->navMacro($args, $tail, @links);
  870 }
  871 
  872 sub title {
  873   my ($self) = @_;
  874   my $r = $self->r;
  875   # using the url arguments won't break if the set/problem are invalid
  876   my $setID = WeBWorK::ContentGenerator::underscore2nbsp($self->r->urlpath->arg("setID"));
  877   my $problemID = $self->r->urlpath->arg("problemID");
  878 
  879   return $r->maketext("[_1]: Problem [_2]",$setID, $problemID);
  880 }
  881 
  882 
  883 # now altered to outsource most output operations to the template, main functions now are simply error checking and answer processing - ghe3
  884 sub body {
  885   my $self = shift;
  886   my $set = $self->{set};
  887   my $problem = $self->{problem};
  888   my $pg = $self->{pg};
  889 
  890   my $valid = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::check_invalid($self);
  891   unless($valid eq "valid"){
  892     return $valid;
  893   }
  894 
  895   # my $editorLink = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::process_editorLink($self);
  896   # if($editorLink eq "permission_error"){
  897     # return "";
  898   # }
  899 
  900   ##### answer processing #####
  901   debug("begin answer processing");
  902   # if answers were submitted:
  903   my $scoreRecordedMessage = WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::process_and_log_answer($self);
  904   debug("end answer processing");
  905 
  906   ##### javaScripts #############
  907   # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_JS($self);
  908 
  909   ##### output #####
  910   # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_summary($self);
  911 
  912   ###########################
  913   # print style sheet for correct and incorrect answers
  914   ###########################
  915 
  916   # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_CSS($self);
  917 
  918   ###########################
  919   # main form
  920   ###########################
  921 
  922   # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_main_form($self,$editorLink);
  923 
  924   # WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::output_footer($self);
  925 
  926   # debugging stuff
  927   if (0) {
  928     print
  929       CGI::hr(),
  930       CGI::h2("debugging information"),
  931       CGI::h3("form fields"),
  932       ref2string($self->{formFields}),
  933       CGI::h3("user object"),
  934       ref2string($self->{user}),
  935       CGI::h3("set object"),
  936       ref2string($set),
  937       CGI::h3("problem object"),
  938       ref2string($problem),
  939       CGI::h3("PG object"),
  940       ref2string($pg, {'WeBWorK::PG::Translator' => 1});
  941   }
  942   debug("leaving body of Problem.pm");
  943   return "";
  944 }
  945 
  946 # output_form_start subroutine
  947 
  948 # prints out the beginning of the main form, and the necessary hidden authentication fields
  949 
  950 sub output_form_start{
  951   my $self = shift;
  952   my $r = $self->r;
  953   print CGI::start_form(-method=>"POST", -action=> $r->uri,-name=>"problemMainForm", onsubmit=>"submitAction()");
  954   print $self->hidden_authen_fields;
  955   return "";
  956 }
  957 
  958 # output_problem_body subroutine
  959 
  960 # prints out the body of the current problem
  961 
  962 sub output_problem_body{
  963   my $self = shift;
  964   my $pg = $self->{pg};
  965 
  966   print "\n";
  967   print CGI::p($pg->{body_text});
  968   return "";
  969 }
  970 
  971 # output_message subroutine
  972 
  973 # prints out a message about the problem
  974 
  975 sub output_message{
  976   my $self = shift;
  977   my $pg = $self->{pg};
  978 
  979   print CGI::p(CGI::b("Note: "). CGI::i($pg->{result}->{msg})) if $pg->{result}->{msg};
  980   return "";
  981 }
  982 
  983 # output_editorLink subroutine
  984 
  985 # processes and prints out the correct link to the editor of the current problem
  986 
  987 sub output_editorLink{
  988 
  989   my $self = shift;
  990 
  991   my $set = $self->{set};
  992   my $problem = $self->{problem};
  993   my $pg = $self->{pg};
  994 
  995   my $r = $self->r;
  996 
  997   my $authz = $r->authz;
  998   my $urlpath = $r->urlpath;
  999   my $user = $r->param('user');
 1000 
 1001   my $courseName = $urlpath->arg("courseID");
 1002 
 1003   # FIXME: move editor link to top, next to problem number.
 1004   # format as "[edit]" like we're doing with course info file, etc.
 1005   # add edit link for set as well.
 1006   my $editorLink = "";
 1007   # if we are here without a real homework set, carry that through
 1008   my $forced_field = [];
 1009   $forced_field = ['sourceFilePath' =>  $r->param("sourceFilePath")] if
 1010     ($set->set_id eq 'Undefined_Set');
 1011   if ($authz->hasPermissions($user, "modify_problem_sets")) {
 1012     my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", $r,
 1013       courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id);
 1014     my $editorURL = $self->systemLink($editorPage, params=>$forced_field);
 1015     $editorLink = CGI::p(CGI::a({href=>$editorURL,target =>'WW_Editor'}, "Edit this problem"));
 1016   }
 1017 
 1018   ##### translation errors? #####
 1019 
 1020   if ($pg->{flags}->{error_flag}) {
 1021     if ($authz->hasPermissions($user, "view_problem_debugging_info")) {
 1022       print $self->errorOutput($pg->{errors}, $pg->{body_text});
 1023     } else {
 1024       print $self->errorOutput($pg->{errors}, $r->maketext("You do not have permission to view the details of this error."));
 1025     }
 1026     print "";
 1027   }
 1028   else{
 1029     print $editorLink;
 1030   }
 1031   return "";
 1032 }
 1033 
 1034 # output_checkboxes subroutine
 1035 
 1036 # prints out the checkbox input elements that are available for the current problem
 1037 
 1038 sub output_checkboxes{
 1039   my $self = shift;
 1040   my %can = %{ $self->{can} };
 1041   my %will = %{ $self->{will} };
 1042 
 1043   if ($can{showCorrectAnswers}) {
 1044     print WeBWorK::CGI_labeled_input(
 1045       -type  => "checkbox",
 1046       -id    => "showCorrectAnswers_id",
 1047       -label_text => "Show correct answers",
 1048       -input_attr => $will{showCorrectAnswers} ?
 1049       {
 1050         -name    => "showCorrectAnswers",
 1051         -checked => "checked",
 1052         -value   => 1,
 1053       }
 1054       :
 1055       {
 1056         -name    => "showCorrectAnswers",
 1057         -value   => 1,
 1058       }
 1059     );
 1060   }
 1061   if ($can{showHints}) {
 1062     print CGI::div({style=>"color:red"},
 1063       WeBWorK::CGI_labeled_input(
 1064         -type  => "checkbox",
 1065         -id    => "showHints_id",
 1066         -label_text => "Show Hints",
 1067         -input_attr => $will{showHints} ?
 1068         {
 1069           -name    => "showHints",
 1070           -checked => "checked",
 1071           -value   => 1,
 1072         }
 1073         :
 1074         {
 1075           -name    => "showCorrectAnswers",
 1076           -value   => 1,
 1077         }
 1078       )
 1079     );
 1080   }
 1081   if ($can{showSolutions}) {
 1082     print WeBWorK::CGI_labeled_input(
 1083       -type  => "checkbox",
 1084       -id    => "showSolutions_id",
 1085       -label_text => "Show Solutions",
 1086       -input_attr => $will{showSolutions} ?
 1087       {
 1088         -name    => "showSolutions",
 1089         -checked => "checked",
 1090         -value   => 1,
 1091       }
 1092       :
 1093       {
 1094         -name    => "showCorrectAnswers",
 1095         -value   => 1,
 1096       }
 1097     );
 1098   }
 1099 
 1100   if ($can{showCorrectAnswers} or $can{showHints} or $can{showSolutions}) {
 1101     print CGI::br();
 1102   }
 1103 
 1104   return "";
 1105 }
 1106 
 1107 # output_submit_buttons
 1108 
 1109 # prints out the submit button input elements that are available for the current problem
 1110 
 1111 sub output_submit_buttons{
 1112   my $self = shift;
 1113   my $r = $self->r;
 1114   my %can = %{ $self->{can} };
 1115 
 1116   my $user = $r->param('user');
 1117   my $effectiveUser = $r->param('effectiveUser');
 1118 
 1119   print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"previewAnswers_id", -input_attr=>{-name=>"previewAnswers", -value=>"Preview Answers"});
 1120   if ($can{checkAnswers}) {
 1121     print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"checkAnswers_id", -input_attr=>{-name=>"checkAnswers", -value=>"Check Answers"});
 1122   }
 1123   if ($can{getSubmitButton}) {
 1124     if ($user ne $effectiveUser) {
 1125       # if acting as a student, make it clear that answer submissions will
 1126       # apply to the student's records, not the professor's.
 1127       print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"submitAnswers_id", -input_attr=>{-name=>$r->maketext("submitAnswers"), -value=>"Submit Answers for $effectiveUser"});
 1128     } else {
 1129       #print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers", -onclick=>"alert('submit button clicked')");
 1130       print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"submitAnswers_id", -input_attr=>{-name=>"submitAnswers", -label=>$r->maketext("Submit answers"), -onclick=>""});
 1131       # FIXME  for unknown reasons the -onclick label seems to have to be there in order to allow the forms onsubmit to trigger
 1132       # WTF???
 1133     }
 1134   }
 1135 
 1136   return "";
 1137 }
 1138 
 1139 # output_score_summary subroutine
 1140 
 1141 # prints out a summary of the student's current progress and status on the current problem
 1142 
 1143 sub output_score_summary{
 1144   my $self = shift;
 1145   my $r = $self->shift;
 1146   my $problem = $self->{problem};
 1147   my $set = $self->{set};
 1148   my $pg = $self->{pg};
 1149   my $scoreRecordedMessage = "";
 1150   unless(defined $self->{scoreRecordedMessage}){
 1151     $scoreRecordedMessage = $self->{scoreRecordedMessage};
 1152   }
 1153   my $submitAnswers = $self->{submitAnswers};
 1154 
 1155   # score summary
 1156   my $attempts = $problem->num_correct + $problem->num_incorrect;
 1157   #my $attemptsNoun = $attempts != 1 ? $r->maketext("times") : $r->maketext("time");
 1158   my $problem_status    = $problem->status || 0;
 1159   my $lastScore = sprintf("%.0f%%", $problem_status * 100); # Round to whole number
 1160   #my ($attemptsLeft, $attemptsLeftNoun);
 1161   my $attemptsLeft = $problem->max_attempts - $attempts;
 1162 # if ($problem->max_attempts == -1) {
 1163 #   # unlimited attempts
 1164 #   $attemptsLeft = $r->maketext("unlimited");
 1165 #   $attemptsLeftNoun = $r->maketext("attempts");
 1166 # } else {
 1167 #   $attemptsLeft = $problem->max_attempts - $attempts;
 1168 #   $attemptsLeftNoun = $attemptsLeft == 1 ? $r->maketext("attempt") : $r->maketext("attempts");
 1169 # }
 1170 
 1171   my $setClosed = 0;
 1172   my $setClosedMessage;
 1173   if (before($set->open_date) or after($set->due_date)) {
 1174     $setClosed = 1;
 1175     if (before($set->open_date)) {
 1176       $setClosedMessage = $r->maketext("This homework set is not yet open.");
 1177     } elsif (after($set->due_date)) {
 1178       $setClosedMessage = $r->maketext("This homework set is closed.");
 1179     }
 1180   }
 1181   #if (before($set->open_date) or after($set->due_date)) {
 1182   # $setClosed = 1;
 1183   # $setClosedMessage = "This homework set is closed.";
 1184   # if ($authz->hasPermissions($user, "view_answers")) {
 1185   #   $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded.";
 1186   # } else {
 1187   #   $setClosedMessage .= " Additional attempts will not be recorded.";
 1188   # }
 1189   #}
 1190   unless (defined( $pg->{state}->{state_summary_msg}) and $pg->{state}->{state_summary_msg}=~/\S/) {
 1191     my $notCountedMessage = ($problem->value) ? "" : $r->maketext("(This problem will not count towards your grade.)");
 1192     print CGI::p(join("",
 1193       $submitAnswers ? $scoreRecordedMessage . CGI::br() : "",
 1194       $r->maketext("You have attempted this problem [quant,_1,time,times].",$attempts), CGI::br(),
 1195       $submitAnswers ? $r->maketext("You received a score of [_1] for this attempt.",sprintf("%.0f%%", $pg->{result}->{score} * 100)) . CGI::br():'',
 1196       $problem->attempted
 1197         ? $r->maketext("Your overall recorded score is [_1].  [_2]",$lastScore,$notCountedMessage) . CGI::br()
 1198         : "",
 1199 #     $setClosed ? $setClosedMessage : $r->maketext("You have [_1] [_2] remaining.",$attemptsLeft,$attemptsLeftNoun)
 1200       $setClosed ? $setClosedMessage : $r->maketext("You have [negquant,_1,unlimited attempts,attempt,attempts] remaining.",$attemptsLeft)
 1201     ));
 1202   }else {
 1203     print CGI::p($pg->{state}->{state_summary_msg});
 1204   }
 1205 
 1206   return "";
 1207 }
 1208 
 1209 # output_misc subroutine
 1210 
 1211 # prints out other necessary elements
 1212 
 1213 sub output_misc{
 1214 
 1215   my $self = shift;
 1216   my $r = $self->r;
 1217   my $ce = $r->ce;
 1218   my $db = $r->db;
 1219   my $pg = $self->{pg};
 1220   my %will = %{ $self->{will} };
 1221   my $user = $r->param('user');
 1222 
 1223   print CGI::start_div();
 1224 
 1225   my $pgdebug = join(CGI::br(), @{$pg->{pgcore}->{flags}->{DEBUG_messages}} );
 1226   my $pgwarning = join(CGI::br(), @{$pg->{pgcore}->{flags}->{WARNING_messages}} );
 1227   my $pginternalerrors = join(CGI::br(),  @{$pg->{pgcore}->get_internal_debug_messages}   );
 1228   my $pgerrordiv = $pgdebug||$pgwarning||$pginternalerrors;  # is 1 if any of these are non-empty
 1229 
 1230   print CGI::p({style=>"color:red;"}, "Checking additional error messages") if $pgerrordiv  ;
 1231   print CGI::p("pg debug<br/> $pgdebug"                   ) if $pgdebug ;
 1232   print CGI::p("pg warning<br/>$pgwarning"                ) if $pgwarning ;
 1233   print CGI::p("pg internal errors<br/> $pginternalerrors") if $pginternalerrors;
 1234   print CGI::end_div()                                      if $pgerrordiv ;
 1235 
 1236   # save state for viewOptions
 1237   print  CGI::hidden(
 1238          -name  => "showOldAnswers",
 1239          -value => $will{showOldAnswers}
 1240        ),
 1241 
 1242        CGI::hidden(
 1243          -name  => "displayMode",
 1244          -value => $self->{displayMode}
 1245        );
 1246   print( CGI::hidden(
 1247          -name    => 'editMode',
 1248          -value   => $self->{editMode},
 1249        )
 1250   ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile';
 1251 
 1252   # this is a security risk -- students can use this to find the source code for the problem
 1253 
 1254   my $permissionLevel = $db->getPermissionLevel($user)->permission;
 1255   my $professorPermissionLevel = $ce->{userRoles}->{professor};
 1256   print( CGI::hidden(
 1257           -name   => 'sourceFilePath',
 1258           -value  =>  $self->{problem}->{source_file}
 1259   ))  if defined($self->{problem}->{source_file}) and $permissionLevel>= $professorPermissionLevel; # only allow this for professors
 1260 
 1261   print( CGI::hidden(
 1262           -name   => 'problemSeed',
 1263           -value  =>  $r->param("problemSeed")
 1264   ))  if defined($r->param("problemSeed")) and $permissionLevel>= $professorPermissionLevel; # only allow this for professors
 1265 
 1266   return "";
 1267 }
 1268 
 1269 # output_summary subroutine
 1270 
 1271 # prints out the summary of the questions that the student has answered for the current problem, along with available information about correctness
 1272 
 1273 sub output_summary{
 1274 
 1275   my $self = shift;
 1276 
 1277   my $editMode = $self->{editMode};
 1278   my $problem = $self->{problem};
 1279   my $pg = $self->{pg};
 1280   my $submitAnswers = $self->{submitAnswers};
 1281   my %will = %{ $self->{will} };
 1282   my $checkAnswers = $self->{checkAnswers};
 1283   my $previewAnswers = $self->{previewAnswers};
 1284 
 1285   my $r = $self->r;
 1286 
 1287   my $authz = $r->authz;
 1288   my $user = $r->param('user');
 1289 
 1290   # attempt summary
 1291   #FIXME -- the following is a kludge:  if showPartialCorrectAnswers is negative don't show anything.
 1292   # until after the due date
 1293   # do I need to check $will{showCorrectAnswers} to make preflight work??
 1294   if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) {
 1295     # print this if user submitted answers OR requested correct answers
 1296 
 1297     print $self->attemptResults($pg, 1,
 1298       $will{showCorrectAnswers},
 1299       $pg->{flags}->{showPartialCorrectAnswers}, 1, 1);
 1300   } elsif ($checkAnswers) {
 1301     # print this if user previewed answers
 1302     print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED"), CGI::br();
 1303     print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1);
 1304       # show attempt answers
 1305       # show correct answers if asked
 1306       # show attempt results (correctness)
 1307       # show attempt previews
 1308   } elsif ($previewAnswers) {
 1309     # print this if user previewed answers
 1310     print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- ANSWERS NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1);
 1311       # show attempt answers
 1312       # don't show correct answers
 1313       # don't show attempt results (correctness)
 1314       # show attempt previews
 1315   }
 1316 
 1317   return "";
 1318 }
 1319 
 1320 # output_custom_edit_message
 1321 
 1322 # prints out a custom edit message
 1323 
 1324 sub output_custom_edit_message{
 1325   my $self = shift;
 1326   my $r = $self->r;
 1327   my $authz = $r->authz;
 1328   my $user = $r->param('user');
 1329   my $editMode = $self->{editMode};
 1330   my $problem = $self->{problem};
 1331 
 1332   # custom message for editor
 1333   if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) {
 1334     if ($editMode eq "temporaryFile") {
 1335       print CGI::p(CGI::div({class=>'temporaryFile'}, "Viewing temporary file: ", $problem->source_file));
 1336     } elsif ($editMode eq "savedFile") {
 1337       # taken care of in the initialization phase
 1338     }
 1339   }
 1340 
 1341   return "";
 1342 }
 1343 
 1344 # output_JS subroutine
 1345 
 1346 # prints out the wz_tooltip.js script for the current site.
 1347 
 1348 sub output_wztooltip_JS{
 1349 
 1350   my $self = shift;
 1351   my $r = $self->r;
 1352   my $ce = $r->ce;
 1353 
 1354   my $site_url = $ce->{webworkURLs}->{htdocs};
 1355 
 1356   print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/wz_tooltip.js"}), CGI::end_script();
 1357   return "";
 1358 }
 1359 
 1360 # output_CSS subroutine
 1361 
 1362 # 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).
 1363 
 1364 sub output_CSS{
 1365 
 1366   my $self = shift;
 1367   my $r = $self->r;
 1368   my $ce = $r->ce;
 1369   my $pg = $self->{pg};
 1370 
 1371   # always show colors for checkAnswers
 1372   # show colors for submit answer if
 1373   if (($self->{checkAnswers}) or ($self->{submitAnswers} and $pg->{flags}->{showPartialCorrectAnswers}) ) {
 1374     print CGI::start_style({type=>"text/css"});
 1375     print '#'.join(', #', @{ $self->{correct_ids} }), $ce->{pg}{options}{correct_answer}   if ref( $self->{correct_ids}  )=~/ARRAY/;   #correct  green
 1376     print '#'.join(', #', @{ $self->{incorrect_ids} }), $ce->{pg}{options}{incorrect_answer} if ref( $self->{incorrect_ids})=~/ARRAY/; #incorrect  reddish
 1377     print CGI::end_style();
 1378   }
 1379 
 1380   return "";
 1381 }
 1382 
 1383 # output_past_answer_button
 1384 
 1385 # prints out the "Show Past Answers" button
 1386 
 1387 sub output_past_answer_button{
 1388   my $self = shift;
 1389   my $r = $self->r;
 1390   my $problem = $self->{problem};
 1391 
 1392   my $authz = $r->authz;
 1393   my $urlpath = $r->urlpath;
 1394   my $user = $r->param('user');
 1395 
 1396   my $courseName = $urlpath->arg("courseID");
 1397 
 1398   my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers", $r,
 1399     courseID => $courseName);
 1400   my $showPastAnswersURL = $self->systemLink($pastAnswersPage, authen => 0); # no authen info for form action
 1401 
 1402   # print answer inspection button
 1403   if ($authz->hasPermissions($user, "view_answers")) {
 1404     print "\n",
 1405       CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"WW_Info"),"\n",
 1406       $self->hidden_authen_fields,"\n",
 1407       CGI::hidden(-name => 'courseID',  -value=>$courseName), "\n",
 1408       CGI::hidden(-name => 'problemID', -value=>$problem->problem_id), "\n",
 1409       CGI::hidden(-name => 'setID',  -value=>$problem->set_id), "\n",
 1410       CGI::hidden(-name => 'studentUser',    -value=>$problem->user_id), "\n",
 1411       CGI::p( {-align=>"left"},
 1412         CGI::submit(-name => 'action',  -value=>$r->maketext("Show Past Answers"))
 1413       ), "\n",
 1414       CGI::endform();
 1415   }
 1416 
 1417   return "";
 1418 }
 1419 
 1420 # output_email_instructor subroutine
 1421 
 1422 # prints out the "Email Instructor" button
 1423 
 1424 sub output_email_instructor{
 1425   my $self = shift;
 1426   my $problem = $self->{problem};
 1427   my %will = %{ $self->{will} };
 1428   my $pg = $self->{pg};
 1429 
 1430   print $self->feedbackMacro(
 1431     module             => __PACKAGE__,
 1432     set                => $self->{set}->set_id,
 1433     problem            => $problem->problem_id,
 1434     displayMode        => $self->{displayMode},
 1435     showOldAnswers     => $will{showOldAnswers},
 1436     showCorrectAnswers => $will{showCorrectAnswers},
 1437     showHints          => $will{showHints},
 1438     showSolutions      => $will{showSolutions},
 1439     pg_object          => $pg,
 1440   );
 1441 
 1442   return "";
 1443 }
 1444 
 1445 # output_hidden_info subroutine
 1446 
 1447 # outputs the hidden fields required for the form
 1448 
 1449 sub output_hidden_info{
 1450   my $self = shift;
 1451 
 1452   if(defined $self->{correct_ids}){
 1453     my $correctRef = $self->{correct_ids};
 1454     my @correct = @$correctRef;
 1455     foreach(@correct){
 1456       print CGI::hidden(-name=>"correct_ids", -value=>$_."_val");
 1457     }
 1458   }
 1459   if(defined $self->{incorrect_ids}){
 1460     my $incorrectRef = $self->{incorrect_ids};
 1461     my @incorrect = @$incorrectRef;
 1462     foreach(@incorrect){
 1463       print CGI::hidden(-name=>"incorrect_ids", -value=>$_."_val");
 1464     }
 1465   }
 1466 
 1467   return "";
 1468 }
 1469 
 1470 # output_JS subroutine
 1471 
 1472 # outputs all of the Javascript needed for this page.
 1473 
 1474 sub output_JS{
 1475   my $self = shift;
 1476   my $r = $self->r;
 1477   my $ce = $r->ce;
 1478 
 1479   my $site_url = $ce->{webworkURLs}->{htdocs};
 1480   print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/addOnLoadEvent.js"}), CGI::end_script();
 1481   print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/color.js"}), CGI::end_script();
 1482   return "";
 1483 }
 1484 
 1485 # Simply here to indicate to the template that this page has body part methods which can be called
 1486 
 1487 sub can_body_parts{
 1488   return "";
 1489 }
 1490 
 1491 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9