/**
  * Traverse this node, updating the results array that is used by
  * {@link stack_potentialresponse_tree::evaluate_response()}.
  *
  * @param stack_potentialresponse_tree_state $results to be updated.
  * @param int $key the index of this node.
  * @param stack_cas_session $cascontext the CAS context that holds all the relevant variables.
  * @param stack_options $options
  * @return array with two elements, the updated $results and the index of the next node.
  */
 public function traverse($results, $key, $cascontext, $options)
 {
     $errorfree = true;
     if ($cascontext->get_errors_key('PRSANS' . $key)) {
         $results->_errors .= $cascontext->get_errors_key('PRSANS' . $key);
         $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRSANS' . ($key + 1), 'error' => $cascontext->get_errors_key('PRSANS' . $key))));
         $errorfree = false;
     }
     if ($cascontext->get_errors_key('PRTANS' . $key)) {
         $results->_errors .= $cascontext->get_errors_key('PRTANS' . $key);
         $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRTANS' . ($key + 1), 'error' => $cascontext->get_errors_key('PRTANS' . $key))));
         $errorfree = false;
     }
     if ($cascontext->get_errors_key('PRATOPT' . $key)) {
         $results->_errors .= $cascontext->get_errors_key('PRATOPT' . $key);
         $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRATOPT' . ($key + 1), 'error' => $cascontext->get_errors_key('PRATOPT' . $key))));
         $errorfree = false;
     }
     if (!$errorfree) {
         return -1;
     }
     $sans = $cascontext->get_value_key('PRSANS' . $key);
     $tans = $cascontext->get_value_key('PRTANS' . $key);
     $atopts = $cascontext->get_value_key('PRATOPT' . $key);
     // If we can't find atopts then they were not processed by the CAS.
     // They might still be some in the potential response which do not
     // need to be processed.
     if (false === $atopts) {
         $atopts = null;
     }
     $nextnode = $this->do_test($sans, $tans, $atopts, $options, $results);
     return $nextnode;
 }
 /**
  * This function actually traverses the tree and generates outcomes.
  *
  * @param stack_cas_session $questionvars the question varaibles.
  * @param stack_options $options
  * @param array $answers name => value the student response.
  * @param int $seed the random number seed.
  * @return stack_potentialresponse_tree_state the result.
  */
 public function evaluate_response(stack_cas_session $questionvars, $options, $answers, $seed)
 {
     if (empty($this->nodes)) {
         throw new stack_exception('stack_potentialresponse_tree: evaluate_response ' . 'attempting to traverse an empty tree. Something is wrong here.');
     }
     $localoptions = clone $options;
     $localoptions->set_option('simplify', $this->simplify);
     $cascontext = $this->create_cas_context_for_evaluation($questionvars, $localoptions, $answers, $seed);
     $results = new stack_potentialresponse_tree_state($this->value, true, 0, 0);
     // Traverse the tree.
     $nodekey = $this->firstnode;
     $visitednodes = array();
     while ($nodekey != -1) {
         if (!array_key_exists($nodekey, $this->nodes)) {
             throw new stack_exception('stack_potentialresponse_tree: ' . 'evaluate_response: attempted to jump to a potential response ' . 'which does not exist in this question.  This is a question ' . 'authoring/validation problem.');
         }
         if (array_key_exists($nodekey, $visitednodes)) {
             $results->add_answernote('[PRT-CIRCULARITY]=' . $nodekey);
             break;
         }
         $visitednodes[$nodekey] = true;
         $nodekey = $this->nodes[$nodekey]->traverse($results, $nodekey, $cascontext, $localoptions);
         if ($results->_errors) {
             break;
         }
     }
     // Make sure these are PHP numbers.
     $results->_score = $results->_score + 0;
     $results->_penalty = $results->_penalty + 0;
     // Restrict score to be between 0 and 1.
     $results->_score = min(max($results->_score, 0), 1);
     // From a strictly logical point of view the 'score' and the 'penalty' are independent.
     // Hence, this clause belongs in the question behaviour.
     // From a practical point of view, it is confusing/off-putting when testing to see "score=1, penalty=0.1".
     // Why does this correct attempt attract a penalty?  So, this is a unilateral decision:
     // If the score is 1 there is never a penalty.
     if ($results->_score > 0.99999995) {
         $results->_penalty = 0;
     }
     if ($results->errors) {
         $results->_score = null;
         $results->_penalty = null;
     }
     $results->set_cas_context($cascontext, $seed);
     return $results;
 }
 /**
  * Actually generate the display of the PRT feedback.
  * @param string $name the PRT name.
  * @param question_attempt $qa the question attempt to display.
  * @param question_definition $question the question being displayed.
  * @param stack_potentialresponse_tree_state $result the results to display.
  * @param question_display_options $options controls what should and should not be displayed.
  * @return string nicely formatted feedback, for display.
  */
 protected function prt_feedback_display($name, question_attempt $qa, question_definition $question, stack_potentialresponse_tree_state $result, question_display_options $options, $includestandardfeedback)
 {
     $err = '';
     if ($result->errors) {
         $err = $result->errors;
     }
     $feedback = '';
     $feedbackbits = $result->get_feedback();
     if ($feedbackbits) {
         $feedback = array();
         $format = null;
         foreach ($feedbackbits as $bit) {
             $feedback[] = $qa->rewrite_pluginfile_urls($bit->feedback, 'qtype_stack', $bit->filearea, $bit->itemid);
             if (!is_null($bit->format)) {
                 if (is_null($format)) {
                     $format = $bit->format;
                 }
                 if ($bit->format != $format) {
                     throw new coding_exception('Inconsistent feedback formats found in PRT ' . $name);
                 }
             }
         }
         if (is_null($format)) {
             $format = FORMAT_HTML;
         }
         $feedback = $result->substitue_variables_in_feedback(implode(' ', $feedback));
         $feedback = format_text(stack_maths::process_display_castext($feedback, $this), $format, array('noclean' => true, 'para' => false));
     }
     $gradingdetails = '';
     if (!$result->errors && $qa->get_behaviour_name() == 'adaptivemultipart') {
         // This is rather a hack, but it will probably work.
         $renderer = $this->page->get_renderer('qbehaviour_adaptivemultipart');
         $gradingdetails = $renderer->render_adaptive_marks($qa->get_behaviour()->get_part_mark_details($name), $options);
     }
     if ($includestandardfeedback) {
         $standardfeedback = $this->standard_prt_feedback($qa, $question, $result);
     } else {
         $standardfeedback = '';
     }
     return html_writer::nonempty_tag('div', $standardfeedback . $err . $feedback . $gradingdetails, array('class' => 'stackprtfeedback stackprtfeedback-' . $name));
 }