[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Problem.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9