Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * Defines the editing form for calculated multiple-choice questions.
      19   *
      20   * @package    qtype
      21   * @subpackage calculatedmulti
      22   * @copyright  2007 Jamie Pratt me@jamiep.org
      23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  
      27  defined('MOODLE_INTERNAL') || die();
      28  
      29  
      30  /**
      31   * Calculated multiple-choice question editing form.
      32   *
      33   * @copyright  2007 Jamie Pratt me@jamiep.org
      34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      35   */
      36  class qtype_calculatedmulti_edit_form extends question_edit_form {
      37      /**
      38       * Handle to the question type for this question.
      39       *
      40       * @var question_calculatedmulti_qtype
      41       */
      42      public $qtypeobj;
      43      public $questiondisplay;
      44      public $initialname = '';
      45      public $reload = false;
      46  
      47      public function __construct($submiturl, $question, $category,
      48              $contexts, $formeditable = true) {
      49          $this->question = $question;
      50          $this->qtypeobj = question_bank::get_qtype('calculatedmulti');
      51          $this->reload = optional_param('reload', false, PARAM_BOOL);
      52          if (!$this->reload) {
      53              // Use database data as this is first pass.
      54              if (isset($this->question->id)) {
      55                  // Remove prefix #{..}# if exists.
      56                  $this->initialname = $question->name;
      57                  $regs= array();
      58                  if (preg_match('~#\{([^[:space:]]*)#~', $question->name , $regs)) {
      59                      $question->name = str_replace($regs[0], '', $question->name);
      60                  };
      61              }
      62          }
      63          parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
      64      }
      65  
      66      protected function can_preview() {
      67          return false; // Generally not possible for calculated multi-choice questions on this page.
      68      }
      69  
      70      public function get_per_answer_fields($mform, $label, $gradeoptions,
      71              &$repeatedoptions, &$answersoption) {
      72          $repeated = array();
      73          $answeroptions = array();
      74          $answeroptions[] = $mform->createElement('text', 'answer',
      75                  $label, array('size' => 50));
      76          $answeroptions[] = $mform->createElement('select', 'fraction',
      77                  get_string('grade'), $gradeoptions);
      78          $repeated[] = $mform->createElement('group', 'answeroptions',
      79                   $label, $answeroptions, null, false);
      80  
      81          // Added answeroptions help button in definition_inner() after called to add_per_answer_fields.
      82  
      83          $repeatedoptions['answer']['type'] = PARAM_RAW;
      84          $repeatedoptions['fraction']['default'] = 0;
      85          $answersoption = 'answers';
      86  
      87          $mform->setType('answer', PARAM_NOTAGS);
      88  
      89          $repeated[] = $mform->createElement('hidden', 'tolerance');
      90          $repeated[] = $mform->createElement('hidden', 'tolerancetype', 1);
      91          $repeatedoptions['tolerance']['type'] = PARAM_FLOAT;
      92          $repeatedoptions['tolerance']['default'] = 0.01;
      93          $repeatedoptions['tolerancetype']['type'] = PARAM_INT;
      94  
      95          // Create display group.
      96          $answerdisplay = array();
      97          $answerdisplay[] =  $mform->createElement('select', 'correctanswerlength',
      98                  get_string('answerdisplay', 'qtype_calculated'), range(0, 9));
      99          $repeatedoptions['correctanswerlength']['default'] = 2;
     100  
     101          $answerlengthformats = array(
     102              '1' => get_string('decimalformat', 'qtype_numerical'),
     103              '2' => get_string('significantfiguresformat', 'qtype_calculated')
     104          );
     105          $answerdisplay[] = $mform->createElement('select', 'correctanswerformat',
     106                  get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
     107          $repeated[] = $mform->createElement('group', 'answerdisplay',
     108                   get_string('answerdisplay', 'qtype_calculated'), $answerdisplay, null, false);
     109  
     110          // Add feedback.
     111          $repeated[] = $mform->createElement('editor', 'feedback',
     112                  get_string('feedback', 'question'), null, $this->editoroptions);
     113  
     114          return $repeated;
     115      }
     116  
     117      protected function definition_inner($mform) {
     118  
     119          $label = get_string('sharedwildcards', 'qtype_calculated');
     120          $mform->addElement('hidden', 'initialcategory', 1);
     121          $mform->addElement('hidden', 'reload', 1);
     122          $mform->setType('initialcategory', PARAM_INT);
     123          $mform->setType('reload', PARAM_BOOL);
     124  
     125          $html2 = '';
     126          $mform->insertElementBefore(
     127                  $mform->createElement('static', 'listcategory', $label, $html2), 'name');
     128          if (isset($this->question->id)) {
     129              $mform->insertElementBefore($mform->createElement('static', 'initialname',
     130                      get_string('questionstoredname', 'qtype_calculated'),
     131                      $this->initialname), 'name');
     132          };
     133          $addfieldsname = 'updatecategory';
     134          $addstring = get_string('updatecategory', 'qtype_calculated');
     135          $mform->registerNoSubmitButton($addfieldsname);
     136          $this->editasmultichoice = 1;
     137  
     138          $mform->insertElementBefore(
     139                  $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
     140          $mform->registerNoSubmitButton('createoptionbutton');
     141          $mform->addElement('hidden', 'multichoice', $this->editasmultichoice);
     142          $mform->setType('multichoice', PARAM_INT);
     143  
     144          $menu = array(get_string('answersingleno', 'qtype_multichoice'),
     145                  get_string('answersingleyes', 'qtype_multichoice'));
     146          $mform->addElement('select', 'single',
     147                  get_string('answerhowmany', 'qtype_multichoice'), $menu);
     148          $mform->setDefault('single', 1);
     149  
     150          $mform->addElement('advcheckbox', 'shuffleanswers',
     151                  get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0, 1));
     152          $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
     153          $mform->setDefault('shuffleanswers', 1);
     154  
     155          $numberingoptions = question_bank::get_qtype('multichoice')->get_numbering_styles();
     156          $mform->addElement('select', 'answernumbering',
     157                  get_string('answernumbering', 'qtype_multichoice'), $numberingoptions);
     158          $mform->setDefault('answernumbering', 'abc');
     159  
     160          $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
     161                  question_bank::fraction_options_full(), max(5, QUESTION_NUMANS_START));
     162          $mform->addHelpButton('answeroptions[0]', 'answeroptions', 'qtype_calculatedmulti');
     163  
     164          $repeated = array();
     165          $nounits = optional_param('nounits', 1, PARAM_INT);
     166          $mform->addElement('hidden', 'nounits', $nounits);
     167          $mform->setType('nounits', PARAM_INT);
     168          $mform->setConstants(array('nounits'=>$nounits));
     169          for ($i = 0; $i < $nounits; $i++) {
     170              $mform->addElement('hidden', 'unit'."[{$i}]",
     171                      optional_param("unit[{$i}]", '', PARAM_NOTAGS));
     172              $mform->setType('unit'."[{$i}]", PARAM_NOTAGS);
     173              $mform->addElement('hidden', 'multiplier'."[{$i}]",
     174                      optional_param("multiplier[{$i}]", '', PARAM_FLOAT));
     175              $mform->setType("multiplier[{$i}]", PARAM_FLOAT);
     176          }
     177  
     178          $this->add_combined_feedback_fields(true);
     179          $mform->disabledIf('shownumcorrect', 'single', 'eq', 1);
     180  
     181          $this->add_interactive_settings(true, true);
     182  
     183          // Hidden elements.
     184          $mform->addElement('hidden', 'synchronize', '');
     185          $mform->setType('synchronize', PARAM_INT);
     186          if (isset($this->question->options) && isset($this->question->options->synchronize)) {
     187              $mform->setDefault('synchronize', $this->question->options->synchronize);
     188          } else {
     189              $mform->setDefault('synchronize', 0);
     190          }
     191          $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
     192          $mform->setType('wizard', PARAM_ALPHA);
     193      }
     194  
     195      public function data_preprocessing($question) {
     196          $question = parent::data_preprocessing($question);
     197          $question = $this->data_preprocessing_answers($question, false);
     198          $question = $this->data_preprocessing_combined_feedback($question, true);
     199          $question = $this->data_preprocessing_hints($question, true, true);
     200  
     201          if (isset($question->options)) {
     202              $question->synchronize     = $question->options->synchronize;
     203              $question->single          = $question->options->single;
     204              $question->answernumbering = $question->options->answernumbering;
     205              $question->shuffleanswers  = $question->options->shuffleanswers;
     206          }
     207  
     208          return $question;
     209      }
     210  
     211      protected function data_preprocessing_answers($question, $withanswerfiles = false) {
     212          $question = parent::data_preprocessing_answers($question, $withanswerfiles);
     213          if (empty($question->options->answers)) {
     214              return $question;
     215          }
     216  
     217          $key = 0;
     218          foreach ($question->options->answers as $answer) {
     219              // See comment in the parent method about this hack.
     220              unset($this->_form->_defaultValues["tolerance[{$key}]"]);
     221              unset($this->_form->_defaultValues["tolerancetype[{$key}]"]);
     222              unset($this->_form->_defaultValues["correctanswerlength[{$key}]"]);
     223              unset($this->_form->_defaultValues["correctanswerformat[{$key}]"]);
     224  
     225              $question->tolerance[$key]           = $answer->tolerance;
     226              $question->tolerancetype[$key]       = $answer->tolerancetype;
     227              $question->correctanswerlength[$key] = $answer->correctanswerlength;
     228              $question->correctanswerformat[$key] = $answer->correctanswerformat;
     229              $key++;
     230          }
     231  
     232          return $question;
     233      }
     234  
     235      /**
     236       * Validate the equations in the some question content.
     237       * @param array $errors where errors are being accumulated.
     238       * @param string $field the field being validated.
     239       * @param string $text the content of that field.
     240       * @return array the updated $errors array.
     241       */
     242      protected function validate_text($errors, $field, $text) {
     243          $problems = qtype_calculated_find_formula_errors_in_text($text);
     244          if ($problems) {
     245              $errors[$field] = $problems;
     246          }
     247          return $errors;
     248      }
     249  
     250      public function validation($data, $files) {
     251          $errors = parent::validation($data, $files);
     252  
     253          // Verifying for errors in {=...} in question text.
     254          $errors = $this->validate_text($errors, 'questiontext', $data['questiontext']['text']);
     255          $errors = $this->validate_text($errors, 'generalfeedback', $data['generalfeedback']['text']);
     256          $errors = $this->validate_text($errors, 'correctfeedback', $data['correctfeedback']['text']);
     257          $errors = $this->validate_text($errors, 'partiallycorrectfeedback', $data['partiallycorrectfeedback']['text']);
     258          $errors = $this->validate_text($errors, 'incorrectfeedback', $data['incorrectfeedback']['text']);
     259  
     260          $answers = $data['answer'];
     261          $answercount = 0;
     262          $maxgrade = false;
     263          $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
     264          $mandatorydatasets = array();
     265          foreach ($answers as $key => $answer) {
     266              $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
     267          }
     268          if (count($mandatorydatasets) == 0) {
     269              foreach ($answers as $key => $answer) {
     270                  $errors['answeroptions['.$key.']'] =
     271                          get_string('atleastonewildcard', 'qtype_calculated');
     272              }
     273          }
     274  
     275          $totalfraction = 0;
     276          $maxfraction = -1;
     277          foreach ($answers as $key => $answer) {
     278              $trimmedanswer = trim($answer);
     279              $fraction = (float) $data['fraction'][$key];
     280              if (empty($trimmedanswer) && $trimmedanswer != '0' && empty($fraction)) {
     281                  continue;
     282              }
     283              if (empty($trimmedanswer)) {
     284                  $errors['answeroptions['.$key.']'] = get_string('errgradesetanswerblank', 'qtype_multichoice');
     285              }
     286              if ($trimmedanswer != '' || $answercount == 0) {
     287                  // Verifying for errors in {=...} in answer text.
     288                  $errors = $this->validate_text($errors, 'answeroptions[' . $key . ']', $answer);
     289                  $errors = $this->validate_text($errors, 'feedback[' . $key . ']',
     290                          $data['feedback'][$key]['text']);
     291              }
     292  
     293              if ($trimmedanswer != '') {
     294                  if ('2' == $data['correctanswerformat'][$key] &&
     295                          '0' == $data['correctanswerlength'][$key]) {
     296                      $errors['correctanswerlength['.$key.']'] =
     297                              get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
     298                  }
     299                  if (!is_numeric($data['tolerance'][$key])) {
     300                      $errors['tolerance['.$key.']'] =
     301                              get_string('xmustbenumeric', 'qtype_numerical',
     302                                  get_string('acceptederror', 'qtype_numerical'));
     303                  }
     304                  if ($data['fraction'][$key] > 0) {
     305                      $totalfraction += $data['fraction'][$key];
     306                  }
     307                  if ($data['fraction'][$key] > $maxfraction) {
     308                      $maxfraction = $data['fraction'][$key];
     309                  }
     310  
     311                  $answercount++;
     312              }
     313          }
     314          if ($answercount == 0) {
     315              $errors['answeroptions[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
     316              $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
     317          } else if ($answercount == 1) {
     318              $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
     319  
     320          }
     321          // Perform sanity checks on fractional grades.
     322          if ($data['single']== 1 ) {
     323              if ($maxfraction != 1) {
     324                  $errors['answeroptions[0]'] = get_string('errfractionsnomax', 'qtype_multichoice',
     325                          $maxfraction * 100);
     326              }
     327          } else {
     328              $totalfraction = round($totalfraction, 2);
     329              if ($totalfraction != 1) {
     330                  $totalfraction = $totalfraction * 100;
     331                  $errors['answeroptions[0]'] =
     332                          get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
     333              }
     334          }
     335          return $errors;
     336      }
     337  
     338      public function qtype() {
     339          return 'calculatedmulti';
     340      }
     341  }
    

    Search This Site: