[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 1591 - (download) (as text) (annotate)
Sat Oct 18 20:46:01 2003 UTC (9 years, 8 months ago) by gage
File size: 34815 byte(s)
Fixed problem in logic which kept an edited problem from reading the
.tmp file when the answer was submitted.  Now the editMode variable
and sourceFilePath variables are passed on as hidden variables by
Problem.pm if they exists in the input form.  This fixes bug #179
as well as bug #109.

The temporary file is now labeled   fileName.pg.user.tmp where user is
the login name of the person editing the file.  If that file exists
then pgProblemEditor will attempt to use that as a source file.
The revert button forces a read from fileName.pg

--Mike

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9