[system] / trunk / pg / macros / compoundProblem.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/compoundProblem.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6387 - (download) (as text) (annotate)
Wed Jul 21 15:44:42 2010 UTC (8 years, 8 months ago) by gage
File size: 26627 byte(s)
import from gage_dev.  Changed references to PG_FLAGS to ::PG->{flags}.  This should fix bugs in compoundProblem.pl, problemPanic.pl and problemRandomize.pl -- but only compoundProblem.pl has been tested -- lightly

    1 sub _compoundProblem_init {};   # don't reload this file
    2 
    3 ######################################################################
    4 #
    5 #  This package implements a method of handling multi-part problems
    6 #  that show only a single part at any one time.  The students can
    7 #  work on one part at a time, and then when they get it right (or
    8 #  under other circumstances deterimed by the professor), they can
    9 #  move on to the next part.  Students can not return to earlier parts
   10 #  once they have been completed.  The score for problem as a whole is
   11 #  made up from the scores on the individual parts, and the relative
   12 #  weighting of the various parts can be specified by the problem
   13 #  author.
   14 #
   15 #  To use the compoundProblem library, use
   16 #
   17 #      loadMacros("compoundProblem.pl");
   18 #
   19 #  at the top of your file, and then create a compoundProblem object
   20 #  via the command
   21 #
   22 #      $cp = new compoundProblem(options)
   23 #
   24 #  where '$cp' is the name of a variable that you will use to
   25 #  refer to the compound problem, and 'options' can include:
   26 #
   27 #    parts => n                The number of parts in the problem.
   28 #                                Default: 1
   29 #
   30 #    weights => [n1,...,nm]    The relative weights to give to each
   31 #                              part in the problem.  For example,
   32 #                                  weights => [2,1,1]
   33 #                              would cause the first part to be worth 50%
   34 #                              of the points (twice the amount for each of
   35 #                              the other two), while the second and third
   36 #                              part would be worth 25% each.  If weights
   37 #                              are not supplied, the parts are weighted
   38 #                              by the number of answer blanks in each part
   39 #                              (and you must provide the total number of
   40 #                              blanks in all the parts by supplying the
   41 #                              totalAnswers option).
   42 #
   43 #    totalAnswers => n         The total number of answer blanks in all
   44 #                              the parts put together (this is used when
   45 #                              computing the per-part scores, if part
   46 #                              weights are not provided).
   47 #
   48 #    saveAllAnswers => 0 or 1  Usually, the contents of named answer blanks
   49 #                              from previous parts are made available to
   50 #                              later parts using variables with the
   51 #                              same name as the answer blank.  Setting
   52 #                              saveAllAnswers to 1 will cause ALL answer
   53 #                              blanks to be available (via variables
   54 #                              like $AnSwEr1, and so on).
   55 #                                 Default:  0
   56 #
   57 #    parserValues => 0 or 1    Determines whether the answers from previous
   58 #                              parts are returned as MathObjects (like
   59 #                              those returned from Real(), Vector(), etc)
   60 #                              or as strings (the unparsed contents of the
   61 #                              student answer).  If you intend to use the
   62 #                              previous answers as numbers, for example,
   63 #                              you would want to set this to 1 so that you
   64 #                              would get the final result of any formula
   65 #                              the student typed, rather than the formula
   66 #                              itself as a character string.
   67 #                                 Default:  0
   68 #
   69 #    nextVisible => type       Tells when the "go on to the next part" option
   70 #                              is available to the student.  The possible
   71 #                              types include:
   72 #
   73 #                                 'ifCorrect'   next is available only when
   74 #                                               all the answers are correct.
   75 #
   76 #                                 'Always'      next is always available
   77 #                                               (but remember that students
   78 #                                               can't go back once they go
   79 #                                               on.)
   80 #
   81 #                                 'Never'       next is never allowed (the
   82 #                                               problem will control going
   83 #                                               on to the next part itself).
   84 #
   85 #                                Default:  'ifCorrect'
   86 #
   87 #    nextStyle => type         Determines the style of "next" indicator to display
   88 #                              (when it is available).  The type can be one of:
   89 #
   90 #                                 'CheckBox'    a checkbox that allows the students
   91 #                                               to go on to the next part when they
   92 #                                               submit their answers.
   93 #
   94 #                                 'Button'      a button that submits their answers
   95 #                                               and goes on to the next part.
   96 #
   97 #                                 'Forced'      forces the student to go on to the
   98 #                                               next part the next time they submit
   99 #                                               answers.
  100 #
  101 #                                 'HTML'        allows you to provide an arbitrary
  102 #                                               HTML string of your own.
  103 #
  104 #                                Default:  'Checkbox'
  105 #
  106 #    nextLabel => string       Specifies the string to use as the label for the checkbox,
  107 #                              the name of the button, the text of the message indicating
  108 #                              that the next submit will move to the next part, or the
  109 #                              HTML string, depending on the setting of nextStyle above.
  110 #
  111 #    nextNoChange => 0 or 1    Since the students must submit their answers again to go on
  112 #                              to the next part, it is possible for them to change their
  113 #                              answers before they submit, and if nextVisible is 'ifCorrect'
  114 #                              they might go on to the next without having correct answers
  115 #                              stored.  This option lets you control whether the answers
  116 #                              are checked against the previous ones before going on to the
  117 #                              next part.  If the answers don't match, a warning is issued
  118 #                              and they are not allowed to move on.
  119 #                                Default:  1
  120 #
  121 #    allowReset => 0 or 1      Determines whether a "Go back to the first part" checkbox
  122 #                              is provided on parts 2 and later.  This is intended for
  123 #                              the professor during testing of the problem (otherwise
  124 #                              it would be impossible to go back to earlier parts).
  125 #                                Default:  0
  126 #
  127 #    resetLabel => string      The string used to label the reset checkbox.
  128 #
  129 #  Once you have created a compoundProblem object, you can use $cp->part to
  130 #  determine the part that the student is working on, and use 'if' statements
  131 #  to display the proper information for the given part.  The compoundProblem
  132 #  object takes care of maintaining the data as the parts change.  (See the
  133 #  compoundProblem.pg file for an example of a compound problem.)
  134 #
  135 #  In order to handle the scoring of the problem as a whole when only part is
  136 #  showing, the compoundProblem object uses its own problem grader to manage
  137 #  the scores, and calls your own grader from there.  The default is to use
  138 #  the one that was installed before the compoundProblem object was created,
  139 #  or avg_problem_grader if none was installed.  You can specify a different
  140 #  one using the $cp->useGrader() method (see below).  It is important that
  141 #  you NOT call install_problem_grader() yourself once you have created the
  142 #  compoundProblem object, as that would disable the special grader, causing
  143 #  the compound problem to fail to work properly.
  144 #
  145 #  You may call the following methods once you have a compoundProblem:
  146 #
  147 #    $cp->part                   Returns the part the student is working on.
  148 #    $cp->part(n)                Sets the part to be part n, as long as the
  149 #                                student has finished the preceeding parts.
  150 #                                If not, the part is set to the highest
  151 #                                one the student hasn't completed, and he
  152 #                                can work up to the given part.  (The
  153 #                                nextVisible option is set to 'ifCorrect' if
  154 #                                it was 'Never' so that students can go on
  155 #                                once they finish the earlier parts.)
  156 #
  157 #    $cp->useGrader(code_ref)    Supplies your own grader to use in
  158 #                                place of the default one.  For example:
  159 #                                  $cp->useGrader(~~&std_problem_grader);
  160 #
  161 #    $cp->score                  Returns the (weighted) score for this part.
  162 #                                Note that this is the score shown at the bottom
  163 #                                of the page on which the student pressed submit
  164 #                                (not the score for the answers the student is
  165 #                                submitting -- that is not available until
  166 #                                after the body of the problem has been created).
  167 #
  168 #    $cp->scoreRaw               Returns the unweighted score for this part.
  169 #
  170 #    $cp->scoreOverall           Returns the overall score for the problem
  171 #                                so far.
  172 #
  173 #    $cp->addAnswers(list)       Make additional answer blanks be available
  174 #                                from one part to another.  E.g.,
  175 #                                   $cp->addAnswers('AnSwEr1');
  176 #                                would make the first unnamed blank be available
  177 #                                in later parts as well.  (This command should
  178 #                                be issued only when the part containing the
  179 #                                given answer blank is displayed.)
  180 #
  181 #    $cp->nextCheckbox(label)    Returns the HTML string for the "go on to next
  182 #                                part" checkbox so you can use it in the body of
  183 #                                the problem if you wish.  This should not be
  184 #                                inserted when the $displayMode is 'TeX'.  If the
  185 #                                label is not given or is blank, the default label
  186 #                                is used.
  187 #
  188 #    $cp->nextButton(label)      Returns the HTML string for the "go on to next
  189 #                                part" button so you can use it in the body of
  190 #                                the problem if you wish.  This should not be
  191 #                                inserted when the $displayMode is 'TeX'.  If the
  192 #                                label is not given or is blank, the default label
  193 #                                is used.
  194 #
  195 #    $cp->nextForces(label)      Returns the HTML string for the forced "go on to
  196 #                                next part" so you can use it in the body of
  197 #                                the problem if you wish.  This should not be
  198 #                                inserted when the $displayMode is 'TeX'.  If the
  199 #                                label is not given or is blank, the default label
  200 #                                is used.
  201 #
  202 #    $cp->reset                  Go back to part 1, clearing the answers
  203 #                                and score.  (Best used when debugging problems.)
  204 #
  205 #    $cp->resetCheckbox(label)   Returns the HTML string for the reset checkbox
  206 #                                so that you can provide one within the body
  207 #                                of the problem if you wish.  This should not be
  208 #                                inserted when the $displayMode is 'TeX'.  If the
  209 #                                label is not given or is blank, the default label
  210 #                                will be used.
  211 #
  212 
  213 ######################################################################
  214 
  215 
  216 package compoundProblem;
  217 
  218 #
  219 #  The state data that is stored between invocations of
  220 #  the problem.
  221 #
  222 our %defaultStatus = (
  223   part => 1,                # the current part
  224   answers => "",            # answer labels from previous parts
  225   new_answers => "",        # answer labels for THIS part
  226   ans_rule_count => 0,      # the ans_rule count from previous parts
  227   new_ans_rule_count => 0,  # the ans_rule count from THIS part
  228   images_created => 0,      # the image count from the precious parts
  229   new_images_created => 0,  # the image count from THIS part
  230   imageName => "",          # name of images_created image file
  231   score => 0,               # the (weighted) score on this part
  232   total => 0,               # the total on previous parts
  233   raw => 0,                 # raw score on this part
  234 );
  235 
  236 #
  237 #  Create a new instance of the compound Problem and initialize
  238 #  it.  This includes reading the status from the previous
  239 #  parts, defining the variables from the answers to previous parts,
  240 #  and setting up the grader so that the current data can be saved.
  241 #
  242 sub new {
  243   my $self = shift; my $class = ref($self) || $self;
  244   my $cp = bless {
  245     parts => 1,
  246     totalAnswers => undef,
  247     weights => undef,            # array of weights per part
  248     saveAllAnswers => 0,         # usually only save named answers
  249     parserValues => 0,           # make Parser objects from the answers?
  250     nextVisible => "ifCorrect",  # or "Always" or "Never"
  251     nextStyle   => "Checkbox",   # or "Button", "Forced", or "HTML"
  252     nextLabel   => undef,        # Checkbox text or button name or HTML
  253     nextNoChange => 1,           # true if answer can't change for new part
  254     allowReset => 0,             # true to show "back to part 1" button
  255     resetLabel => undef,         # label for reset button
  256     grader => $main::PG->{flags}->{PROBLEM_GRADER_TO_USE} || \&main::avg_problem_grader,
  257     @_,
  258     status => $defaultStatus,
  259   }, $class;
  260   die "You must provide either the totalAnswers or weights"
  261     unless $cp->{totalAnswers} || $cp->{weights};
  262   $cp->getTotalWeight if $cp->{weights};
  263   main::loadMacros("Parser.pl") if $cp->{parserValues};
  264   $cp->reset if $cp->{allowReset} && $main::inputs_ref->{_reset};
  265   $cp->getStatus;
  266   $cp->initPart;
  267   return $cp;
  268 }
  269 
  270 #
  271 #  Compute the total of the weights so that the parts can
  272 #  be properly scaled.
  273 #
  274 sub getTotalWeight {
  275   my $self = shift;
  276   $self->{totalWeight} = 0; $self->{totalAnswers} = 1;
  277   foreach my $w (@{$self->{weights}}) {$self->{totalWeight} += $w}
  278   $self->{totalWeight} = 1 if $self->{totalWeight} == 0;
  279 }
  280 
  281 #
  282 #  Look up the status from the previous invocation
  283 #  and see if we need to go on to the next part.
  284 #
  285 sub getStatus {
  286   my $self = shift;
  287   main::RECORD_FORM_LABEL("_next");
  288   main::RECORD_FORM_LABEL("_status");
  289   $self->{status} = $self->decode;
  290   $self->{isNew} = $main::inputs_ref->{_next} || ($main::inputs_ref->{submitAnswers} &&
  291      $main::inputs_ref->{submitAnswers} eq ($self->{nextLabel} || "Go on to Next Part"));
  292   if ($self->{isNew}) {
  293     $self->checkAnswers;
  294     $self->incrementPart unless $self->{nextNoChange} && $self->{answersChanged};
  295   }
  296 }
  297 
  298 #
  299 #  Initialize the current part by setting the ans_rule
  300 #  count (so that later parts will get unique answer names),
  301 #  installing the grader (to save the data), and setting
  302 #  the variables for previous answers.
  303 #
  304 sub initPart {
  305   my $self = shift;
  306   $main::ans_rule_count = $self->{status}{ans_rule_count};
  307   $main::images_created{$self->{status}{imageName}} = $self->{status}{images_created}
  308     if $self->{status}{imageName};
  309   main::install_problem_grader(\&compoundProblem::grader);
  310   $main::PG->{flags}->{compoundProblem} = $self;
  311   $self->initAnswers($self->{status}{answers});
  312 }
  313 
  314 #
  315 #  Look through the list of answer labels and set
  316 #  the variables for them to be the associated student
  317 #  answer.  Make it a Parser value if requested.
  318 #  Record the value so that is will be available
  319 #  again on the next invocation.
  320 #
  321 sub initAnswers {
  322   my $self = shift; my $answers = shift;
  323   foreach my $id (split(/;/,$answers)) {
  324     my $value = $main::inputs_ref->{$id}; $value = '' unless defined($value);
  325     if ($self->{parserValues}) {
  326       my $parser = Parser::Formula($value);
  327       $parser = Parser::Evaluate($parser) if $parser && $parser->isConstant;
  328       $value = $parser if $parser;
  329     }
  330     ${"main::$id"} = $value unless $id =~ m/$main::ANSWER_PREFIX/o;
  331     $value = quoteHTML($value);
  332     main::TEXT(qq!<input type="hidden" name="$id" value="$value" />!);
  333     main::RECORD_FORM_LABEL($id);
  334   }
  335 }
  336 
  337 #
  338 #  Look to see is any answers have changed on this
  339 #  invocation of the problem.
  340 #
  341 sub checkAnswers {
  342   my $self = shift;
  343   foreach my $id (keys(%{$main::inputs_ref})) {
  344     if ($id =~ m/^previous_(.*)$/) {
  345       if ($main::inputs_ref->{$id} ne $main::inputs_ref->{$1}) {
  346   $self->{answersChanged} = 1;
  347   $self->{isNew} = 0 if $self->{nextNoChange};
  348   return;
  349       }
  350     }
  351   }
  352 }
  353 
  354 #
  355 #  Go on to the next part, updating the status
  356 #  to include the data from the old part so that
  357 #  it will be properly preserved when the next
  358 #  part is showing.
  359 #
  360 sub incrementPart {
  361   my $self = shift;
  362   my $status = $self->{status};
  363   if ($status->{part} < $self->{parts}) {
  364     $status->{part}++;
  365     $status->{answers} .= ';' if $status->{answers};
  366     $status->{answers} .= $status->{new_answers};
  367     $status->{ans_rule_count} = $status->{new_ans_rule_count};
  368     $status->{images_created} = $status->{new_images_created};
  369     $status->{total} += $status->{score};
  370     $status->{score} = $status->{raw} = 0;
  371     $status->{new_answers} = '';
  372   }
  373 }
  374 
  375 ######################################################################
  376 
  377 #
  378 #  Encode all the status information so that it can be
  379 #  maintained as the student submits answers.  Since this
  380 #  state information includes things like the score from
  381 #  the previous parts, it is "encrypted" using a dumb
  382 #  hex encoding (making it harder for a student to recognize
  383 #  it as valuable data if they view the page source).
  384 #
  385 sub encode {
  386   my $self = shift; my $status = shift || $self->{status};
  387   my @data = (); my $data = "";
  388   foreach my $id (main::lex_sort(keys(%defaultStatus))) {push(@data,$status->{$id})}
  389   foreach my $c (split(//,join('|',@data))) {$data .= toHex($c)}
  390   return $data;
  391 }
  392 
  393 #
  394 #  Decode the data and break it into the status hash.
  395 #
  396 sub decode {
  397   my $self = shift; my $status = shift || $main::inputs_ref->{_status};
  398   return {%defaultStatus} unless $status;
  399   my @data = (); foreach my $hex (split(/(..)/,$status)) {push(@data,fromHex($hex)) if $hex ne ''}
  400   @data = split('\\|',join('',@data)); $status = {%defaultStatus};
  401   if (scalar(@data) == 8) {
  402     # insert imageName, images_created, new_images_created, if missing
  403     splice(@data,2,0,"",0); splice(@data,6,0,0);
  404   }
  405   foreach my $id (main::lex_sort(keys(%defaultStatus))) {$status->{$id} = shift(@data)}
  406   return $status;
  407 }
  408 
  409 
  410 #
  411 #  Hex encoding is shifted by 10 to obfuscate it further.
  412 #  (shouldn't be a problem since the status will be made of
  413 #  printable characters, so they are all above ASCII 32)
  414 #
  415 sub toHex {main::spf(ord(shift)-10,"%X")}
  416 sub fromHex {main::spf(hex(shift)+10,"%c")}
  417 
  418 
  419 #
  420 #  Make sure the data can be properly preserved within
  421 #  an HTML <INPUT TYPE="HIDDEN"> tag.
  422 #
  423 sub quoteHTML {
  424   my $string = shift;
  425   $string =~ s/&/\&amp;/g; $string =~ s/"/\&quot;/g;
  426   $string =~ s/>/\&gt;/g;  $string =~ s/</\&lt;/g;
  427   return $string;
  428 }
  429 
  430 ######################################################################
  431 
  432 #
  433 #  Set the grader for this part to the specified one.
  434 #
  435 sub useGrader {
  436   my $self = shift;
  437   $self->{grader} = shift;
  438 }
  439 
  440 #
  441 #  Make additional answer blanks from the current part
  442 #  be preserved for use in future parts.
  443 #
  444 sub addAnswers {
  445   my $self = shift;
  446   $self->{extraAnswers} = [] unless $self->{extraAnswers};
  447   push(@{$self->{extraAnswers}},@_);
  448 }
  449 
  450 #
  451 #  Go back to part 1 and clear the answers and scores.
  452 #
  453 sub reset {
  454   my $self = shift;
  455   if ($main::inputs_ref->{_status}) {
  456     my $status = $self->decode($main::inputs_ref->{_status});
  457     foreach my $id (split(/;/,$status->{answers})) {delete $main::inputs_ref->{$id}}
  458     foreach my $id (1..$status->{ans_rule_count})
  459       {delete $main::inputs_ref->{"${main::QUIZ_PREFIX}${main::ANSWER_PREFIX}$id"}}
  460   }
  461   $main::inputs_ref->{_status} = $self->encode(\%defaultStatus);
  462   $main::inputs_ref->{_next} = 0;
  463 }
  464 
  465 #
  466 #  Return the HTML for the "Go back to part 1" checkbox.
  467 #
  468 sub resetCheckbox {
  469   my $self = shift;
  470   my $label = shift || " <b>Go back to Part 1</b> (when you submit your answers).";
  471   my $par = shift; $par = ($par ? $main::PAR : '');
  472   qq'$par<input type="checkbox" name="_reset" value="1" />$label';
  473 }
  474 
  475 #
  476 #  Return the HTML for the "next part" checkbox.
  477 #
  478 sub nextCheckbox {
  479   my $self = shift;
  480   my $label = shift || " <b>Go on to next part</b> (when you submit your answers).";
  481   my $par = shift; $par = ($par ? $main::PAR : '');
  482   $self->{nextInserted} = 1;
  483   qq!$par<input type="checkbox" name="_next" value="next" />$label!;
  484 }
  485 
  486 #
  487 #  Return the HTML for the "next part" button.
  488 #
  489 sub nextButton {
  490   my $self = shift;
  491   my $label = quoteHTML(shift || "Go on to Next Part");
  492   my $par = shift; $par = ($par ? $main::PAR : '');
  493   $par . qq!<input type="submit" name="submitAnswers" value="$label" !
  494        .      q!onclick="document.getElementById('_next').value=1" />!;
  495 }
  496 
  497 #
  498 #  Return the HTML for when going to the next part is forced.
  499 #
  500 sub nextForced {
  501   my $self = shift;
  502   my $label = shift || "<b>Submit your answers again to go on to the next part.</b>";
  503   $label = $main::PAR . $label if shift;
  504   $self->{nextInserted} = 1;
  505   qq!$label<input type="hidden" name="_next" id="_next" value="Next" />!;
  506 }
  507 
  508 #
  509 #  Return the raw HTML provided
  510 #
  511 sub nextHTML {shift; shift}
  512 
  513 ######################################################################
  514 
  515 #
  516 #  Return the current part, or try to set the part to the given
  517 #  part (returns the part actually set, which may be earlier if
  518 #  the student didn't complete an earlier part).
  519 #
  520 sub part {
  521   my $self = shift; my $status = $self->{status};
  522   my $part = shift;
  523   return $status->{part} unless defined $part && $main::displayMode ne 'TeX';
  524   $part = 1 if $part < 1; $part = $self->{parts} if $part > $self->{parts};
  525   if ($part > $status->{part} && !$main::inputs_ref->{_noadvance}) {
  526     unless ((lc($self->{nextVisible}) eq 'ifcorrect' && $status->{raw} < 1) ||
  527              lc($self->{nextVisible}) eq 'never') {
  528       $self->initAnswers($status->{new_answers});
  529       $self->incrementPart; $self->{isNew} = 1;
  530     }
  531   }
  532   if ($part != $status->{part}) {
  533     main::TEXT('<input type="hidden" name="_noadvance" value="1" />');
  534     $self->{nextVisible} = 'IfCorrect' if lc($self->{nextVisible}) eq 'never';
  535   }
  536   return $status->{part};
  537 }
  538 
  539 #
  540 #  Return the various scores
  541 #
  542 sub score {shift->{status}{score}}
  543 sub scoreRaw {shift->{status}{raw}}
  544 sub scoreOverall {
  545   my $self = shift;
  546   return $self->{status}{score} + $self->{status}{total};
  547 }
  548 
  549 ######################################################################
  550 #
  551 #  The custom grader that does the work of computing the scores
  552 #  and saving the data.
  553 #
  554 sub grader {
  555   my $self = $main::PG->{flags}->{compoundProblem};
  556   #
  557   #  Get the answer names and the weight for the current part.
  558   #
  559   my @answers = keys(%{$_[0]});
  560   my $weight = scalar(@answers)/$self->{totalAnswers};
  561   $weight = $self->{weights}[$self->{status}{part}-1]/$self->{totalWeight}
  562     if $self->{weights} && defined($self->{weights}[$self->{status}{part}-1]);
  563   @answers = grep(!/$main::ANSWER_PREFIX/o,@answers) unless $self->{saveAllAnswers};
  564   push(@answers,@{$self->{extraAnswers}}) if $self->{extraAnswers};
  565   my $space = '<img src="about:blank" style="height:1px; width:3em; visibility:hidden" />';
  566 
  567   #
  568   #  Call the original grader, but put back the old recorded_score
  569   #  (the grader will have updated it based on the score for the PART,
  570   #  not the problem as a whole).
  571   #
  572   my $oldScore = ($_[1])->{recorded_score};
  573   my ($result,$state) = &{$self->{grader}}(@_);
  574   $state->{recorded_score} = $oldScore;
  575 
  576   #
  577   #  Update that state information and encode it.
  578   #
  579   my $status = $self->{status};
  580   $status->{raw}   = $result->{score};
  581   $status->{score} = $result->{score}*$weight;
  582   $status->{new_ans_rule_count} = $main::ans_rule_count;
  583   if (defined(%main::images_created)) {
  584     $status->{imageName} = (keys %main::images_created)[0];
  585     $status->{new_images_created} = $main::images_created{$status->{imageName}};
  586   }
  587   $status->{new_answers} = join(';',@answers);
  588   my $data = quoteHTML($self->encode);
  589 
  590   #
  591   #  Update the recorded score
  592   #
  593   my $newScore = $status->{total} + $status->{score};
  594   $state->{recorded_score} = $newScore if $newScore > $oldScore;
  595   $state->{recorded_score} = 0 if $self->{allowReset} && $main::inputs_ref->{_reset};
  596 
  597   #
  598   #  Add the compoundProblem message and data
  599   #
  600   $result->{type} = "compoundProblem ($result->{type})";
  601   $result->{msg} .= '</i><p><b>Note:</b> <i>' if $result->{msg};
  602   $result->{msg} .= 'This problem has more than one part.'
  603                  .  '<br/>'.$space.'<small>Your score for this attempt is for this part only;</small>'
  604      .  '<br/>'.$space.'<small>your overall score is for all the parts combined.</small>'
  605                  .  qq!<input type="hidden" name="_status" value="$data" />!;
  606 
  607   #
  608   #  Warn if the answers changed when they shouldn't have
  609   #
  610   $result->{msg} .= '<p><b>You may not change your answers when going on to the next part!</b>'
  611     if $self->{nextNoChange} && $self->{answersChanged};
  612 
  613   #
  614   #  Include the "next part" checkbox, button, or whatever.
  615   #
  616   my $par = 1;
  617   if ($self->{parts} > $status->{part} && !$main::inputs_ref->{previewAnswers}) {
  618     if (lc($self->{nextVisible}) eq 'always' ||
  619        (lc($self->{nextVisible}) eq 'ifcorrect' && $result->{score} >= 1)) {
  620       my $method = "next".$self->{nextStyle}; $par = 0;
  621       $result->{msg} .= $self->$method($self->{nextLabel},1).'<br/>';
  622     }
  623   }
  624 
  625   #
  626   #  Add the reset checkbox, if needed
  627   #
  628   $result->{msg} .= $self->resetCheckbox($self->{resetLabel},$par)
  629     if $self->{allowReset} && $status->{part} > 1;
  630 
  631   #
  632   #  Make sure we don't go on unless the next button really is checked
  633   #
  634   $result->{msg} .= '<input type="hidden" name="_next" value="0" />'
  635     unless $self->{nextInserted};
  636 
  637   return ($result,$state);
  638 }
  639 
  640 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9