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   * A base class for question editing forms.
      19   *
      20   * @package    moodlecore
      21   * @subpackage questiontypes
      22   * @copyright  2006 The Open University
      23   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
      24   */
      25  
      26  
      27  defined('MOODLE_INTERNAL') || die();
      28  
      29  global $CFG;
      30  require_once($CFG->libdir.'/formslib.php');
      31  
      32  
      33  abstract class question_wizard_form extends moodleform {
      34      /**
      35       * Add all the hidden form fields used by question/question.php.
      36       */
      37      protected function add_hidden_fields() {
      38          $mform = $this->_form;
      39  
      40          $mform->addElement('hidden', 'id');
      41          $mform->setType('id', PARAM_INT);
      42  
      43          $mform->addElement('hidden', 'inpopup');
      44          $mform->setType('inpopup', PARAM_INT);
      45  
      46          $mform->addElement('hidden', 'cmid');
      47          $mform->setType('cmid', PARAM_INT);
      48  
      49          $mform->addElement('hidden', 'courseid');
      50          $mform->setType('courseid', PARAM_INT);
      51  
      52          $mform->addElement('hidden', 'returnurl');
      53          $mform->setType('returnurl', PARAM_LOCALURL);
      54  
      55          $mform->addElement('hidden', 'scrollpos');
      56          $mform->setType('scrollpos', PARAM_INT);
      57  
      58          $mform->addElement('hidden', 'appendqnumstring');
      59          $mform->setType('appendqnumstring', PARAM_ALPHA);
      60      }
      61  }
      62  
      63  /**
      64   * Form definition base class. This defines the common fields that
      65   * all question types need. Question types should define their own
      66   * class that inherits from this one, and implements the definition_inner()
      67   * method.
      68   *
      69   * @copyright  2006 The Open University
      70   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
      71   */
      72  abstract class question_edit_form extends question_wizard_form {
      73      const DEFAULT_NUM_HINTS = 2;
      74  
      75      /**
      76       * Question object with options and answers already loaded by get_question_options
      77       * Be careful how you use this it is needed sometimes to set up the structure of the
      78       * form in definition_inner but data is always loaded into the form with set_data.
      79       * @var object
      80       */
      81      protected $question;
      82  
      83      protected $contexts;
      84      protected $category;
      85      protected $categorycontext;
      86  
      87      /** @var object current context */
      88      public $context;
      89      /** @var array html editor options */
      90      public $editoroptions;
      91      /** @var array options to preapre draft area */
      92      public $fileoptions;
      93      /** @var object instance of question type */
      94      public $instance;
      95  
      96      public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) {
      97          global $DB;
      98  
      99          $this->question = $question;
     100          $this->contexts = $contexts;
     101  
     102          $record = $DB->get_record('question_categories',
     103                  array('id' => $question->category), 'contextid');
     104          $this->context = context::instance_by_id($record->contextid);
     105  
     106          $this->editoroptions = array('subdirs' => 1, 'maxfiles' => EDITOR_UNLIMITED_FILES,
     107                  'context' => $this->context);
     108          $this->fileoptions = array('subdirs' => 1, 'maxfiles' => -1, 'maxbytes' => -1);
     109  
     110          $this->category = $category;
     111          $this->categorycontext = context::instance_by_id($category->contextid);
     112  
     113          parent::__construct($submiturl, null, 'post', '', null, $formeditable);
     114      }
     115  
     116      /**
     117       * Build the form definition.
     118       *
     119       * This adds all the form fields that the default question type supports.
     120       * If your question type does not support all these fields, then you can
     121       * override this method and remove the ones you don't want with $mform->removeElement().
     122       */
     123      protected function definition() {
     124          global $COURSE, $CFG, $DB, $PAGE;
     125  
     126          $qtype = $this->qtype();
     127          $langfile = "qtype_{$qtype}";
     128  
     129          $mform = $this->_form;
     130  
     131          // Standard fields at the start of the form.
     132          $mform->addElement('header', 'generalheader', get_string("general", 'form'));
     133  
     134          if (!isset($this->question->id)) {
     135              if (!empty($this->question->formoptions->mustbeusable)) {
     136                  $contexts = $this->contexts->having_add_and_use();
     137              } else {
     138                  $contexts = $this->contexts->having_cap('moodle/question:add');
     139              }
     140  
     141              // Adding question.
     142              $mform->addElement('questioncategory', 'category', get_string('category', 'question'),
     143                      array('contexts' => $contexts));
     144          } else if (!($this->question->formoptions->canmove ||
     145                  $this->question->formoptions->cansaveasnew)) {
     146              // Editing question with no permission to move from category.
     147              $mform->addElement('questioncategory', 'category', get_string('category', 'question'),
     148                      array('contexts' => array($this->categorycontext)));
     149              $mform->addElement('hidden', 'usecurrentcat', 1);
     150              $mform->setType('usecurrentcat', PARAM_BOOL);
     151              $mform->setConstant('usecurrentcat', 1);
     152          } else {
     153              // Editing question with permission to move from category or save as new q.
     154              $currentgrp = array();
     155              $currentgrp[0] = $mform->createElement('questioncategory', 'category',
     156                      get_string('categorycurrent', 'question'),
     157                      array('contexts' => array($this->categorycontext)));
     158              if ($this->question->formoptions->canedit ||
     159                      $this->question->formoptions->cansaveasnew) {
     160                  // Not move only form.
     161                  $currentgrp[1] = $mform->createElement('checkbox', 'usecurrentcat', '',
     162                          get_string('categorycurrentuse', 'question'));
     163                  $mform->setDefault('usecurrentcat', 1);
     164              }
     165              $currentgrp[0]->freeze();
     166              $currentgrp[0]->setPersistantFreeze(false);
     167              $mform->addGroup($currentgrp, 'currentgrp',
     168                      get_string('categorycurrent', 'question'), null, false);
     169  
     170              $mform->addElement('questioncategory', 'categorymoveto',
     171                      get_string('categorymoveto', 'question'),
     172                      array('contexts' => array($this->categorycontext)));
     173              if ($this->question->formoptions->canedit ||
     174                      $this->question->formoptions->cansaveasnew) {
     175                  // Not move only form.
     176                  $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked');
     177              }
     178          }
     179  
     180          $mform->addElement('text', 'name', get_string('questionname', 'question'),
     181                  array('size' => 50, 'maxlength' => 255));
     182          $mform->setType('name', PARAM_TEXT);
     183          $mform->addRule('name', null, 'required', null, 'client');
     184  
     185          $mform->addElement('editor', 'questiontext', get_string('questiontext', 'question'),
     186                  array('rows' => 15), $this->editoroptions);
     187          $mform->setType('questiontext', PARAM_RAW);
     188          $mform->addRule('questiontext', null, 'required', null, 'client');
     189  
     190          $mform->addElement('text', 'defaultmark', get_string('defaultmark', 'question'),
     191                  array('size' => 7));
     192          $mform->setType('defaultmark', PARAM_FLOAT);
     193          $mform->setDefault('defaultmark', 1);
     194          $mform->addRule('defaultmark', null, 'required', null, 'client');
     195  
     196          $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'question'),
     197                  array('rows' => 10), $this->editoroptions);
     198          $mform->setType('generalfeedback', PARAM_RAW);
     199          $mform->addHelpButton('generalfeedback', 'generalfeedback', 'question');
     200  
     201          // Any questiontype specific fields.
     202          $this->definition_inner($mform);
     203  
     204          if (!empty($CFG->usetags)) {
     205              $mform->addElement('header', 'tagsheader', get_string('tags'));
     206              $mform->addElement('tags', 'tags', get_string('tags'));
     207          }
     208  
     209          if (!empty($this->question->id)) {
     210              $mform->addElement('header', 'createdmodifiedheader',
     211                      get_string('createdmodifiedheader', 'question'));
     212              $a = new stdClass();
     213              if (!empty($this->question->createdby)) {
     214                  $a->time = userdate($this->question->timecreated);
     215                  $a->user = fullname($DB->get_record(
     216                          'user', array('id' => $this->question->createdby)));
     217              } else {
     218                  $a->time = get_string('unknown', 'question');
     219                  $a->user = get_string('unknown', 'question');
     220              }
     221              $mform->addElement('static', 'created', get_string('created', 'question'),
     222                       get_string('byandon', 'question', $a));
     223              if (!empty($this->question->modifiedby)) {
     224                  $a = new stdClass();
     225                  $a->time = userdate($this->question->timemodified);
     226                  $a->user = fullname($DB->get_record(
     227                          'user', array('id' => $this->question->modifiedby)));
     228                  $mform->addElement('static', 'modified', get_string('modified', 'question'),
     229                          get_string('byandon', 'question', $a));
     230              }
     231          }
     232  
     233          $this->add_hidden_fields();
     234  
     235          $mform->addElement('hidden', 'qtype');
     236          $mform->setType('qtype', PARAM_ALPHA);
     237  
     238          $mform->addElement('hidden', 'makecopy');
     239          $mform->setType('makecopy', PARAM_INT);
     240  
     241          $buttonarray = array();
     242          $buttonarray[] = $mform->createElement('submit', 'updatebutton',
     243                               get_string('savechangesandcontinueediting', 'question'));
     244          if ($this->can_preview()) {
     245              $previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
     246                      $this->question->id, $this->context, true);
     247              $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink);
     248          }
     249  
     250          $mform->addGroup($buttonarray, 'updatebuttonar', '', array(' '), false);
     251          $mform->closeHeaderBefore('updatebuttonar');
     252  
     253          $this->add_action_buttons(true, get_string('savechanges'));
     254  
     255          if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit ||
     256                  $this->question->formoptions->cansaveasnew))) {
     257              $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp'));
     258          }
     259      }
     260  
     261      /**
     262       * Add any question-type specific form fields.
     263       *
     264       * @param object $mform the form being built.
     265       */
     266      protected function definition_inner($mform) {
     267          // By default, do nothing.
     268      }
     269  
     270      /**
     271       * Is the question being edited in a state where it can be previewed?
     272       * @return bool whether to show the preview link.
     273       */
     274      protected function can_preview() {
     275          return empty($this->question->beingcopied) && !empty($this->question->id) &&
     276                  $this->question->formoptions->canedit;
     277      }
     278  
     279      /**
     280       * Get the list of form elements to repeat, one for each answer.
     281       * @param object $mform the form being built.
     282       * @param $label the label to use for each option.
     283       * @param $gradeoptions the possible grades for each answer.
     284       * @param $repeatedoptions reference to array of repeated options to fill
     285       * @param $answersoption reference to return the name of $question->options
     286       *      field holding an array of answers
     287       * @return array of form fields.
     288       */
     289      protected function get_per_answer_fields($mform, $label, $gradeoptions,
     290              &$repeatedoptions, &$answersoption) {
     291          $repeated = array();
     292          $answeroptions = array();
     293          $answeroptions[] = $mform->createElement('text', 'answer',
     294                  $label, array('size' => 40));
     295          $answeroptions[] = $mform->createElement('select', 'fraction',
     296                  get_string('grade'), $gradeoptions);
     297          $repeated[] = $mform->createElement('group', 'answeroptions',
     298                   $label, $answeroptions, null, false);
     299          $repeated[] = $mform->createElement('editor', 'feedback',
     300                  get_string('feedback', 'question'), array('rows' => 5), $this->editoroptions);
     301          $repeatedoptions['answer']['type'] = PARAM_RAW;
     302          $repeatedoptions['fraction']['default'] = 0;
     303          $answersoption = 'answers';
     304          return $repeated;
     305      }
     306  
     307      /**
     308       * Add a set of form fields, obtained from get_per_answer_fields, to the form,
     309       * one for each existing answer, with some blanks for some new ones.
     310       * @param object $mform the form being built.
     311       * @param $label the label to use for each option.
     312       * @param $gradeoptions the possible grades for each answer.
     313       * @param $minoptions the minimum number of answer blanks to display.
     314       *      Default QUESTION_NUMANS_START.
     315       * @param $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD.
     316       */
     317      protected function add_per_answer_fields(&$mform, $label, $gradeoptions,
     318              $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) {
     319          $mform->addElement('header', 'answerhdr',
     320                      get_string('answers', 'question'), '');
     321          $mform->setExpanded('answerhdr', 1);
     322          $answersoption = '';
     323          $repeatedoptions = array();
     324          $repeated = $this->get_per_answer_fields($mform, $label, $gradeoptions,
     325                  $repeatedoptions, $answersoption);
     326  
     327          if (isset($this->question->options)) {
     328              $repeatsatstart = count($this->question->options->$answersoption);
     329          } else {
     330              $repeatsatstart = $minoptions;
     331          }
     332  
     333          $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions,
     334                  'noanswers', 'addanswers', $addoptions,
     335                  $this->get_more_choices_string(), true);
     336      }
     337  
     338      /**
     339       * Language string to use for 'Add {no} more {whatever we call answers}'.
     340       */
     341      protected function get_more_choices_string() {
     342          return get_string('addmorechoiceblanks', 'question');
     343      }
     344  
     345      protected function add_combined_feedback_fields($withshownumpartscorrect = false) {
     346          $mform = $this->_form;
     347  
     348          $mform->addElement('header', 'combinedfeedbackhdr',
     349                  get_string('combinedfeedback', 'question'));
     350  
     351          $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback');
     352          foreach ($fields as $feedbackname) {
     353              $element = $mform->addElement('editor', $feedbackname,
     354                                  get_string($feedbackname, 'question'),
     355                                  array('rows' => 5), $this->editoroptions);
     356              $mform->setType($feedbackname, PARAM_RAW);
     357              // Using setValue() as setDefault() does not work for the editor class.
     358              $element->setValue(array('text' => get_string($feedbackname.'default', 'question')));
     359  
     360              if ($withshownumpartscorrect && $feedbackname == 'partiallycorrectfeedback') {
     361                  $mform->addElement('advcheckbox', 'shownumcorrect',
     362                          get_string('options', 'question'),
     363                          get_string('shownumpartscorrectwhenfinished', 'question'));
     364                  $mform->setDefault('shownumcorrect', true);
     365              }
     366          }
     367      }
     368  
     369      /**
     370       * Create the form elements required by one hint.
     371       * @param string $withclearwrong whether this quesiton type uses the 'Clear wrong' option on hints.
     372       * @param string $withshownumpartscorrect whether this quesiton type uses the 'Show num parts correct' option on hints.
     373       * @return array form field elements for one hint.
     374       */
     375      protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) {
     376          $mform = $this->_form;
     377  
     378          $repeatedoptions = array();
     379          $repeated = array();
     380          $repeated[] = $mform->createElement('editor', 'hint', get_string('hintn', 'question'),
     381                  array('rows' => 5), $this->editoroptions);
     382          $repeatedoptions['hint']['type'] = PARAM_RAW;
     383  
     384          $optionelements = array();
     385          if ($withclearwrong) {
     386              $optionelements[] = $mform->createElement('advcheckbox', 'hintclearwrong',
     387                      get_string('options', 'question'), get_string('clearwrongparts', 'question'));
     388          }
     389          if ($withshownumpartscorrect) {
     390              $optionelements[] = $mform->createElement('advcheckbox', 'hintshownumcorrect', '',
     391                      get_string('shownumpartscorrect', 'question'));
     392          }
     393  
     394          if (count($optionelements)) {
     395              $repeated[] = $mform->createElement('group', 'hintoptions',
     396                   get_string('hintnoptions', 'question'), $optionelements, null, false);
     397          }
     398  
     399          return array($repeated, $repeatedoptions);
     400      }
     401  
     402      protected function add_interactive_settings($withclearwrong = false,
     403              $withshownumpartscorrect = false) {
     404          $mform = $this->_form;
     405  
     406          $mform->addElement('header', 'multitriesheader',
     407                  get_string('settingsformultipletries', 'question'));
     408  
     409          $penalties = array(
     410              1.0000000,
     411              0.5000000,
     412              0.3333333,
     413              0.2500000,
     414              0.2000000,
     415              0.1000000,
     416              0.0000000
     417          );
     418          if (!empty($this->question->penalty) && !in_array($this->question->penalty, $penalties)) {
     419              $penalties[] = $this->question->penalty;
     420              sort($penalties);
     421          }
     422          $penaltyoptions = array();
     423          foreach ($penalties as $penalty) {
     424              $penaltyoptions["{$penalty}"] = (100 * $penalty) . '%';
     425          }
     426          $mform->addElement('select', 'penalty',
     427                  get_string('penaltyforeachincorrecttry', 'question'), $penaltyoptions);
     428          $mform->addHelpButton('penalty', 'penaltyforeachincorrecttry', 'question');
     429          $mform->setDefault('penalty', 0.3333333);
     430  
     431          if (isset($this->question->hints)) {
     432              $counthints = count($this->question->hints);
     433          } else {
     434              $counthints = 0;
     435          }
     436  
     437          if ($this->question->formoptions->repeatelements) {
     438              $repeatsatstart = max(self::DEFAULT_NUM_HINTS, $counthints);
     439          } else {
     440              $repeatsatstart = $counthints;
     441          }
     442  
     443          list($repeated, $repeatedoptions) = $this->get_hint_fields(
     444                  $withclearwrong, $withshownumpartscorrect);
     445          $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions,
     446                  'numhints', 'addhint', 1, get_string('addanotherhint', 'question'), true);
     447      }
     448  
     449      public function set_data($question) {
     450          question_bank::get_qtype($question->qtype)->set_default_options($question);
     451  
     452          // Prepare question text.
     453          $draftid = file_get_submitted_draft_itemid('questiontext');
     454  
     455          if (!empty($question->questiontext)) {
     456              $questiontext = $question->questiontext;
     457          } else {
     458              $questiontext = $this->_form->getElement('questiontext')->getValue();
     459              $questiontext = $questiontext['text'];
     460          }
     461          $questiontext = file_prepare_draft_area($draftid, $this->context->id,
     462                  'question', 'questiontext', empty($question->id) ? null : (int) $question->id,
     463                  $this->fileoptions, $questiontext);
     464  
     465          $question->questiontext = array();
     466          $question->questiontext['text'] = $questiontext;
     467          $question->questiontext['format'] = empty($question->questiontextformat) ?
     468                  editors_get_preferred_format() : $question->questiontextformat;
     469          $question->questiontext['itemid'] = $draftid;
     470  
     471          // Prepare general feedback.
     472          $draftid = file_get_submitted_draft_itemid('generalfeedback');
     473  
     474          if (empty($question->generalfeedback)) {
     475              $generalfeedback = $this->_form->getElement('generalfeedback')->getValue();
     476              $question->generalfeedback = $generalfeedback['text'];
     477          }
     478  
     479          $feedback = file_prepare_draft_area($draftid, $this->context->id,
     480                  'question', 'generalfeedback', empty($question->id) ? null : (int) $question->id,
     481                  $this->fileoptions, $question->generalfeedback);
     482          $question->generalfeedback = array();
     483          $question->generalfeedback['text'] = $feedback;
     484          $question->generalfeedback['format'] = empty($question->generalfeedbackformat) ?
     485                  editors_get_preferred_format() : $question->generalfeedbackformat;
     486          $question->generalfeedback['itemid'] = $draftid;
     487  
     488          // Remove unnecessary trailing 0s form grade fields.
     489          if (isset($question->defaultgrade)) {
     490              $question->defaultgrade = 0 + $question->defaultgrade;
     491          }
     492          if (isset($question->penalty)) {
     493              $question->penalty = 0 + $question->penalty;
     494          }
     495  
     496          // Set any options.
     497          $extraquestionfields = question_bank::get_qtype($question->qtype)->extra_question_fields();
     498          if (is_array($extraquestionfields) && !empty($question->options)) {
     499              array_shift($extraquestionfields);
     500              foreach ($extraquestionfields as $field) {
     501                  if (property_exists($question->options, $field)) {
     502                      $question->$field = $question->options->$field;
     503                  }
     504              }
     505          }
     506  
     507          // Subclass adds data_preprocessing code here.
     508          $question = $this->data_preprocessing($question);
     509  
     510          parent::set_data($question);
     511      }
     512  
     513      /**
     514       * Perform an preprocessing needed on the data passed to {@link set_data()}
     515       * before it is used to initialise the form.
     516       * @param object $question the data being passed to the form.
     517       * @return object $question the modified data.
     518       */
     519      protected function data_preprocessing($question) {
     520          return $question;
     521      }
     522  
     523      /**
     524       * Perform the necessary preprocessing for the fields added by
     525       * {@link add_per_answer_fields()}.
     526       * @param object $question the data being passed to the form.
     527       * @return object $question the modified data.
     528       */
     529      protected function data_preprocessing_answers($question, $withanswerfiles = false) {
     530          if (empty($question->options->answers)) {
     531              return $question;
     532          }
     533  
     534          $key = 0;
     535          foreach ($question->options->answers as $answer) {
     536              if ($withanswerfiles) {
     537                  // Prepare the feedback editor to display files in draft area.
     538                  $draftitemid = file_get_submitted_draft_itemid('answer['.$key.']');
     539                  $question->answer[$key]['text'] = file_prepare_draft_area(
     540                      $draftitemid,          // Draftid
     541                      $this->context->id,    // context
     542                      'question',            // component
     543                      'answer',              // filarea
     544                      !empty($answer->id) ? (int) $answer->id : null, // itemid
     545                      $this->fileoptions,    // options
     546                      $answer->answer        // text.
     547                  );
     548                  $question->answer[$key]['itemid'] = $draftitemid;
     549                  $question->answer[$key]['format'] = $answer->answerformat;
     550              } else {
     551                  $question->answer[$key] = $answer->answer;
     552              }
     553  
     554              $question->fraction[$key] = 0 + $answer->fraction;
     555              $question->feedback[$key] = array();
     556  
     557              // Evil hack alert. Formslib can store defaults in two ways for
     558              // repeat elements:
     559              //   ->_defaultValues['fraction[0]'] and
     560              //   ->_defaultValues['fraction'][0].
     561              // The $repeatedoptions['fraction']['default'] = 0 bit above means
     562              // that ->_defaultValues['fraction[0]'] has already been set, but we
     563              // are using object notation here, so we will be setting
     564              // ->_defaultValues['fraction'][0]. That does not work, so we have
     565              // to unset ->_defaultValues['fraction[0]'].
     566              unset($this->_form->_defaultValues["fraction[{$key}]"]);
     567  
     568              // Prepare the feedback editor to display files in draft area.
     569              $draftitemid = file_get_submitted_draft_itemid('feedback['.$key.']');
     570              $question->feedback[$key]['text'] = file_prepare_draft_area(
     571                  $draftitemid,          // Draftid
     572                  $this->context->id,    // context
     573                  'question',            // component
     574                  'answerfeedback',      // filarea
     575                  !empty($answer->id) ? (int) $answer->id : null, // itemid
     576                  $this->fileoptions,    // options
     577                  $answer->feedback      // text.
     578              );
     579              $question->feedback[$key]['itemid'] = $draftitemid;
     580              $question->feedback[$key]['format'] = $answer->feedbackformat;
     581              $key++;
     582          }
     583  
     584          // Now process extra answer fields.
     585          $extraanswerfields = question_bank::get_qtype($question->qtype)->extra_answer_fields();
     586          if (is_array($extraanswerfields)) {
     587              // Omit table name.
     588              array_shift($extraanswerfields);
     589              $question = $this->data_preprocessing_extra_answer_fields($question, $extraanswerfields);
     590          }
     591  
     592          return $question;
     593      }
     594  
     595      /**
     596       * Perform the necessary preprocessing for the extra answer fields.
     597       *
     598       * Questions that do something not trivial when editing extra answer fields
     599       * will want to override this.
     600       * @param object $question the data being passed to the form.
     601       * @param array $extraanswerfields extra answer fields (without table name).
     602       * @return object $question the modified data.
     603       */
     604      protected function data_preprocessing_extra_answer_fields($question, $extraanswerfields) {
     605          // Setting $question->$field[$key] won't work in PHP, so we need set an array of answer values to $question->$field.
     606          // As we may have several extra fields with data for several answers in each, we use an array of arrays.
     607          // Index in $extrafieldsdata is an extra answer field name, value - array of it's data for each answer.
     608          $extrafieldsdata = array();
     609          // First, prepare an array if empty arrays for each extra answer fields data.
     610          foreach ($extraanswerfields as $field) {
     611              $extrafieldsdata[$field] = array();
     612          }
     613  
     614          // Fill arrays with data from $question->options->answers.
     615          $key = 0;
     616          foreach ($question->options->answers as $answer) {
     617              foreach ($extraanswerfields as $field) {
     618                  // See hack comment in {@link data_preprocessing_answers()}.
     619                  unset($this->_form->_defaultValues["{$field}[{$key}]"]);
     620                  $extrafieldsdata[$field][$key] = $this->data_preprocessing_extra_answer_field($answer, $field);
     621              }
     622              $key++;
     623          }
     624  
     625          // Set this data in the $question object.
     626          foreach ($extraanswerfields as $field) {
     627              $question->$field = $extrafieldsdata[$field];
     628          }
     629          return $question;
     630      }
     631  
     632      /**
     633       * Perfmorm preprocessing for particular extra answer field.
     634       *
     635       * Questions with non-trivial DB - form element relationship will
     636       * want to override this.
     637       * @param object $answer an answer object to get extra field from.
     638       * @param string $field extra answer field name.
     639       * @return field value to be set to the form.
     640       */
     641      protected function data_preprocessing_extra_answer_field($answer, $field) {
     642          return $answer->$field;
     643      }
     644  
     645      /**
     646       * Perform the necessary preprocessing for the fields added by
     647       * {@link add_combined_feedback_fields()}.
     648       * @param object $question the data being passed to the form.
     649       * @return object $question the modified data.
     650       */
     651      protected function data_preprocessing_combined_feedback($question,
     652              $withshownumcorrect = false) {
     653          if (empty($question->options)) {
     654              return $question;
     655          }
     656  
     657          $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback');
     658          foreach ($fields as $feedbackname) {
     659              $draftid = file_get_submitted_draft_itemid($feedbackname);
     660              $feedback = array();
     661              $feedback['text'] = file_prepare_draft_area(
     662                  $draftid,              // Draftid
     663                  $this->context->id,    // context
     664                  'question',            // component
     665                  $feedbackname,         // filarea
     666                  !empty($question->id) ? (int) $question->id : null, // itemid
     667                  $this->fileoptions,    // options
     668                  $question->options->$feedbackname // text.
     669              );
     670              $feedbackformat = $feedbackname . 'format';
     671              $feedback['format'] = $question->options->$feedbackformat;
     672              $feedback['itemid'] = $draftid;
     673  
     674              $question->$feedbackname = $feedback;
     675          }
     676  
     677          if ($withshownumcorrect) {
     678              $question->shownumcorrect = $question->options->shownumcorrect;
     679          }
     680  
     681          return $question;
     682      }
     683  
     684      /**
     685       * Perform the necessary preprocessing for the hint fields.
     686       * @param object $question the data being passed to the form.
     687       * @return object $question the modified data.
     688       */
     689      protected function data_preprocessing_hints($question, $withclearwrong = false,
     690              $withshownumpartscorrect = false) {
     691          if (empty($question->hints)) {
     692              return $question;
     693          }
     694  
     695          $key = 0;
     696          foreach ($question->hints as $hint) {
     697              $question->hint[$key] = array();
     698  
     699              // Prepare feedback editor to display files in draft area.
     700              $draftitemid = file_get_submitted_draft_itemid('hint['.$key.']');
     701              $question->hint[$key]['text'] = file_prepare_draft_area(
     702                  $draftitemid,          // Draftid
     703                  $this->context->id,    // context
     704                  'question',            // component
     705                  'hint',                // filarea
     706                  !empty($hint->id) ? (int) $hint->id : null, // itemid
     707                  $this->fileoptions,    // options
     708                  $hint->hint            // text.
     709              );
     710              $question->hint[$key]['itemid'] = $draftitemid;
     711              $question->hint[$key]['format'] = $hint->hintformat;
     712              $key++;
     713  
     714              if ($withclearwrong) {
     715                  $question->hintclearwrong[] = $hint->clearwrong;
     716              }
     717              if ($withshownumpartscorrect) {
     718                  $question->hintshownumcorrect[] = $hint->shownumcorrect;
     719              }
     720          }
     721  
     722          return $question;
     723      }
     724  
     725      public function validation($fromform, $files) {
     726          $errors = parent::validation($fromform, $files);
     727          if (empty($fromform['makecopy']) && isset($this->question->id)
     728                  && ($this->question->formoptions->canedit ||
     729                          $this->question->formoptions->cansaveasnew)
     730                  && empty($fromform['usecurrentcat']) && !$this->question->formoptions->canmove) {
     731              $errors['currentgrp'] = get_string('nopermissionmove', 'question');
     732          }
     733  
     734          // Default mark.
     735          if (array_key_exists('defaultmark', $fromform) && $fromform['defaultmark'] < 0) {
     736              $errors['defaultmark'] = get_string('defaultmarkmustbepositive', 'question');
     737          }
     738  
     739          return $errors;
     740      }
     741  
     742      /**
     743       * Override this in the subclass to question type name.
     744       * @return the question type name, should be the same as the name() method
     745       *      in the question type class.
     746       */
     747      public abstract function qtype();
     748  
     749      /**
     750       * Returns an array of editor options with collapsed options turned off.
     751       * @deprecated since 2.6
     752       * @return array
     753       */
     754      protected function get_non_collabsible_editor_options() {
     755          debugging('get_non_collabsible_editor_options() is deprecated, use $this->editoroptions instead.', DEBUG_DEVELOPER);
     756          return $this->editoroptions;
     757      }
     758  
     759  }
    

    Search This Site: