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

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1636 - (download) (as text) (annotate)
Wed Nov 19 18:44:47 2003 UTC (9 years, 6 months ago) by sh002i
File size: 35776 byte(s)
added code to check for undef return values from DB "get" calls.

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::ContentGenerator::Problem;
    7 use base qw(WeBWorK::ContentGenerator);
    8 
    9 =head1 NAME
   10 
   11 WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem.
   12 
   13 =cut
   14 
   15 use strict;
   16 use warnings;
   17 use CGI qw();
   18 use File::Path qw(rmtree);
   19 use WeBWorK::Form;
   20 use WeBWorK::PG;
   21 use WeBWorK::PG::ImageGenerator;
   22 use WeBWorK::PG::IO;
   23 use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory);
   24 use WeBWorK::DB::Utils qw(global2user user2global findDefaults);
   25 use WeBWorK::Timing;
   26 
   27 my $timer0_ON=0;  # times pg translation phase
   28 
   29 ############################################################
   30 #
   31 # user
   32 # effectiveUser
   33 # key
   34 #
   35 # displayMode
   36 # showOldAnswers
   37 # showCorrectAnswers
   38 # showHints
   39 # showSolutions
   40 #
   41 # AnSwEr# - answer blanks in problem
   42 #
   43 # redisplay - name of the "Redisplay Problem" button
   44 # submitAnswers - name of "Submit Answers" button
   45 # checkAnswers - name of the "Check Answers" button
   46 # previewAnswers - name of the "Preview Answers" button
   47 #
   48 # FIXME: this table is heinously out of date
   49 #
   50 ############################################################
   51 
   52 # FIXME: what is this?
   53 sub templateName {
   54   "problem";
   55 }
   56 
   57 sub pre_header_initialize {
   58   my ($self, $setName, $problemNumber) = @_;
   59   my $r                    = $self->{r};
   60   my $courseEnv            = $self->{ce};
   61   my $db                   = $self->{db};
   62   my $userName             = $r->param('user');
   63   my $effectiveUserName    = $r->param('effectiveUser');
   64   my $key                  = $r->param('key');
   65 
   66   my $user = $db->getUser($userName); # checked
   67   die "record for user $userName (real user) does not exist."
   68     unless defined $user;
   69 
   70   my $effectiveUser = $db->getUser($effectiveUserName); # checked
   71   die "record for user $effectiveUserName (effective user) does not exist."
   72     unless defined $effectiveUser;
   73 
   74   my $PermissionLevel = $db->getPermissionLevel($userName); # checked
   75   die "permission level record for user $userName does not exist (but the user does? odd...)"
   76     unless defined $PermissionLevel;
   77   my $permissionLevel = $PermissionLevel->permission;
   78 
   79   # obtain the merged set for $effectiveUser
   80   my $set = $db->getMergedSet($effectiveUserName, $setName); # checked
   81 
   82   # obtain the merged problem for $effectiveUser
   83   my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked
   84 
   85   my $editMode = $r->param("editMode");
   86 
   87   if ($permissionLevel > 0 and defined $editMode) {
   88     # professors are allowed to fabricate sets and problems not
   89     # assigned to them (or anyone). this allows them to use the
   90     # editor to
   91 
   92     # if that is not yet defined obtain the global set, convert
   93     # it to a user set, and add fake user data
   94     unless (defined $set) {
   95       my $userSetClass = $db->{set_user}->{record};
   96       my $globalSet = $db->getGlobalSet($setName); # checked
   97       # if the global set doesn't exist either, bail!
   98       die "Set $setName does not exist"
   99         unless defined $set;
  100       $set = global2user($userSetClass, $globalSet);
  101       $set->psvn(0);
  102     }
  103 
  104     # if that is not yet defined obtain the global problem,
  105     # convert it to a user problem, and add fake user data
  106     unless (defined $problem) {
  107       my $userProblemClass = $db->{problem_user}->{record};
  108       my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked
  109       # if the global problem doesn't exist either, bail!
  110       die "Problem $problemNumber in set $setName does not exist"
  111         unless defined $problem;
  112       $problem = global2user($userProblemClass, $globalProblem);
  113       $problem->user_id($effectiveUserName);
  114       $problem->problem_seed(0);
  115       $problem->status(0);
  116       $problem->attempted(0);
  117       $problem->last_answer("");
  118       $problem->num_correct(0);
  119       $problem->num_incorrect(0);
  120     }
  121 
  122     # now we're sure we have valid UserSet and UserProblem objects
  123     # yay!
  124 
  125     # now deal with possible editor overrides:
  126 
  127     # if the caller is asking to override the source file, and
  128     # editMode calls for a temporary file, do so
  129     my $sourceFilePath = $r->param("sourceFilePath");
  130     if (defined $sourceFilePath and $editMode eq "temporaryFile") {
  131       $problem->source_file($sourceFilePath);
  132     }
  133 
  134     # if the caller is asking to override the problem seed, do so
  135     my $problemSeed = $r->param("problemSeed");
  136     if (defined $problemSeed) {
  137       $problem->problem_seed($problemSeed);
  138     }
  139   } else {
  140     # students can't view problems not assigned to them
  141     die "Set $setName is not assigned to $effectiveUserName"
  142       unless defined $set;
  143     die "Problem $problemNumber in set $setName is not assigned to $effectiveUserName"
  144       unless defined $problem;
  145   }
  146 
  147   $self->{userName}          = $userName;
  148   $self->{effectiveUserName} = $effectiveUserName;
  149   $self->{user}              = $user;
  150   $self->{effectiveUser}     = $effectiveUser;
  151   $self->{permissionLevel}   = $permissionLevel;
  152   $self->{set}               = $set;
  153   $self->{problem}           = $problem;
  154   $self->{editMode}          = $editMode;
  155 
  156   ##### form processing #####
  157 
  158   # set options from form fields (see comment at top of file for names)
  159   my $displayMode        = $r->param("displayMode") || $courseEnv->{pg}->{options}->{displayMode};
  160   my $redisplay          = $r->param("redisplay");
  161   my $submitAnswers      = $r->param("submitAnswers");
  162   my $checkAnswers       = $r->param("checkAnswers");
  163   my $previewAnswers     = $r->param("previewAnswers");
  164 
  165   # fields which may be defined when using Problem Editor
  166   #my $override_seed = ($permissionLevel>=10) ? $r->param('problemSeed') : undef;
  167   #my $override_problem_source = ($permissionLevel>=10) ? $r->param('sourceFilePath') : undef;
  168   #my $editMode = undef;
  169   #my $submit_button = $r->param('submit_button');
  170   #if ( defined($submit_button ) ) {
  171   # $editMode = "temporaryFile" if $submit_button eq 'Refresh';
  172   # $editMode = 'savedFile'     if $submit_button eq 'Save';
  173   #}
  174   #
  175   ##override using the source file data from the form field
  176   #$problem->source_file($override_problem_source) if defined($override_problem_source);
  177   #$problem->problem_seed($override_seed)          if defined($override_seed);
  178   #
  179   ## store path to source file for title.
  180   #$self->{problem_source_name}    =  $problem->source_file;
  181   #$self->{edit_mode}   = $editMode;
  182   #$self->{current_problem_source}  = (defined($override_problem_source) ) ?
  183 
  184   # coerce form fields into CGI::Vars format
  185   my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars };
  186 
  187 
  188   $self->{displayMode}    = $displayMode;
  189   $self->{redisplay}      = $redisplay;
  190   $self->{submitAnswers}  = $submitAnswers;
  191   $self->{checkAnswers}   = $checkAnswers;
  192   $self->{previewAnswers} = $previewAnswers;
  193   $self->{formFields}     = $formFields;
  194 
  195   ##### permissions #####
  196 
  197   # are we allowed to view this problem?
  198   $self->{isOpen} = time >= $set->open_date || $permissionLevel > 0;
  199   return unless $self->{isOpen};
  200 
  201   # what does the user want to do?
  202   my %want = (
  203     showOldAnswers     => $r->param("showOldAnswers")     || $courseEnv->{pg}->{options}->{showOldAnswers},
  204     showCorrectAnswers => $r->param("showCorrectAnswers") || $courseEnv->{pg}->{options}->{showCorrectAnswers},
  205     showHints          => $r->param("showHints")          || $courseEnv->{pg}->{options}->{showHints},
  206     showSolutions      => $r->param("showSolutions")      || $courseEnv->{pg}->{options}->{showSolutions},
  207     recordAnswers      => $submitAnswers,
  208     checkAnswers       => $checkAnswers,
  209   );
  210 
  211   # are certain options enforced?
  212   my %must = (
  213     showOldAnswers     => 0,
  214     showCorrectAnswers => 0,
  215     showHints          => 0,
  216     showSolutions      => 0,
  217     recordAnswers      => mustRecordAnswers($permissionLevel),
  218     checkAnswers       => 0,
  219   );
  220 
  221   # does the user have permission to use certain options?
  222   my %can = (
  223     showOldAnswers     => 1,
  224     showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date),
  225     showHints          => 1,
  226     showSolutions      => canShowSolutions($permissionLevel, $set->answer_date),
  227     recordAnswers      => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date,
  228       $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1),
  229       # attempts=num_correct+num_incorrect+1, as this happens before updating $problem
  230     checkAnswers       => canCheckAnswers($permissionLevel, $set->answer_date),
  231   );
  232   #########################################################
  233   # more complicated logic for showing check answer button:
  234   #########################################################
  235   # checkAnswers button shows up after due date -- once a student can't record anymore
  236   # checkAnswers button always shows up when an instructor or TA is acting
  237   # as someone else (the $user and $effectiveUserName aren't the same).
  238   $can{checkAnswers} =  ($can{checkAnswers}    &&   not $can{recordAnswers}           ) ||
  239                         ( defined($userName) and defined($effectiveUserName) and
  240                           ($userName ne $effectiveUserName)
  241                          );
  242   #########################################################
  243   # more complicated logif for showing "submit answer" button
  244   #########################################################
  245   # We hide the submit answer button if someone is acting as a student
  246   # This prevents errors where you accidently submit the answer for a student
  247   # Not sure whether this a feature or a bug
  248 
  249   $can{recordAnswers} = ($can{recordAnswers} and not
  250                             (   defined($userName) and defined($effectiveUserName) and
  251                                 ($userName ne $effectiveUserName)
  252                             )
  253                         );
  254   # final values for options
  255   my %will;
  256   foreach (keys %must) {
  257     $will{$_} = $can{$_} && ($want{$_} || $must{$_});
  258   }
  259 
  260   ##### sticky answers #####
  261 
  262   if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) {
  263     # do this only if new answers are NOT being submitted
  264     my %oldAnswers = decodeAnswers($problem->last_answer);
  265     $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers;
  266   }
  267 
  268   ##### translation #####
  269 
  270   $WeBWorK::timer0->continue("begin pg processing") if $timer0_ON;
  271   my $pg = WeBWorK::PG->new(
  272     $courseEnv,
  273     $effectiveUser,
  274     $key,
  275     $set,
  276     $problem,
  277     $set->psvn, # FIXME: this field should be removed
  278     $formFields,
  279     { # translation options
  280       displayMode     => $displayMode,
  281       showHints       => $will{showHints},
  282       showSolutions   => $will{showSolutions},
  283       refreshMath2img => $will{showHints} || $will{showSolutions},
  284       processAnswers  => 1,
  285     },
  286   );
  287 
  288   $WeBWorK::timer0->continue("end pg processing") if $timer0_ON;
  289   ##### fix hint/solution options #####
  290 
  291   $can{showHints}     &&= $pg->{flags}->{hintExists}
  292                       &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans};
  293   $can{showSolutions} &&= $pg->{flags}->{solutionExists};
  294 
  295   ##### store fields #####
  296 
  297   $self->{want} = \%want;
  298   $self->{must} = \%must;
  299   $self->{can}  = \%can;
  300   $self->{will} = \%will;
  301   $self->{pg} = $pg;
  302 }
  303 
  304 #sub if_warnings($$) {
  305 # my ($self, $arg) = @_;
  306 # return 0 unless $self->{isOpen};
  307 # return $self->{pg}->{warnings} ne "";
  308 #}
  309 
  310 sub if_errors($$) {
  311   my ($self, $arg) = @_;
  312   return 0 unless $self->{isOpen};
  313   return $self->{pg}->{flags}->{error_flag};
  314 }
  315 
  316 sub head {
  317   my $self = shift;
  318   return "" unless $self->{isOpen};
  319   return $self->{pg}->{head_text} if $self->{pg}->{head_text};
  320 }
  321 
  322 sub options {
  323   my $self = shift;
  324   return join("",
  325     CGI::start_form("POST", $self->{r}->uri),
  326     $self->hidden_authen_fields,
  327     CGI::hr(),
  328     CGI::start_div({class=>"viewOptions"}),
  329     $self->viewOptions(),
  330     CGI::end_div(),
  331     CGI::end_form()
  332   );
  333 }
  334 
  335 sub path {
  336   my $self = shift;
  337   my $args = $_[-1];
  338   my $setName = $self->{set}->set_id;
  339   my $problemNumber = $self->{problem}->problem_id;
  340 
  341   my $ce = $self->{ce};
  342   my $root = $ce->{webworkURLs}->{root};
  343   my $courseName = $ce->{courseName};
  344   return $self->pathMacro($args,
  345     "Home" => "$root",
  346     $courseName => "$root/$courseName",
  347     $setName => "$root/$courseName/$setName",
  348     "Problem $problemNumber" => "",
  349   );
  350 }
  351 
  352 sub siblings {
  353   my $self = shift;
  354   my $setName = $self->{set}->set_id;
  355   my $problemNumber = $self->{problem}->problem_id;
  356 
  357   my $ce = $self->{ce};
  358   my $db = $self->{db};
  359   my $root = $ce->{webworkURLs}->{root};
  360   my $courseName = $ce->{courseName};
  361   print CGI::strong("Problems"), CGI::br();
  362 
  363   my $effectiveUser = $self->{r}->param("effectiveUser");
  364   my @problemIDs = $db->listUserProblems($effectiveUser, $setName);
  365   foreach my $problem (sort { $a <=> $b } @problemIDs) {
  366     print '&nbsp;&nbsp;'.CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?"
  367       . $self->url_authen_args . "&displayMode=" . $self->{displayMode}},
  368       "Problem ".$problem), CGI::br();
  369   }
  370 
  371   return "";
  372 }
  373 
  374 sub nav {
  375   $WeBWorK::timer0->continue("begin nav subroutine") if $timer0_ON;
  376   my $self = shift;
  377   my $args = $_[-1];
  378   my $setName = $self->{set}->set_id;
  379   my $problemNumber = $self->{problem}->problem_id;
  380 
  381   my $ce = $self->{ce};
  382   my $db = $self->{db};
  383   my $root = $ce->{webworkURLs}->{root};
  384   my $courseName = $ce->{courseName};
  385 
  386   my $wwdb          = $self->{wwdb};
  387   my $effectiveUser = $self->{r}->param("effectiveUser");
  388   my $tail = "&displayMode=".$self->{displayMode};
  389 
  390   my @links = ("Problem List" , "$root/$courseName/$setName", "navProbList");
  391 
  392   my @problemIDs = $db->listUserProblems($effectiveUser, $setName);
  393   my ($prevID, $nextID);
  394   foreach my $id (@problemIDs) {
  395     $prevID = $id if $id < $problemNumber
  396       and (not defined $prevID or $id > $prevID);
  397     $nextID = $id if $id > $problemNumber
  398       and (not defined $nextID or $id < $nextID);
  399   }
  400   unshift @links, "Previous Problem" , ($prevID
  401     ? "$root/$courseName/$setName/".$prevID
  402     : "") , "navPrev";
  403   push @links, "Next Problem" , ($nextID
  404     ? "$root/$courseName/$setName/".$nextID
  405     : "") , "navNext";
  406 
  407   my $result = $self->navMacro($args, $tail, @links);
  408   $WeBWorK::timer0->continue("end nav subroutine") if $timer0_ON;
  409   return $result;
  410 }
  411 
  412 sub title {
  413   my $self = shift;
  414   my $setName = $self->{set}->set_id;
  415   my $problemNumber = $self->{problem}->problem_id;
  416 
  417   return "$setName : Problem $problemNumber";
  418 }
  419 
  420 sub body {
  421   my $self = shift;
  422 
  423   return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open."))
  424     unless $self->{isOpen};
  425 
  426   # unpack some useful variables
  427   my $r               = $self->{r};
  428   my $db              = $self->{db};
  429   my $ce = $self->{ce};
  430   my $root = $ce->{webworkURLs}->{root};
  431   my $courseName = $ce->{courseName};
  432   my $set             = $self->{set};
  433   my $problem         = $self->{problem};
  434   my $editMode        = $self->{editMode};
  435   my $permissionLevel = $self->{permissionLevel};
  436   my $submitAnswers   = $self->{submitAnswers};
  437   my $checkAnswers    = $self->{checkAnswers};
  438   my $previewAnswers  = $self->{previewAnswers};
  439   my %want            = %{ $self->{want} };
  440   my %can             = %{ $self->{can}  };
  441   my %must            = %{ $self->{must} };
  442   my %will            = %{ $self->{will} };
  443   my $pg              = $self->{pg};
  444 
  445 
  446 
  447   #####create Editor link   #####
  448   # print editor link if the user is an instructor AND the file is not in temporary editing mode
  449   my $editorLinkMessage   =   '';
  450   # and ( (not defined($self->{editMode}))  or $self->{editMode} eq 'savedFile')  # FIXME is this needed?
  451   if ($self->{permissionLevel}>=10  ) {
  452     $editorLinkMessage = CGI::a({-href=>$ce->{webworkURLs}->{root}."/$courseName/instructor/pgProblemEditor/".
  453     $set->set_id.'/'.$problem->problem_id.'?'.$self->url_authen_args},'Edit this problem');
  454   }
  455   ##### translation errors? #####
  456 
  457   if ($pg->{flags}->{error_flag}) {
  458     return $self->errorOutput($pg->{errors}, $pg->{body_text}.CGI::p($editorLinkMessage));
  459   }
  460 
  461   ##### answer processing #####
  462   $WeBWorK::timer0->continue("begin answer processing") if $timer0_ON;
  463   # if answers were submitted:
  464   my $scoreRecordedMessage;
  465   if ($submitAnswers) {
  466     # get a "pure" (unmerged) UserProblem to modify
  467     # this will be undefined if the problem has not been assigned to this user
  468     my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); # checked
  469     if (defined $pureProblem) {
  470       # store answers in DB for sticky answers
  471       my %answersToStore;
  472       my %answerHash = %{ $pg->{answers} };
  473       $answersToStore{$_} = $self->{formFields}->{$_}  #$answerHash{$_}->{original_student_ans} -- this may have been modified for fields with multiple values.  Don't use it!!
  474         foreach (keys %answerHash);
  475       # There may be some more answers to store -- one which are auxiliary entries to a primary answer.  Evaluating
  476       # matrices works in this way, only the first answer triggers an answer evaluator, the rest are just inputs
  477       # however we need to store them.  Fortunately they are still in the input form.
  478       my @extra_answer_names  = @{ $pg->{flags}->{KEPT_EXTRA_ANSWERS}};
  479 
  480       $answersToStore{$_} = $self->{formFields}->{$_} foreach  (@extra_answer_names);
  481 
  482       # Now let's encode these answers to store them -- append the extra answers to the end of answer entry order
  483       my @answer_order = (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}, @extra_answer_names);
  484       my $answerString = encodeAnswers(%answersToStore,
  485          @answer_order);
  486 
  487       # store last answer to database
  488       $problem->last_answer($answerString);
  489       $pureProblem->last_answer($answerString);
  490       $db->putUserProblem($pureProblem);
  491 
  492       # store state in DB if it makes sense
  493       if ($will{recordAnswers}) {
  494         $problem->status($pg->{state}->{recorded_score});
  495         $problem->attempted(1);
  496         $problem->num_correct($pg->{state}->{num_of_correct_ans});
  497         $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
  498         $pureProblem->status($pg->{state}->{recorded_score});
  499         $pureProblem->attempted(1);
  500         $pureProblem->num_correct($pg->{state}->{num_of_correct_ans});
  501         $pureProblem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
  502         if ($db->putUserProblem($pureProblem)) {
  503           $scoreRecordedMessage = "Your score was recorded.";
  504         } else {
  505           $scoreRecordedMessage = "Your score was not recorded because there was a failure in storing the problem record to the database.";
  506         }
  507         # write to the transaction log, just to make sure
  508         writeLog($self->{ce}, "transaction",
  509           $problem->problem_id."\t".
  510           $problem->set_id."\t".
  511           $problem->user_id."\t".
  512           $problem->source_file."\t".
  513           $problem->value."\t".
  514           $problem->max_attempts."\t".
  515           $problem->problem_seed."\t".
  516           $pureProblem->status."\t".
  517           $pureProblem->attempted."\t".
  518           $pureProblem->last_answer."\t".
  519           $pureProblem->num_correct."\t".
  520           $pureProblem->num_incorrect
  521         );
  522       } else {
  523         if (time < $set->open_date or time > $set->due_date) {
  524           $scoreRecordedMessage = "Your score was not recorded because this problem set is closed.";
  525         } else {
  526           $scoreRecordedMessage = "Your score was not recorded.";
  527         }
  528       }
  529     } else {
  530       $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you.";
  531     }
  532   }
  533 
  534   # logging student answers
  535 
  536   my $answer_log    = $self->{ce}->{courseFiles}->{logs}->{'answer_log'};
  537   if ( defined($answer_log )) {
  538     if ($submitAnswers ) {
  539       my $answerString = "";
  540       my %answerHash = %{ $pg->{answers} };
  541       $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t"
  542         foreach (sort keys  %answerHash);
  543       $answerString = '' unless defined($answerString); # insure string is defined.
  544       writeCourseLog($self->{ce}, "answer_log",
  545               join("",
  546             '|', $problem->user_id,
  547             '|', $problem->set_id,
  548             '|', $problem->problem_id,
  549             '|',"\t",
  550             time(),"\t",
  551             $answerString,
  552           ),
  553       );
  554 
  555     }
  556   }
  557 
  558   $WeBWorK::timer0->continue("end answer processing") if $timer0_ON;
  559 
  560   ##### output #####
  561 
  562   print CGI::start_div({class=>"problemHeader"});
  563 
  564   # custom message for editor
  565   if ($permissionLevel >= 10 and defined $editMode) {
  566     if ($editMode eq "temporaryFile") {
  567       print CGI::p(CGI::i("Editing temporary file: ", $problem->source_file));
  568     } elsif ($editMode eq "savedFile") {
  569       print CGI::p(CGI::i("Problem saved to: ", $problem->source_file));
  570     }
  571   }
  572 
  573   # attempt summary
  574   #FIXME -- the following is a kludge:  if showPartialCorrectAnswers is negative don't show anything.
  575   # until after the due date
  576   # do I need to check $wills{howCorrectAnswers} to make preflight work??
  577   if (($pg->{flags}->{showPartialCorrectAnswers}>= 0 and $submitAnswers) ) {
  578     # print this if user submitted answers OR requested correct answers
  579 
  580     print $self->attemptResults($pg, 1,
  581       $will{showCorrectAnswers},
  582       $pg->{flags}->{showPartialCorrectAnswers}, 1, 1);
  583   } elsif ($checkAnswers) {
  584     # print this if user previewed answers
  585     print "ANSWERS ONLY CHECKED  -- ",CGI::br(),"ANSWERS NOT RECORDED", CGI::br();
  586     print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1);
  587       # show attempt answers
  588       # show correct answers if asked
  589       # show attempt results (correctness)
  590       # show attempt previews
  591   } elsif ($previewAnswers) {
  592     # print this if user previewed answers
  593     print "PREVIEW ONLY -- NOT RECORDED",CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1);
  594       # show attempt answers
  595       # don't show correct answers
  596       # don't show attempt results (correctness)
  597       # show attempt previews
  598   }
  599 
  600   print CGI::end_div();
  601 
  602   print CGI::start_div({class=>"problem"});
  603 
  604   # main form
  605   print
  606     CGI::startform("POST", $r->uri),
  607     $self->hidden_authen_fields,
  608     CGI::p($pg->{body_text}),
  609     CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})),
  610     CGI::p(
  611       ($can{showCorrectAnswers}
  612         ? CGI::checkbox(
  613             -name    => "showCorrectAnswers",
  614             -checked => $will{showCorrectAnswers},
  615             -label   => "Show correct answers",
  616           ) ." "
  617         : "" ),
  618       ($can{showHints}
  619         ? '<div style="color:red">'. CGI::checkbox(
  620           -name    => "showHints",
  621           -checked => $will{showHints},
  622           -label   => "Show Hints",
  623           ) . "</div> "
  624         : " " ),
  625       ($can{showSolutions}
  626         ? CGI::checkbox(
  627           -name    => "showSolutions",
  628           -checked => $will{showSolutions},
  629           -label   => "Show Solutions",
  630           ) . " "
  631         : " " ),CGI::br(),
  632       CGI::submit(-name=>"previewAnswers",
  633         -label=>"Preview Answers"),
  634       ($can{recordAnswers}
  635         ? CGI::submit(-name=>"submitAnswers",
  636           -label=>"Submit Answers")
  637         : ""),
  638       ( $can{checkAnswers}
  639         ? CGI::submit(-name=>"checkAnswers",
  640           -label=>"Check Answers")
  641         : ""),
  642     );
  643   print CGI::end_div();
  644 
  645   print CGI::start_div({class=>"scoreSummary"});
  646 
  647   # score summary
  648   my $attempts = $problem->num_correct + $problem->num_incorrect;
  649   my $attemptsNoun = $attempts != 1 ? "times" : "time";
  650   my $lastScore = sprintf("%.0f%%", $problem->status * 100); # Round to whole number
  651   my ($attemptsLeft, $attemptsLeftNoun);
  652   if ($problem->max_attempts == -1) {
  653     # unlimited attempts
  654     $attemptsLeft = "unlimited";
  655     $attemptsLeftNoun = "attempts";
  656   } else {
  657     $attemptsLeft = $problem->max_attempts - $attempts;
  658     $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts";
  659   }
  660 
  661   my $setClosed = 0;
  662   my $setClosedMessage;
  663   if (time < $set->open_date or time > $set->due_date) {
  664     $setClosed = 1;
  665     $setClosedMessage = "This problem set is closed.";
  666     if ($permissionLevel > 0) {
  667       $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded.";
  668     } else {
  669       $setClosedMessage .= " Additional attempts will not be recorded.";
  670     }
  671   }
  672   print CGI::p(
  673     $submitAnswers ? $scoreRecordedMessage . CGI::br() : "",
  674     "You have attempted this problem $attempts $attemptsNoun.", CGI::br(),
  675     $problem->attempted
  676       ? "Your recorded score is $lastScore." . CGI::br()
  677       : "",
  678     $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining."
  679   );
  680   print CGI::end_div();
  681 
  682   # save state for viewOptions
  683   print  CGI::hidden(
  684          -name  => "showOldAnswers",
  685          -value => $will{showOldAnswers}
  686        ),
  687 
  688        CGI::hidden(
  689          -name  => "displayMode",
  690          -value => $self->{displayMode}
  691        );
  692   print( CGI::hidden(
  693          -name    => 'editMode',
  694          -value   => $self->{editMode},
  695        )
  696   ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile';
  697   print( CGI::hidden(
  698           -name   => 'sourceFilePath',
  699           -value  =>  $self->{problem}->{source_file}
  700   ))  if defined($self->{problem}->{source_file});
  701 
  702   # end of main form
  703   print CGI::endform();
  704 
  705 
  706   print  CGI::start_div({class=>"problemFooter"});
  707 
  708   # arguments for answer inspection button
  709   my $prof_url = $ce->{webworkURLs}->{oldProf};
  710   my $webworkURL = $ce->{webworkURLs}->{root};
  711   my $cgi_url = $prof_url;
  712   $cgi_url=~ s|/[^/]*$||;  # clip profLogin.pl
  713   my $authen_args = $self->url_authen_args();
  714   my $showPastAnswersURL = "$webworkURL/$courseName/instructor/show_answers/";
  715 
  716   # print answer inspection button
  717   if ($self->{permissionLevel} > 0) {
  718     print "\n",
  719       CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n",
  720       $self->hidden_authen_fields,"\n",
  721       CGI::hidden(-name => 'course',  -value=>$courseName), "\n",
  722       CGI::hidden(-name => 'problemNumber', -value=>$problem->problem_id), "\n",
  723       CGI::hidden(-name => 'setName',  -value=>$problem->set_id), "\n",
  724       CGI::hidden(-name => 'studentUser',    -value=>$problem->user_id), "\n",
  725       CGI::p( {-align=>"left"},
  726         CGI::submit(-name => 'action',  -value=>'Show Past Answers')
  727       ), "\n",
  728       CGI::endform();
  729   }
  730 
  731   #print CGI::end_div();
  732   #
  733   #print CGI::start_div();
  734 
  735   # arguments for feedback form
  736   my $feedbackURL = "$root/$courseName/feedback/";
  737 
  738   #print feedback form
  739   print
  740     CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n",
  741     $self->hidden_authen_fields,"\n",
  742     CGI::hidden("module",             __PACKAGE__),"\n",
  743     CGI::hidden("set",                $set->set_id),"\n",
  744     CGI::hidden("problem",            $problem->problem_id),"\n",
  745     CGI::hidden("displayMode",        $self->{displayMode}),"\n",
  746     CGI::hidden("showOldAnswers",     $will{showOldAnswers}),"\n",
  747     CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n",
  748     CGI::hidden("showHints",          $will{showHints}),"\n",
  749     CGI::hidden("showSolutions",      $will{showSolutions}),"\n",
  750     CGI::p({-align=>"left"},
  751       CGI::submit(-name=>"feedbackForm", -label=>"Email instructor")
  752     ),
  753     CGI::endform(),"\n";
  754 
  755   # FIXME print editor link
  756   print $editorLinkMessage;   #empty unless it is appropriate to have an editor link.
  757 
  758   print CGI::end_div();
  759 
  760   # warning output
  761   #if ($pg->{warnings} ne "") {
  762   # print CGI::hr(), $self->warningOutput($pg->{warnings});
  763   #}
  764 
  765   # debugging stuff
  766   if (0) {
  767     print
  768       CGI::hr(),
  769       CGI::h2("debugging information"),
  770       CGI::h3("form fields"),
  771       ref2string($self->{formFields}),
  772       CGI::h3("user object"),
  773       ref2string($self->{user}),
  774       CGI::h3("set object"),
  775       ref2string($set),
  776       CGI::h3("problem object"),
  777       ref2string($problem),
  778       CGI::h3("PG object"),
  779       ref2string($pg, {'WeBWorK::PG::Translator' => 1});
  780   }
  781 
  782   return "";
  783 }
  784 
  785 ##### output utilities #####
  786 
  787 sub attemptResults($$$$$$) {
  788   my $self = shift;
  789   my $pg = shift;
  790   my $showAttemptAnswers = shift;
  791   my $showCorrectAnswers = shift;
  792   my $showAttemptResults = $showAttemptAnswers && shift;
  793   my $showSummary = shift;
  794   my $showAttemptPreview = shift || 0;
  795   my $ce = $self->{ce};
  796   my $problemResult = $pg->{result}; # the overall result of the problem
  797   my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
  798 
  799   my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
  800 
  801   my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview";
  802   my $imgGen = WeBWorK::PG::ImageGenerator->new(
  803     tempDir  => $ce->{webworkDirs}->{tmp},
  804     latex  => $ce->{externalPrograms}->{latex},
  805     dvipng   => $ce->{externalPrograms}->{dvipng},
  806     useCache => 1,
  807     cacheDir => $ce->{webworkDirs}->{equationCache},
  808     cacheURL => $ce->{webworkURLs}->{equationCache},
  809     cacheDB  => $ce->{webworkFiles}->{equationCacheDB},
  810   );
  811 
  812   my $header;
  813   #$header .= CGI::th("Part");
  814   $header .= $showAttemptAnswers ? CGI::th("Entered")  : "";
  815   $header .= $showAttemptPreview ? CGI::th("Answer Preview")  : "";
  816   $header .= $showCorrectAnswers ? CGI::th("Correct")  : "";
  817   $header .= $showAttemptResults ? CGI::th("Result")   : "";
  818   $header .= $showMessages       ? CGI::th("messages") : "";
  819   my @tableRows = ( $header );
  820   my $numCorrect;
  821   foreach my $name (@answerNames) {
  822     my $answerResult  = $pg->{answers}->{$name};
  823     my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
  824     my $preview       = ($showAttemptPreview
  825                           ? $self->previewAnswer($answerResult, $imgGen)
  826                           : "");
  827     my $correctAnswer = $answerResult->{correct_ans};
  828     my $answerScore   = $answerResult->{score};
  829     my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
  830     #FIXME  --Can we be sure that $answerScore is an integer-- could the problem give partial credit?
  831     $numCorrect += $answerScore > 0;
  832     my $resultString = $answerScore ? "correct" : "incorrect";
  833 
  834     # get rid of the goofy prefix on the answer names (supposedly, the format
  835     # of the answer names is changeable. this only fixes it for "AnSwEr"
  836     #$name =~ s/^AnSwEr//;
  837 
  838     my $row;
  839     #$row .= CGI::td($name);
  840     $row .= $showAttemptAnswers ? CGI::td(nbsp($studentAnswer)) : "";
  841     $row .= $showAttemptPreview ? CGI::td(nbsp($preview))       : "";
  842     $row .= $showCorrectAnswers ? CGI::td(nbsp($correctAnswer)) : "";
  843     $row .= $showAttemptResults ? CGI::td(nbsp($resultString))  : "";
  844     $row .= $answerMessage      ? CGI::td(nbsp($answerMessage)) : "";
  845     push @tableRows, $row;
  846   }
  847 
  848   # render equation images
  849   $imgGen->render(refresh => 1);
  850 
  851 # my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
  852   my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100);
  853 #   FIXME  -- I left the old code in in case we have to back out.
  854 # my $summary = "On this attempt, you answered $numCorrect out of "
  855 #   . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
  856   my $summary = "";
  857   if (scalar @answerNames == 1) {
  858       if ($numCorrect == scalar @answerNames) {
  859         $summary .= "The above answer is correct.";
  860        } else {
  861          $summary .= "The above answer is NOT correct.";
  862        }
  863   } else {
  864       if ($numCorrect == scalar @answerNames) {
  865         $summary .= "All of the above answers are correct.";
  866        } else {
  867          $summary .= "At least one of the above answers is NOT correct.";
  868        }
  869   }
  870   #FIXME  there must be a better way to force refresh.
  871   #my $refresh_warning = 'Hold down shift and click "refresh" or "reload" to update answer preview images.';
  872   #return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) .
  873   #CGI::div({style=>'color:red; font-size:10pt'},$refresh_warning) .
  874   #($showSummary ? CGI::p({class=>'emphasis'},$summary) : "");
  875   # ... this has been fixed by equation caching.
  876   return
  877     CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows))
  878     . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : "");
  879 }
  880 sub nbsp {
  881   my $str = shift;
  882   ($str =~/\S/) ? $str : '&nbsp;'  ;  # returns non-breaking space for empty strings
  883                                       # tricky cases:   $str =0;
  884                                       #  $str is a complex number
  885 }
  886 sub viewOptions($) {
  887   my $self = shift;
  888   my $displayMode = $self->{displayMode};
  889   my %must = %{ $self->{must} };
  890   my %can  = %{ $self->{can}  };
  891   my %will = %{ $self->{will} };
  892 
  893   my $optionLine;
  894   $can{showOldAnswers} and $optionLine .= join "",
  895     "Show: &nbsp;".CGI::br(),
  896     CGI::checkbox(
  897       -name    => "showOldAnswers",
  898       -checked => $will{showOldAnswers},
  899       -label   => "Saved answers",
  900     ), "&nbsp;&nbsp;".CGI::br();
  901 
  902   $optionLine and $optionLine .= join "", CGI::br();
  903 
  904   return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"},
  905       "View&nbsp;equations&nbsp;as:&nbsp;&nbsp;&nbsp;&nbsp;".CGI::br(),
  906     CGI::radio_group(
  907       -name    => "displayMode",
  908       -values  => ['plainText', 'formattedText', 'images'],
  909       -default => $displayMode,
  910       -linebreak=>'true',
  911       -labels  => {
  912         plainText     => "plain",
  913         formattedText => "formatted",
  914         images        => "images",
  915       }
  916     ), CGI::br(),CGI::hr(),
  917     $optionLine,
  918     CGI::submit(-name=>"redisplay", -label=>"Save Options"),
  919   );
  920 }
  921 
  922 sub previewAnswer($$) {
  923   my ($self, $answerResult, $imgGen) = @_;
  924   my $ce            = $self->{ce};
  925   my $effectiveUser = $self->{effectiveUser};
  926   my $set           = $self->{set};
  927   my $problem       = $self->{problem};
  928   my $displayMode   = $self->{displayMode};
  929 
  930   # note: right now, we have to do things completely differently when we are
  931   # rendering math from INSIDE the translator and from OUTSIDE the translator.
  932   # so we'll just deal with each case explicitly here. there's some code
  933   # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
  934 
  935   my $tex = $answerResult->{preview_latex_string};
  936 
  937   return "" unless defined $tex and $tex ne "";
  938 
  939   if ($displayMode eq "plainText") {
  940     return $tex;
  941   } elsif ($displayMode eq "formattedText") {
  942     my $tthCommand = $ce->{externalPrograms}->{tth}
  943       . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
  944       . "\\(".$tex."\\)\n"
  945       . "END_OF_INPUT\n";
  946 
  947     # call tth
  948     my $result = `$tthCommand`;
  949     if ($?) {
  950       return "<b>[tth failed: $? $@]</b>";
  951     }
  952     return $result;
  953   } elsif ($displayMode eq "images") {
  954     ## how are we going to name this?
  955     #my $targetPathCommon = "/m2i/"
  956     # . $effectiveUser->user_id . "."
  957     # . $set->set_id . "."
  958     # . $problem->problem_id . "."
  959     # . $answerResult->{ans_name} . ".png";
  960     #
  961     ## figure out where to put things
  962     #my $wd = makeTempDirectory($ce->{courseDirs}->{html_temp}, "webwork-dvipng");
  963     #my $latex = $ce->{externalPrograms}->{latex};
  964     #my $dvipng = $ce->{externalPrograms}->{dvipng};
  965     #my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon;
  966     #   # should use surePathToTmpFile, but we have to
  967     #   # isolate it from the problem enivronment first
  968     #my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon;
  969     #
  970     ## call dvipng to generate a preview
  971     #dvipng($wd, $latex, $dvipng, $tex, $targetPath);
  972     #rmtree($wd, 0, 0);
  973     #if (-e $targetPath) {
  974     # return "<img src=\"$targetURL\" alt=\"$tex\" />";
  975     #} else {
  976     # return "<b>[math2img failed]</b>";
  977     #}
  978     $imgGen->add($answerResult->{preview_latex_string});
  979 
  980   }
  981 }
  982 
  983 ##### logging subroutine ####
  984 
  985 
  986 
  987 ##### permission queries #####
  988 
  989 # this stuff should be abstracted out into the permissions system
  990 # however, the permission system only knows about things in the
  991 # course environment and the username. hmmm...
  992 
  993 # also, i should fix these so that they have a consistent calling
  994 # format -- perhaps:
  995 #   canPERM($courseEnv, $user, $set, $problem, $permissionLevel)
  996 
  997 sub canShowCorrectAnswers($$) {
  998   my ($permissionLevel, $answerDate) = @_;
  999   return $permissionLevel > 0 || time > $answerDate;
 1000 }
 1001 
 1002 sub canShowSolutions($$) {
 1003   my ($permissionLevel, $answerDate) = @_;
 1004   return canShowCorrectAnswers($permissionLevel, $answerDate);
 1005 }
 1006 
 1007 sub canRecordAnswers($$$$$) {
 1008   my ($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts) = @_;
 1009   my $permHigh = $permissionLevel > 0;
 1010   my $timeOK = time >= $openDate && time <= $dueDate;
 1011   my $attemptsOK = $maxAttempts == -1 || $attempts <= $maxAttempts;
 1012   my $recordAnswers = $permHigh || ($timeOK && $attemptsOK);
 1013   return $recordAnswers;
 1014 }
 1015 
 1016 sub canCheckAnswers($$) {
 1017   my ($permissionLevel, $answerDate) = @_;
 1018   my $permHigh = $permissionLevel > 0;
 1019   my $timeOK = time >= $answerDate;
 1020   my $recordAnswers = $permHigh || $timeOK;
 1021   return $recordAnswers;
 1022 }
 1023 
 1024 sub mustRecordAnswers($) {
 1025   my ($permissionLevel) = @_;
 1026   return $permissionLevel == 0;
 1027 }
 1028 
 1029 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9