<commit_before><?php
/**
 * @author <NAME> and many others. <NAME>.
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 * @package questionbank
 * @subpackage questiontypes
 *//** */

require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php");

/**
 * NUMERICAL QUESTION TYPE CLASS
 *
 * This class contains some special features in order to make the
 * question type embeddable within a multianswer (cloze) question
 *
 * This question type behaves like shortanswer in most cases.
 * Therefore, it extends the shortanswer question type...
 * @package questionbank
 * @subpackage questiontypes
 */
class question_numerical_qtype extends question_shortanswer_qtype {

    public $virtualqtype = false;
    public $unitpenalty = 0;
    public $raw_unitgrade = 0 ;
    public $raw_unitpenalty = 0 ;
    public $valid_numerical_unit = true ;
    function name() {
        return 'numerical';
    }

    function has_wildcards_in_responses() {
        return true;
    }

    function requires_qtypes() {
        return array('shortanswer');
    }

    function get_question_options(&$question) {
        // Get the question answers and their respective tolerances
        // Note: question_numerical is an extension of the answer table rather than
        //       the question table as is usually the case for qtype
        //       specific tables.
        global $CFG, $DB, $OUTPUT;
        if (!$question->options->answers = $DB->get_records_sql(
                                "SELECT a.*, n.tolerance " .
                                "FROM {question_answers} a, " .
                                "     {question_numerical} n " .
                                "WHERE a.question = ? " .
                                "    AND   a.id = n.answer " .
                                "ORDER BY a.id ASC", array($question->id))) {
            echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!');
            return false;
        }
        $this->get_numerical_units($question);
        //get_numerical_options() need to know if there are units
        // to set correctly default values 
        $this->get_numerical_options($question);

        // If units are defined we strip off the default unit from the answer, if
        // it is present. (Required for compatibility with the old code and DB).
        if ($defaultunit = $this->get_default_numerical_unit($question)) {
            foreach($question->options->answers as $key => $val) {
                $answer = trim($val->answer);
                $length = strlen($defaultunit->unit);
                if ($length && substr($answer, -$length) == $defaultunit->unit) {
                    $question->options->answers[$key]->answer =
                            substr($answer, 0, strlen($answer)-$length);
                }
            }
        }

        return true;
    }
    function get_numerical_options(&$question) {
        global $DB;
        if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) {
            $question->options->unitgradingtype = 0;
            $question->options->unitpenalty = 0;
            // the default 
            if ($defaultunit = $this->get_default_numerical_unit($question)) {
                // so units can be graded
                $question->options->showunits = 0 ;
            }else {
                // only numerical will be graded
                $question->options->showunits = 3 ;
            }
            $question->options->unitsleft = 0 ;
            $question->options->instructions = '' ;
        } else {
            $question->options->unitgradingtype = $options->unitgradingtype;
            $question->options->unitpenalty = $options->unitpenalty;
            $question->options->showunits = $options->showunits ;
            $question->options->unitsleft = $options->unitsleft ;
            $question->options->instructions = $options->instructions ;
        }


        return true;
    }
    function get_numerical_units(&$question) {
        global $DB;
        if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) {
            $units  = array_values($units);
        } else {
            $units = array();
        }
        foreach ($units as $key => $unit) {
            $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_NUMBER);
        }
        $question->options->units = $units;
        return true;
    }

    function get_default_numerical_unit(&$question) {
        if (isset($question->options->units[0])) {
            foreach ($question->options->units as $unit) {
                if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) {
                    return $unit;
                }
            }
        }
        return false;
    }

    /**
     * Save the units and the answers associated with this question.
     */
    function save_question_options($question) {
        global $DB;
        // Get old versions of the objects
        if (!$oldanswers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
            $oldanswers = array();
        }

        if (!$oldoptions = $DB->get_records('question_numerical', array('question' =>  $question->id), 'answer ASC')) {
            $oldoptions = array();
        }

        // Save the units.
        $result = $this->save_numerical_units($question);
        if (isset($result->error)) {
            return $result;
        } else {
            $units = &$result->units;
        }

        // Insert all the new answers
        foreach ($question->answer as $key => $dataanswer) {
            // Check for, and ingore, completely blank answer from the form.
            if (trim($dataanswer) == '' && $question->fraction[$key] == 0 &&
                    html_is_blank($question->feedback[$key])) {
                continue;
            }

            $answer = new stdClass;
            $answer->question = $question->id;
            if (trim($dataanswer) === '*') {
                $answer->answer = '*';
            } else {
                $answer->answer = $this->apply_unit($dataanswer, $units);
                if ($answer->answer === false) {
                    $result->notice = get_string('invalidnumericanswer', 'quiz');
                }
            }
            $answer->fraction = $question->fraction[$key];
            $answer->feedback = trim($question->feedback[$key]);

            if ($oldanswer = array_shift($oldanswers)) {  // Existing answer, so reuse it
                $answer->id = $oldanswer->id;
                $DB->update_record("question_answers", $answer);
            } else { // This is a completely new answer
                $answer->id = $DB->insert_record("question_answers", $answer);
            }

            // Set up the options object
            if (!$options = array_shift($oldoptions)) {
                $options = new stdClass;
            }
            $options->question  = $question->id;
            $options->answer    = $answer->id;
            if (trim($question->tolerance[$key]) == '') {
                $options->tolerance = '';
            } else {
                $options->tolerance = $this->apply_unit($question->tolerance[$key], $units);
                if ($options->tolerance === false) {
                    $result->notice = get_string('invalidnumerictolerance', 'quiz');
                }
            }

            // Save options
            if (isset($options->id)) { // reusing existing record
                $DB->update_record('question_numerical', $options);
            } else { // new options
                $DB->insert_record('question_numerical', $options);
            }
        }
        // delete old answer records
        if (!empty($oldanswers)) {
            foreach($oldanswers as $oa) {
                $DB->delete_records('question_answers', array('id' => $oa->id));
            }
        }

        // delete old answer records
        if (!empty($oldoptions)) {
            foreach($oldoptions as $oo) {
                $DB->delete_records('question_numerical', array('id' => $oo->id));
            }
        }
        $result = $this->save_numerical_options($question);
        if (isset($result->error)) {
            return $result;
        }
        // Report any problems.
        if (!empty($result->notice)) {
            return $result;
        }
        return true;
    }

    function save_numerical_options(&$question) {
        global $DB;
        $result = new stdClass;
        // numerical options
        $update = true ;
        $options = $DB->get_record("question_numerical_options", array("question" => $question->id));
        if (!$options) {
            $update = false;
            $options = new stdClass;
            $options->question = $question->id;
        }
        if(isset($question->unitgradingtype)){
            $options->unitgradingtype = $question->unitgradingtype;
        }else {
            $options->unitgradingtype = 0 ;
        }
        if(isset($question->unitpenalty)){
            $options->unitpenalty = $question->unitpenalty;
        }else {
            $options->unitpenalty = 0 ;
        }
        // if we came from the form then 'unitrole' exists
        if(isset($question->unitrole)){
            if ($question->unitrole == 0 ){
                $options->showunits = $question->showunits0;
            }else {
                $options->showunits = $question->showunits1;
            }
        }else {                
            if(isset($question->showunits)){
                $options->showunits = $question->showunits;
            }else {
                if ($defaultunit = $this->get_default_numerical_unit($question)) {
                    // so units can be graded
                    $options->showunits = 0 ;
                }else {
                    // only numerical will be graded
                    $options->showunits = 3 ;
                }
            }
        }
        if(isset($question->unitsleft)){
            $options->unitsleft = $question->unitsleft;
        }else {
            $options->unitsleft = 0 ;
        }
        if(isset($question->instructions)){
            $options->instructions = trim($question->instructions);
        }else {
            $options->instructions = '' ;
        }
        if ($update) {
            if (!$DB->update_record("question_numerical_options", $options)) {
                $result->error = "Could not update numerical question options! (id=$options->id)";
                return $result;
            }
        } else {
            if (!$DB->insert_record("question_numerical_options", $options)) {
                $result->error = "Could not insert numerical question options!";
                return $result;
            }
        }
        return $result;
    }

    function save_numerical_units($question) {
        global $DB;
        $result = new stdClass;

        // Delete the units previously saved for this question.
        $DB->delete_records('question_numerical_units', array('question' => $question->id));

        // Nothing to do.
        if (!isset($question->multiplier)) {
            $result->units = array();
            return $result;
        }

        // Save the new units.
        $units = array();
        $unitalreadyinsert = array();
        foreach ($question->multiplier as $i => $multiplier) {
            // Discard any unit which doesn't specify the unit or the multiplier
            if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])&& !array_key_exists(addslashes($question->unit[$i]),$unitalreadyinsert)) {
                $unitalreadyinsert[addslashes($question->unit[$i])] = 1 ;
                $units[$i] = new stdClass;
                $units[$i]->question = $question->id;
                $units[$i]->multiplier = $this->apply_unit($question->multiplier[$i], array());
                $units[$i]->unit = $question->unit[$i];
                $DB->insert_record('question_numerical_units', $units[$i]);
            }
        }
        unset($question->multiplier, $question->unit);

        $result->units = &$units;
        return $result;
    }

    function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
        $state->responses = array();
        $state->responses['answer'] =  '';
        $state->responses['unit'] = '';
     /*   if ($question->options->showunits == 1){
            $state->responses['unit'] = '0';
        }*/

        return true;
    }
    function restore_session_and_responses(&$question, &$state) {
       if(false === strpos($state->responses[''], '|||||')){
        // temporary
             $state->responses['answer']= $state->responses[''];
             $state->responses['unit'] = '';
             $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
       }else {
            $responses = explode('|||||', $state->responses['']);
            $state->responses['answer']= $responses[0];
            $state->responses['unit'] = $responses[1];
       }
      //                             echo "<p> restore response $responses ||   <pre>";print_r($state);echo "</pre></p>";

      /*
       if ($question->options->showunits == 1 && isset($question->options->units)){
            $state->responses['unit']=$this->find_unit_index($question,$state->responses['unit']);
       }*/
       return true;
    }

    function find_unit_index(&$question,$value){
            $length = 0;
            $goodkey = 0 ;
            foreach ($question->options->units as $key => $unit){
                    if($unit->unit ==$value ) {
                    return $key ;
                }
            }
        return 0 ;
    }

    function split_old_answer($rawresponse, $units, &$answer ,&$unit ) {
        $answer = $rawresponse ;
        // remove spaces and normalise decimal places.
        $search  = array(' ', ',');
        $replace = array('', '.');
        $rawresponse = str_replace($search, $replace, trim($rawresponse));
        if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
                $rawresponse, $responseparts)) {
            $unit = $responseparts[5] ;
            $answer = $responseparts[1] ;
        }
        return ;
    }


    function save_session_and_responses(&$question, &$state) {
        global $DB;
        //                            echo "<p> save session   <pre>";print_r($state);echo "</pre></p>";

        $responses = '';
        if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
            $responses = $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit;
        }else if(isset($state->responses['unit'])){
            $responses = $state->responses['answer'].'|||||'.$state->responses['unit'] ;
        }else {
            $responses = $state->responses['answer'].'|||||';
        }
        // Set the legacy answer field
        if (!$DB->set_field('question_states', 'answer', $responses, array('id' => $state->id))) {
            return false;
        }
        return true;
    }

/**
     * Deletes question from the question-type specific tables
     *
     * @return boolean Success/Failure
     * @param object $question  The question being deleted
     */
    function delete_question($questionid) {
        global $DB;
        $DB->delete_records("question_numerical", array("question" => $questionid));
        $DB->delete_records("question_numerical_options", array("question" => $questionid));
        $DB->delete_records("question_numerical_units", array("question" => $questionid));
        return true;
    }


    function compare_responses(&$question, $state, $teststate) {
               if ($question->options->showunits == 1 && isset($question->options->units) && isset($question->options->units[$state->responses['unit']] )){
            $state->responses['unit']=$question->options->units[$state->responses['unit']]->unit;
        };


        $responses = '';
        $testresponses = '';
        if (isset($state->responses['answer'])){
            $responses = $state->responses['answer'];
        }
        if (isset($state->responses['unit'])){
            $responses .= $state->responses['unit'];
        }
        if (isset($teststate->responses['answer'])){
            $testresponses = $teststate->responses['answer'];
        }
        if (isset($teststate->responses['unit'])){
            $testresponses .= $teststate->responses['unit'];
        }
     //    echo "<p> compare response $responses || $testresponses  <pre>";print_r($state);echo "</pre></p>";

        if ( isset($responses)  && isset($testresponses )) {

            return $responses == $testresponses ;
        }
        return false;
    }

    /**
     * Checks whether a response matches a given answer, taking the tolerance
     * and units into account. Returns a true for if a response matches the
     * answer, false if it doesn't.
     */
    function test_response(&$question, &$state, $answer) {
        // Deal with the match anything answer.
        if ($answer->answer === '*') {
            return true;
        }
         //       echo "<p> test response numerical state  <pre>";print_r($state);echo "</pre></p>";
        //        echo "<p> test response numerical question  <pre>";print_r($question);echo "</pre></p>";

       if( isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
            $state->responses['']=$state->responses['answer'].$question->options->units[$state->responses['unit']]->unit;
           //  echo "<p>test responses valid unit </p>";
        }else if(isset($state->responses['unit'])){
            $state->responses['']= $state->responses['answer'].$state->responses['unit'] ;// why?
        }
         //       echo "<p> test response numerical state  before apply <pre>";print_r($state);echo "</pre></p>";

        $response = $this->apply_unit($state->responses[''], $question->options->units);
                     //  $this->valid_numerical_unit = $this->valid_unit($state->responses[''], $question->options->units);
     //  if ($this->valid_numerical_unit) echo "<p>test responses valid unit </p>";
     //  if (!$this->valid_numerical_unit) echo "<p>test responses not valid unit </p>";
       // echo "<p>state response test $response <pre>";print_r($state);echo "</pre></p>";
       // $this->raw_unitpenalty = 0.1 ;

        if ($response === false) {
            return false; // The student did not type a number.
        }

        // The student did type a number, so check it with tolerances.
        $this->get_tolerance_interval($answer);
        return ($answer->min <= $response && $response <= $answer->max);
    }

    function grade_responses(&$question, &$state, $cmoptions) {
        // The default implementation uses the test_response method to
        // compare what the student entered against each of the possible
        // answers stored in the question, and uses the grade from the
        // first one that matches. It also sets the marks and penalty.
        // This should be good enought for most simple question types.
          //     echo "<p>grade responses  <pre>";print_r($state->responses);echo "</pre></p>";

        //first the split response from unit choice display is converted as
        // standard numerical response value.unit
  /*      if (!empty($question->options->showunits) && isset($state->responses['unit'])){
            $state->responses[''] .= $question->options->units[$state->responses['unit']]->unit ;
        }
  */
        //to apply the unit penalty we need to analyse the response in a more complex way
        //the apply_unit() function analysis could be used to obtain the infos
        // however it is used to detect good or bad numbers but also
        // gives false
        $state->raw_grade = 0;
        foreach($question->options->answers as $answer) {
            if($this->test_response($question, $state, $answer)) {
                $state->raw_grade = $answer->fraction;
                $this->raw_unitgrade = $answer->fraction;
                if( $question->options->showunits == 3) {
                    $this->valid_numerical_unit == true ;
                }else {
                $this->valid_numerical_unit = $this->valid_unit($state->responses[''], $question->options->units);
            }
                break;
            }
        }
     //  if ($this->valid_numerical_unit) echo "<p>grade responses valid unit </p>";
     //  if (!$this->valid_numerical_unit) echo "<p>grade responses not valid unit </p>";
        // apply unit penalty
        $this->raw_unitpenalty = 0 ;
        if(!empty($question->options->unitpenalty)&& !$this->valid_numerical_unit ){
            if($question->options->unitgradingtype == 1){
                $this->raw_unitpenalty = $question->options->unitpenalty* $state->raw_grade ;
            }else {
                $this->raw_unitpenalty = $question->options->unitpenalty* $question->maxgrade;
            }
            $state->raw_grade -= $question->options->unitpenalty ;
        }
      //  echo "<p>grade responses  <pre>";print_r($state->responses);echo "</pre></p>";

       // $this->raw_unitpenalty = $question->options->unitpenalty ;
        // Make sure we don't assign negative or too high marks.
     //   $state->raw_grade -= $question->options->unitpenalty ;
        $state->raw_grade = min(max((float) $state->raw_grade,
                            0.0), 1.0) * $question->maxgrade;

        // Update the penalty.
        $state->penalty = $question->penalty * $question->maxgrade;<commit_msg>MDL-20296 testing units at each question submit<commit_after><?php
/**
 * @author <NAME> and many others. <NAME>.
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 * @package questionbank
 * @subpackage questiontypes
 *//** */

require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php");

/**
 * NUMERICAL QUESTION TYPE CLASS
 *
 * This class contains some special features in order to make the
 * question type embeddable within a multianswer (cloze) question
 *
 * This question type behaves like shortanswer in most cases.
 * Therefore, it extends the shortanswer question type...
 * @package questionbank
 * @subpackage questiontypes
 */
class question_numerical_qtype extends question_shortanswer_qtype {

    public $virtualqtype = false;
    public $unitpenalty = 0;
    public $raw_unitgrade = 0 ;
    public $raw_unitpenalty = 0 ;
    public $valid_numerical_unit = true ;
    function name() {
        return 'numerical';
    }

    function has_wildcards_in_responses() {
        return true;
    }

    function requires_qtypes() {
        return array('shortanswer');
    }

    function get_question_options(&$question) {
        // Get the question answers and their respective tolerances
        // Note: question_numerical is an extension of the answer table rather than
        //       the question table as is usually the case for qtype
        //       specific tables.
        global $CFG, $DB, $OUTPUT;
        if (!$question->options->answers = $DB->get_records_sql(
                                "SELECT a.*, n.tolerance " .
                                "FROM {question_answers} a, " .
                                "     {question_numerical} n " .
                                "WHERE a.question = ? " .
                                "    AND   a.id = n.answer " .
                                "ORDER BY a.id ASC", array($question->id))) {
            echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!');
            return false;
        }
        $this->get_numerical_units($question);
        //get_numerical_options() need to know if there are units
        // to set correctly default values 
        $this->get_numerical_options($question);

        // If units are defined we strip off the default unit from the answer, if
        // it is present. (Required for compatibility with the old code and DB).
        if ($defaultunit = $this->get_default_numerical_unit($question)) {
            foreach($question->options->answers as $key => $val) {
                $answer = trim($val->answer);
                $length = strlen($defaultunit->unit);
                if ($length && substr($answer, -$length) == $defaultunit->unit) {
                    $question->options->answers[$key]->answer =
                            substr($answer, 0, strlen($answer)-$length);
                }
            }
        }

        return true;
    }
    function get_numerical_options(&$question) {
        global $DB;
        if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) {
            $question->options->unitgradingtype = 0;
            $question->options->unitpenalty = 0;
            // the default 
            if ($defaultunit = $this->get_default_numerical_unit($question)) {
                // so units can be graded
                $question->options->showunits = 0 ;
            }else {
                // only numerical will be graded
                $question->options->showunits = 3 ;
            }
            $question->options->unitsleft = 0 ;
            $question->options->instructions = '' ;
        } else {
            $question->options->unitgradingtype = $options->unitgradingtype;
            $question->options->unitpenalty = $options->unitpenalty;
            $question->options->showunits = $options->showunits ;
            $question->options->unitsleft = $options->unitsleft ;
            $question->options->instructions = $options->instructions ;
        }


        return true;
    }
    function get_numerical_units(&$question) {
        global $DB;
        if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) {
            $units  = array_values($units);
        } else {
            $units = array();
        }
        foreach ($units as $key => $unit) {
            $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_NUMBER);
        }
        $question->options->units = $units;
        return true;
    }

    function get_default_numerical_unit(&$question) {
        if (isset($question->options->units[0])) {
            foreach ($question->options->units as $unit) {
                if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) {
                    return $unit;
                }
            }
        }
        return false;
    }

    /**
     * Save the units and the answers associated with this question.
     */
    function save_question_options($question) {
        global $DB;
        // Get old versions of the objects
        if (!$oldanswers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
            $oldanswers = array();
        }

        if (!$oldoptions = $DB->get_records('question_numerical', array('question' =>  $question->id), 'answer ASC')) {
            $oldoptions = array();
        }

        // Save the units.
        $result = $this->save_numerical_units($question);
        if (isset($result->error)) {
            return $result;
        } else {
            $units = &$result->units;
        }

        // Insert all the new answers
        foreach ($question->answer as $key => $dataanswer) {
            // Check for, and ingore, completely blank answer from the form.
            if (trim($dataanswer) == '' && $question->fraction[$key] == 0 &&
                    html_is_blank($question->feedback[$key])) {
                continue;
            }

            $answer = new stdClass;
            $answer->question = $question->id;
            if (trim($dataanswer) === '*') {
                $answer->answer = '*';
            } else {
                $answer->answer = $this->apply_unit($dataanswer, $units);
                if ($answer->answer === false) {
                    $result->notice = get_string('invalidnumericanswer', 'quiz');
                }
            }
            $answer->fraction = $question->fraction[$key];
            $answer->feedback = trim($question->feedback[$key]);

            if ($oldanswer = array_shift($oldanswers)) {  // Existing answer, so reuse it
                $answer->id = $oldanswer->id;
                $DB->update_record("question_answers", $answer);
            } else { // This is a completely new answer
                $answer->id = $DB->insert_record("question_answers", $answer);
            }

            // Set up the options object
            if (!$options = array_shift($oldoptions)) {
                $options = new stdClass;
            }
            $options->question  = $question->id;
            $options->answer    = $answer->id;
            if (trim($question->tolerance[$key]) == '') {
                $options->tolerance = '';
            } else {
                $options->tolerance = $this->apply_unit($question->tolerance[$key], $units);
                if ($options->tolerance === false) {
                    $result->notice = get_string('invalidnumerictolerance', 'quiz');
                }
            }

            // Save options
            if (isset($options->id)) { // reusing existing record
                $DB->update_record('question_numerical', $options);
            } else { // new options
                $DB->insert_record('question_numerical', $options);
            }
        }
        // delete old answer records
        if (!empty($oldanswers)) {
            foreach($oldanswers as $oa) {
                $DB->delete_records('question_answers', array('id' => $oa->id));
            }
        }

        // delete old answer records
        if (!empty($oldoptions)) {
            foreach($oldoptions as $oo) {
                $DB->delete_records('question_numerical', array('id' => $oo->id));
            }
        }
        $result = $this->save_numerical_options($question);
        if (isset($result->error)) {
            return $result;
        }
        // Report any problems.
        if (!empty($result->notice)) {
            return $result;
        }
        return true;
    }

    function save_numerical_options(&$question) {
        global $DB;
        $result = new stdClass;
        // numerical options
        $update = true ;
        $options = $DB->get_record("question_numerical_options", array("question" => $question->id));
        if (!$options) {
            $update = false;
            $options = new stdClass;
            $options->question = $question->id;
        }
        if(isset($question->unitgradingtype)){
            $options->unitgradingtype = $question->unitgradingtype;
        }else {
            $options->unitgradingtype = 0 ;
        }
        if(isset($question->unitpenalty)){
            $options->unitpenalty = $question->unitpenalty;
        }else {
            $options->unitpenalty = 0 ;
        }
        // if we came from the form then 'unitrole' exists
        if(isset($question->unitrole)){
            if ($question->unitrole == 0 ){
                $options->showunits = $question->showunits0;
            }else {
                $options->showunits = $question->showunits1;
            }
        }else {                
            if(isset($question->showunits)){
                $options->showunits = $question->showunits;
            }else {
                if ($defaultunit = $this->get_default_numerical_unit($question)) {
                    // so units can be graded
                    $options->showunits = 0 ;
                }else {
                    // only numerical will be graded
                    $options->showunits = 3 ;
                }
            }
        }
        if(isset($question->unitsleft)){
            $options->unitsleft = $question->unitsleft;
        }else {
            $options->unitsleft = 0 ;
        }
        if(isset($question->instructions)){
            $options->instructions = trim($question->instructions);
        }else {
            $options->instructions = '' ;
        }
        if ($update) {
            if (!$DB->update_record("question_numerical_options", $options)) {
                $result->error = "Could not update numerical question options! (id=$options->id)";
                return $result;
            }
        } else {
            if (!$DB->insert_record("question_numerical_options", $options)) {
                $result->error = "Could not insert numerical question options!";
                return $result;
            }
        }
        return $result;
    }

    function save_numerical_units($question) {
        global $DB;
        $result = new stdClass;

        // Delete the units previously saved for this question.
        $DB->delete_records('question_numerical_units', array('question' => $question->id));

        // Nothing to do.
        if (!isset($question->multiplier)) {
            $result->units = array();
            return $result;
        }

        // Save the new units.
        $units = array();
        $unitalreadyinsert = array();
        foreach ($question->multiplier as $i => $multiplier) {
            // Discard any unit which doesn't specify the unit or the multiplier
            if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])&& !array_key_exists(addslashes($question->unit[$i]),$unitalreadyinsert)) {
                $unitalreadyinsert[addslashes($question->unit[$i])] = 1 ;
                $units[$i] = new stdClass;
                $units[$i]->question = $question->id;
                $units[$i]->multiplier = $this->apply_unit($question->multiplier[$i], array());
                $units[$i]->unit = $question->unit[$i];
                $DB->insert_record('question_numerical_units', $units[$i]);
            }
        }
        unset($question->multiplier, $question->unit);

        $result->units = &$units;
        return $result;
    }

    function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
        $state->responses = array();
        $state->responses['answer'] =  '';
        $state->responses['unit'] = '';
     /*   if ($question->options->showunits == 1){
            $state->responses['unit'] = '0';
        }*/

        return true;
    }
    function restore_session_and_responses(&$question, &$state) {
       if(false === strpos($state->responses[''], '|||||')){
        // temporary
             $state->responses['answer']= $state->responses[''];
             $state->responses['unit'] = '';
             $this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
       }else {
            $responses = explode('|||||', $state->responses['']);
            $state->responses['answer']= $responses[0];
            $state->responses['unit'] = $responses[1];
       }
      //                             echo "<p> restore response $responses ||   <pre>";print_r($state);echo "</pre></p>";

      /*
       if ($question->options->showunits == 1 && isset($question->options->units)){
            $state->responses['unit']=$this->find_unit_index($question,$state->responses['unit']);
       }*/
       return true;
    }

    function find_unit_index(&$question,$value){
            $length = 0;
            $goodkey = 0 ;
            foreach ($question->options->units as $key => $unit){
                    if($unit->unit ==$value ) {
                    return $key ;
                }
            }
        return 0 ;
    }

    function split_old_answer($rawresponse, $units, &$answer ,&$unit ) {
        $answer = $rawresponse ;
        // remove spaces and normalise decimal places.
        $search  = array(' ', ',');
        $replace = array('', '.');
        $rawresponse = str_replace($search, $replace, trim($rawresponse));
        if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
                $rawresponse, $responseparts)) {
            $unit = $responseparts[5] ;
            $answer = $responseparts[1] ;
        }
        return ;
    }


    function save_session_and_responses(&$question, &$state) {
        global $DB;
        //                            echo "<p> save session   <pre>";print_r($state);echo "</pre></p>";

        $responses = '';
        if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
            $responses = $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit;
        }else if(isset($state->responses['unit'])){
            $responses = $state->responses['answer'].'|||||'.$state->responses['unit'] ;
        }else {
            $responses = $state->responses['answer'].'|||||';
        }
        // Set the legacy answer field
        if (!$DB->set_field('question_states', 'answer', $responses, array('id' => $state->id))) {
            return false;
        }
        return true;
    }

/**
     * Deletes question from the question-type specific tables
     *
     * @return boolean Success/Failure
     * @param object $question  The question being deleted
     */
    function delete_question($questionid) {
        global $DB;
        $DB->delete_records("question_numerical", array("question" => $questionid));
        $DB->delete_records("question_numerical_options", array("question" => $questionid));
        $DB->delete_records("question_numerical_units", array("question" => $questionid));
        return true;
    }


    function compare_responses(&$question, $state, $teststate) {
               if ($question->options->showunits == 1 && isset($question->options->units) && isset($question->options->units[$state->responses['unit']] )){
            $state->responses['unit']=$question->options->units[$state->responses['unit']]->unit;
        };


        $responses = '';
        $testresponses = '';
        if (isset($state->responses['answer'])){
            $responses = $state->responses['answer'];
        }
        if (isset($state->responses['unit'])){
            $responses .= $state->responses['unit'];
        }
        if (isset($teststate->responses['answer'])){
            $testresponses = $teststate->responses['answer'];
        }
        if (isset($teststate->responses['unit'])){
            $testresponses .= $teststate->responses['unit'];
        }
         // echo "<p> compare response $responses || $testresponses  <pre>";print_r($state);echo "</pre></p>";

        if ( isset($responses)  && isset($testresponses )) {

            return $responses == $testresponses ;
        }
        return false;
    }

    /**
     * Checks whether a response matches a given answer, taking the tolerance
     * and units into account. Returns a true for if a response matches the
     * answer, false if it doesn't.
     */
    function test_response(&$question, &$state, $answer) {
        // Deal with the match anything answer.
        if ($answer->answer === '*') {
            return true;
        }
        //       echo "<p> test response numerical state  <pre>";print_r($state);echo "</pre></p>";
        //        echo "<p> test response numerical question  <pre>";print_r($question);echo "</pre></p>";

       if( isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
            $state->responses['']=$state->responses['answer'].$question->options->units[$state->responses['unit']]->unit;
           //  echo "<p>test responses valid unit </p>";
        }else if(isset($state->responses['unit'])){
            $state->responses['']= $state->responses['answer'].$state->responses['unit'] ;// why?
        }
         //     echo "<p> test response numerical state  before apply unit <pre>";print_r($state);echo "</pre></p>";

        $response = $this->apply_unit($state->responses[''], $question->options->units);
                     //  $this->valid_numerical_unit = $this->valid_unit($state->responses[''], $question->options->units);
     //  if ($this->valid_numerical_unit) echo "<p>test responses valid unit </p>";
     //  if (!$this->valid_numerical_unit) echo "<p>test responses not valid unit </p>";
     //   echo "<p>state response test $response after apply unit <pre>";print_r($state);echo "</pre></p>";
       // $this->raw_unitpenalty = 0.1 ;

        if ($response === false) {
            return false; // The student did not type a number.
        }

        // The student did type a number, so check it with tolerances.
        $this->get_tolerance_interval($answer);
        return ($answer->min <= $response && $response <= $answer->max);
    }

    function grade_responses(&$question, &$state, $cmoptions) {
        // The default implementation uses the test_response method to
        // compare what the student entered against each of the possible
        // answers stored in the question, and uses the grade from the
        // first one that matches. It also sets the marks and penalty.
        // This should be good enought for most simple question types.
          //     echo "<p>grade responses  <pre>";print_r($state->responses);echo "</pre></p>";

        //first the split response from unit choice display is converted as
        // standard numerical response value.unit
  /*      if (!empty($question->options->showunits) && isset($state->responses['unit'])){
            $state->responses[''] .= $question->options->units[$state->responses['unit']]->unit ;
        }
  */
        //to apply the unit penalty we need to analyse the response in a more complex way
        //the apply_unit() function analysis could be used to obtain the infos
        // however it is used to detect good or bad numbers but also
        // gives false
        $state->raw_grade = 0;
        foreach($question->options->answers as $answer) {
            if($this->test_response($question, $state, $answer)) {
                $state->raw_grade = $answer->fraction;
                $this->raw_unitgrade = $answer->fraction;
                break;
            }
        }
        // in all cases the unit should be tested
        if( $question->options->showunits == 3) {
            $this->valid_numerical_unit == true ;
        }else {
            $this->valid_numerical_unit = $this->valid_unit($state->responses[''], $question->options->units);
        }
      // if ($this->valid_numerical_unit) echo "<p>grade responses valid unit </p>";
      // if (!$this->valid_numerical_unit) echo "<p>grade responses not valid unit </p>";
        // apply unit penalty
        $this->raw_unitpenalty = 0 ;
        if(!empty($question->options->unitpenalty)&& !$this->valid_numerical_unit ){
            if($question->options->unitgradingtype == 1){
                $this->raw_unitpenalty = $question->options->unitpenalty* $state->raw_grade ;
            }else {
                $this->raw_unitpenalty = $question->options->unitpenalty* $question->maxgrade;
            }
            $state->raw_grade -= $question->options->unitpenalty ;
        }
      //  echo "<p>grade responses  <pre>";print_r($state->responses);echo "</pre></p>";

       // $this->raw_unitpenalty = $question->options->unitpenalty ;
        // Make sure we don't assign negative or too high marks.
     //   $state->raw_grade -= $question->options->unitpenalty ;
        $state->raw_grade = min(max((float) $state->raw_grade,
                            0.0), 1.0) * $question->maxgrade;

        // Update the penalty.
        $state->penalty = $question->penalty * $question->maxgrade;
{'fraction_non_alphanumeric': 0.15431817144942744, 'fraction_numerical': 0.0026232948583420775, 'mean_word_length': 2.0012322858903264, 'pattern_counts': {'":': 0, '<': 107, '<?xml version=': 0, '>': 767, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2259529', 'n_tokens_mistral': 12099, 'n_tokens_neox': 11690, 'n_words': 2569}