[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 1539 - (download) (as text) (annotate)
Sat Sep 27 16:44:53 2003 UTC (9 years, 7 months ago) by gage
File size: 34335 byte(s)
When recording answers the answer strings are now obtained directly from the original
formField entries, rather than from the processed version in the answer hashes.
This insures that the memorized answers will be identical with the ones that would
have been submitted from the form.

This is important for answers with multiple values which are represented by
null separated strings.  The answer evaluation process converts these strings
to references to arrays, and -- in order to have good display properties -- the
original_student_answer slot in the AnswerHash contains a representation such as
( 4, 5, 6).

This is NOT suitable for resubmitting as an answer in a form field and is therefore not the
right thing to store in the data base when saving answers.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9