[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / ContentGenerator / Problem.pm Repository:
ViewVC logotype

View of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1956 - (download) (as text) (annotate)
Wed Apr 7 22:18:46 2004 UTC (9 years, 1 month ago) by gage
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm
File size: 36353 byte(s)
Corrected typo in defining
class.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9