Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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', '', ['data-qtype' => $this->qtype()], $formeditable); 114 } 115 116 /** 117 * Return default value for a given form element either from user_preferences table or $default. 118 * 119 * To make use of user_preferences in your qtype default settings, you need to replace 120 * $mform->setDefault({elementname}, {defaultvalue}); in edit_{qtypename}_form.php with 121 * $mform->setDefault({elementname}, $this->get_default_value({elementname}, {defaultvalue})); 122 * 123 * @param string $name the name of the form field. 124 * @param mixed $default default value. 125 * @return string|null default value for a given form element. 126 */ 127 protected function get_default_value(string $name, $default): ?string { 128 return question_bank::get_qtype($this->qtype())->get_default_value($name, $default); 129 } 130 131 /** 132 * Build the form definition. 133 * 134 * This adds all the form fields that the default question type supports. 135 * If your question type does not support all these fields, then you can 136 * override this method and remove the ones you don't want with $mform->removeElement(). 137 */ 138 protected function definition() { 139 global $DB, $PAGE; 140 141 $mform = $this->_form; 142 143 // Standard fields at the start of the form. 144 $mform->addElement('header', 'generalheader', get_string("general", 'form')); 145 146 if (!isset($this->question->id)) { 147 if (!empty($this->question->formoptions->mustbeusable)) { 148 $contexts = $this->contexts->having_add_and_use(); 149 } else { 150 $contexts = $this->contexts->having_cap('moodle/question:add'); 151 } 152 153 // Adding question. 154 $mform->addElement('questioncategory', 'category', get_string('category', 'question'), 155 array('contexts' => $contexts)); 156 } else if (!($this->question->formoptions->canmove || 157 $this->question->formoptions->cansaveasnew)) { 158 // Editing question with no permission to move from category. 159 $mform->addElement('questioncategory', 'category', get_string('category', 'question'), 160 array('contexts' => array($this->categorycontext))); 161 $mform->addElement('hidden', 'usecurrentcat', 1); 162 $mform->setType('usecurrentcat', PARAM_BOOL); 163 $mform->setConstant('usecurrentcat', 1); 164 } else { 165 // Editing question with permission to move from category or save as new q. 166 $currentgrp = array(); 167 $currentgrp[0] = $mform->createElement('questioncategory', 'category', 168 get_string('categorycurrent', 'question'), 169 array('contexts' => array($this->categorycontext))); 170 if ($this->question->formoptions->canedit || 171 $this->question->formoptions->cansaveasnew) { 172 // Not move only form. 173 $currentgrp[1] = $mform->createElement('checkbox', 'usecurrentcat', '', 174 get_string('categorycurrentuse', 'question')); 175 $mform->setDefault('usecurrentcat', 1); 176 } 177 $currentgrp[0]->freeze(); 178 $currentgrp[0]->setPersistantFreeze(false); 179 $mform->addGroup($currentgrp, 'currentgrp', 180 get_string('categorycurrent', 'question'), null, false); 181 182 $mform->addElement('questioncategory', 'categorymoveto', 183 get_string('categorymoveto', 'question'), 184 array('contexts' => array($this->categorycontext))); 185 if ($this->question->formoptions->canedit || 186 $this->question->formoptions->cansaveasnew) { 187 // Not move only form. 188 $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked'); 189 } 190 } 191 192 $mform->addElement('text', 'name', get_string('questionname', 'question'), 193 array('size' => 50, 'maxlength' => 255)); 194 $mform->setType('name', PARAM_TEXT); 195 $mform->addRule('name', null, 'required', null, 'client'); 196 197 $mform->addElement('editor', 'questiontext', get_string('questiontext', 'question'), 198 array('rows' => 15), $this->editoroptions); 199 $mform->setType('questiontext', PARAM_RAW); 200 $mform->addRule('questiontext', null, 'required', null, 'client'); 201 202 $mform->addElement('float', 'defaultmark', get_string('defaultmark', 'question'), 203 array('size' => 7)); 204 $mform->setDefault('defaultmark', $this->get_default_value('defaultmark', 1)); 205 $mform->addRule('defaultmark', null, 'required', null, 'client'); 206 207 $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'question'), 208 array('rows' => 10), $this->editoroptions); 209 $mform->setType('generalfeedback', PARAM_RAW); 210 $mform->addHelpButton('generalfeedback', 'generalfeedback', 'question'); 211 212 $mform->addElement('text', 'idnumber', get_string('idnumber', 'question'), 'maxlength="100" size="10"'); 213 $mform->addHelpButton('idnumber', 'idnumber', 'question'); 214 $mform->setType('idnumber', PARAM_RAW); 215 216 // Any questiontype specific fields. 217 $this->definition_inner($mform); 218 219 if (core_tag_tag::is_enabled('core_question', 'question')) { 220 $this->add_tag_fields($mform); 221 } 222 223 if (!empty($this->question->id)) { 224 $mform->addElement('header', 'createdmodifiedheader', 225 get_string('createdmodifiedheader', 'question')); 226 $a = new stdClass(); 227 if (!empty($this->question->createdby)) { 228 $a->time = userdate($this->question->timecreated); 229 $a->user = fullname($DB->get_record( 230 'user', array('id' => $this->question->createdby))); 231 } else { 232 $a->time = get_string('unknown', 'question'); 233 $a->user = get_string('unknown', 'question'); 234 } 235 $mform->addElement('static', 'created', get_string('created', 'question'), 236 get_string('byandon', 'question', $a)); 237 if (!empty($this->question->modifiedby)) { 238 $a = new stdClass(); 239 $a->time = userdate($this->question->timemodified); 240 $a->user = fullname($DB->get_record( 241 'user', array('id' => $this->question->modifiedby))); 242 $mform->addElement('static', 'modified', get_string('modified', 'question'), 243 get_string('byandon', 'question', $a)); 244 } 245 } 246 247 $this->add_hidden_fields(); 248 249 $mform->addElement('hidden', 'qtype'); 250 $mform->setType('qtype', PARAM_ALPHA); 251 252 $mform->addElement('hidden', 'makecopy'); 253 $mform->setType('makecopy', PARAM_INT); 254 255 $buttonarray = array(); 256 $buttonarray[] = $mform->createElement('submit', 'updatebutton', 257 get_string('savechangesandcontinueediting', 'question')); 258 if ($this->can_preview()) { 259 $previewlink = $PAGE->get_renderer('core_question')->question_preview_link( 260 $this->question->id, $this->context, true); 261 $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink); 262 } 263 264 $mform->addGroup($buttonarray, 'updatebuttonar', '', array(' '), false); 265 $mform->closeHeaderBefore('updatebuttonar'); 266 267 $this->add_action_buttons(true, get_string('savechanges')); 268 269 if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || 270 $this->question->formoptions->cansaveasnew))) { 271 $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp')); 272 } 273 } 274 275 /** 276 * Add any question-type specific form fields. 277 * 278 * @param object $mform the form being built. 279 */ 280 protected function definition_inner($mform) { 281 // By default, do nothing. 282 } 283 284 /** 285 * Is the question being edited in a state where it can be previewed? 286 * @return bool whether to show the preview link. 287 */ 288 protected function can_preview() { 289 return empty($this->question->beingcopied) && !empty($this->question->id) && 290 $this->question->formoptions->canedit; 291 } 292 293 /** 294 * Get the list of form elements to repeat, one for each answer. 295 * @param object $mform the form being built. 296 * @param $label the label to use for each option. 297 * @param $gradeoptions the possible grades for each answer. 298 * @param $repeatedoptions reference to array of repeated options to fill 299 * @param $answersoption reference to return the name of $question->options 300 * field holding an array of answers 301 * @return array of form fields. 302 */ 303 protected function get_per_answer_fields($mform, $label, $gradeoptions, 304 &$repeatedoptions, &$answersoption) { 305 $repeated = array(); 306 $answeroptions = array(); 307 $answeroptions[] = $mform->createElement('text', 'answer', 308 $label, array('size' => 40)); 309 $answeroptions[] = $mform->createElement('select', 'fraction', 310 get_string('gradenoun'), $gradeoptions); 311 $repeated[] = $mform->createElement('group', 'answeroptions', 312 $label, $answeroptions, null, false); 313 $repeated[] = $mform->createElement('editor', 'feedback', 314 get_string('feedback', 'question'), array('rows' => 5), $this->editoroptions); 315 $repeatedoptions['answer']['type'] = PARAM_RAW; 316 $repeatedoptions['fraction']['default'] = 0; 317 $answersoption = 'answers'; 318 return $repeated; 319 } 320 321 /** 322 * Add the tag and course tag fields to the mform. 323 * 324 * If the form is being built in a course context then add the field 325 * for course tags. 326 * 327 * If the question category doesn't belong to a course context or we 328 * aren't editing in a course context then add the tags element to allow 329 * tags to be added to the question category context. 330 * 331 * @param object $mform The form being built 332 */ 333 protected function add_tag_fields($mform) { 334 global $CFG, $DB; 335 336 $hastagcapability = question_has_capability_on($this->question, 'tag'); 337 // Is the question category in a course context? 338 $qcontext = $this->categorycontext; 339 $qcoursecontext = $qcontext->get_course_context(false); 340 $iscourseoractivityquestion = !empty($qcoursecontext); 341 // Is the current context we're editing in a course context? 342 $editingcontext = $this->contexts->lowest(); 343 $editingcoursecontext = $editingcontext->get_course_context(false); 344 $iseditingcontextcourseoractivity = !empty($editingcoursecontext); 345 346 $mform->addElement('header', 'tagsheader', get_string('tags')); 347 $tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $this->contexts->all()); 348 $tagstrings = []; 349 foreach ($tags as $tag) { 350 $tagstrings[$tag->name] = $tag->name; 351 } 352 353 $showstandard = core_tag_area::get_showstandard('core_question', 'question'); 354 if ($showstandard != core_tag_tag::HIDE_STANDARD) { 355 $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname'; 356 $standardtags = $DB->get_records('tag', 357 array('isstandard' => 1, 'tagcollid' => core_tag_area::get_collection('core', 'question')), 358 $namefield, 'id,' . $namefield); 359 foreach ($standardtags as $standardtag) { 360 $tagstrings[$standardtag->$namefield] = $standardtag->$namefield; 361 } 362 } 363 364 $options = [ 365 'tags' => true, 366 'multiple' => true, 367 'noselectionstring' => get_string('anytags', 'quiz'), 368 ]; 369 $mform->addElement('autocomplete', 'tags', get_string('tags'), $tagstrings, $options); 370 371 if (!$hastagcapability) { 372 $mform->hardFreeze('tags'); 373 } 374 375 if ($iseditingcontextcourseoractivity && !$iscourseoractivityquestion) { 376 // If the question is being edited in a course or activity context 377 // and the question isn't a course or activity level question then 378 // allow course tags to be added to the course. 379 $coursetagheader = get_string('questionformtagheader', 'core_question', 380 $editingcoursecontext->get_context_name(true)); 381 $mform->addElement('header', 'coursetagsheader', $coursetagheader); 382 $mform->addElement('autocomplete', 'coursetags', get_string('tags'), $tagstrings, $options); 383 384 if (!$hastagcapability) { 385 $mform->hardFreeze('coursetags'); 386 } 387 } 388 } 389 390 /** 391 * Add a set of form fields, obtained from get_per_answer_fields, to the form, 392 * one for each existing answer, with some blanks for some new ones. 393 * @param object $mform the form being built. 394 * @param $label the label to use for each option. 395 * @param $gradeoptions the possible grades for each answer. 396 * @param $minoptions the minimum number of answer blanks to display. 397 * Default QUESTION_NUMANS_START. 398 * @param $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD. 399 */ 400 protected function add_per_answer_fields(&$mform, $label, $gradeoptions, 401 $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) { 402 $mform->addElement('header', 'answerhdr', 403 get_string('answers', 'question'), ''); 404 $mform->setExpanded('answerhdr', 1); 405 $answersoption = ''; 406 $repeatedoptions = array(); 407 $repeated = $this->get_per_answer_fields($mform, $label, $gradeoptions, 408 $repeatedoptions, $answersoption); 409 410 if (isset($this->question->options)) { 411 $repeatsatstart = count($this->question->options->$answersoption); 412 } else { 413 $repeatsatstart = $minoptions; 414 } 415 416 $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 417 'noanswers', 'addanswers', $addoptions, 418 $this->get_more_choices_string(), true); 419 } 420 421 /** 422 * Language string to use for 'Add {no} more {whatever we call answers}'. 423 */ 424 protected function get_more_choices_string() { 425 return get_string('addmorechoiceblanks', 'question'); 426 } 427 428 protected function add_combined_feedback_fields($withshownumpartscorrect = false) { 429 $mform = $this->_form; 430 431 $mform->addElement('header', 'combinedfeedbackhdr', 432 get_string('combinedfeedback', 'question')); 433 434 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 435 foreach ($fields as $feedbackname) { 436 $element = $mform->addElement('editor', $feedbackname, 437 get_string($feedbackname, 'question'), 438 array('rows' => 5), $this->editoroptions); 439 $mform->setType($feedbackname, PARAM_RAW); 440 // Using setValue() as setDefault() does not work for the editor class. 441 $element->setValue(array('text' => get_string($feedbackname.'default', 'question'))); 442 443 if ($withshownumpartscorrect && $feedbackname == 'partiallycorrectfeedback') { 444 $mform->addElement('advcheckbox', 'shownumcorrect', 445 get_string('options', 'question'), 446 get_string('shownumpartscorrectwhenfinished', 'question')); 447 $mform->setDefault('shownumcorrect', true); 448 } 449 } 450 } 451 452 /** 453 * Create the form elements required by one hint. 454 * @param string $withclearwrong whether this quesiton type uses the 'Clear wrong' option on hints. 455 * @param string $withshownumpartscorrect whether this quesiton type uses the 'Show num parts correct' option on hints. 456 * @return array form field elements for one hint. 457 */ 458 protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) { 459 $mform = $this->_form; 460 461 $repeatedoptions = array(); 462 $repeated = array(); 463 $repeated[] = $mform->createElement('editor', 'hint', get_string('hintn', 'question'), 464 array('rows' => 5), $this->editoroptions); 465 $repeatedoptions['hint']['type'] = PARAM_RAW; 466 467 $optionelements = array(); 468 if ($withclearwrong) { 469 $optionelements[] = $mform->createElement('advcheckbox', 'hintclearwrong', 470 get_string('options', 'question'), get_string('clearwrongparts', 'question')); 471 } 472 if ($withshownumpartscorrect) { 473 $optionelements[] = $mform->createElement('advcheckbox', 'hintshownumcorrect', '', 474 get_string('shownumpartscorrect', 'question')); 475 } 476 477 if (count($optionelements)) { 478 $repeated[] = $mform->createElement('group', 'hintoptions', 479 get_string('hintnoptions', 'question'), $optionelements, null, false); 480 } 481 482 return array($repeated, $repeatedoptions); 483 } 484 485 protected function add_interactive_settings($withclearwrong = false, 486 $withshownumpartscorrect = false) { 487 $mform = $this->_form; 488 489 $mform->addElement('header', 'multitriesheader', 490 get_string('settingsformultipletries', 'question')); 491 492 $penalties = array( 493 1.0000000, 494 0.5000000, 495 0.3333333, 496 0.2500000, 497 0.2000000, 498 0.1000000, 499 0.0000000 500 ); 501 if (!empty($this->question->penalty) && !in_array($this->question->penalty, $penalties)) { 502 $penalties[] = $this->question->penalty; 503 sort($penalties); 504 } 505 $penaltyoptions = array(); 506 foreach ($penalties as $penalty) { 507 $penaltyoptions["{$penalty}"] = format_float(100 * $penalty, 5, true, true) . '%'; 508 } 509 $mform->addElement('select', 'penalty', 510 get_string('penaltyforeachincorrecttry', 'question'), $penaltyoptions); 511 $mform->addHelpButton('penalty', 'penaltyforeachincorrecttry', 'question'); 512 $mform->setDefault('penalty', $this->get_default_value('penalty', 0.3333333)); 513 514 if (isset($this->question->hints)) { 515 $counthints = count($this->question->hints); 516 } else { 517 $counthints = 0; 518 } 519 520 if ($this->question->formoptions->repeatelements) { 521 $repeatsatstart = max(self::DEFAULT_NUM_HINTS, $counthints); 522 } else { 523 $repeatsatstart = $counthints; 524 } 525 526 list($repeated, $repeatedoptions) = $this->get_hint_fields( 527 $withclearwrong, $withshownumpartscorrect); 528 $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 529 'numhints', 'addhint', 1, get_string('addanotherhint', 'question'), true); 530 } 531 532 public function set_data($question) { 533 question_bank::get_qtype($question->qtype)->set_default_options($question); 534 535 // Prepare question text. 536 $draftid = file_get_submitted_draft_itemid('questiontext'); 537 538 if (!empty($question->questiontext)) { 539 $questiontext = $question->questiontext; 540 } else { 541 $questiontext = $this->_form->getElement('questiontext')->getValue(); 542 $questiontext = $questiontext['text']; 543 } 544 $questiontext = file_prepare_draft_area($draftid, $this->context->id, 545 'question', 'questiontext', empty($question->id) ? null : (int) $question->id, 546 $this->fileoptions, $questiontext); 547 548 $question->questiontext = array(); 549 $question->questiontext['text'] = $questiontext; 550 $question->questiontext['format'] = empty($question->questiontextformat) ? 551 editors_get_preferred_format() : $question->questiontextformat; 552 $question->questiontext['itemid'] = $draftid; 553 554 // Prepare general feedback. 555 $draftid = file_get_submitted_draft_itemid('generalfeedback'); 556 557 if (empty($question->generalfeedback)) { 558 $generalfeedback = $this->_form->getElement('generalfeedback')->getValue(); 559 $question->generalfeedback = $generalfeedback['text']; 560 } 561 562 $feedback = file_prepare_draft_area($draftid, $this->context->id, 563 'question', 'generalfeedback', empty($question->id) ? null : (int) $question->id, 564 $this->fileoptions, $question->generalfeedback); 565 $question->generalfeedback = array(); 566 $question->generalfeedback['text'] = $feedback; 567 $question->generalfeedback['format'] = empty($question->generalfeedbackformat) ? 568 editors_get_preferred_format() : $question->generalfeedbackformat; 569 $question->generalfeedback['itemid'] = $draftid; 570 571 // Remove unnecessary trailing 0s form grade fields. 572 if (isset($question->defaultgrade)) { 573 $question->defaultgrade = 0 + $question->defaultgrade; 574 } 575 if (isset($question->penalty)) { 576 $question->penalty = 0 + $question->penalty; 577 } 578 579 // Set any options. 580 $extraquestionfields = question_bank::get_qtype($question->qtype)->extra_question_fields(); 581 if (is_array($extraquestionfields) && !empty($question->options)) { 582 array_shift($extraquestionfields); 583 foreach ($extraquestionfields as $field) { 584 if (property_exists($question->options, $field)) { 585 $question->$field = $question->options->$field; 586 } 587 } 588 } 589 590 // Subclass adds data_preprocessing code here. 591 $question = $this->data_preprocessing($question); 592 593 parent::set_data($question); 594 } 595 596 /** 597 * Perform an preprocessing needed on the data passed to {@link set_data()} 598 * before it is used to initialise the form. 599 * @param object $question the data being passed to the form. 600 * @return object $question the modified data. 601 */ 602 protected function data_preprocessing($question) { 603 return $question; 604 } 605 606 /** 607 * Perform the necessary preprocessing for the fields added by 608 * {@link add_per_answer_fields()}. 609 * @param object $question the data being passed to the form. 610 * @return object $question the modified data. 611 */ 612 protected function data_preprocessing_answers($question, $withanswerfiles = false) { 613 if (empty($question->options->answers)) { 614 return $question; 615 } 616 617 $key = 0; 618 foreach ($question->options->answers as $answer) { 619 if ($withanswerfiles) { 620 // Prepare the feedback editor to display files in draft area. 621 $draftitemid = file_get_submitted_draft_itemid('answer['.$key.']'); 622 $question->answer[$key]['text'] = file_prepare_draft_area( 623 $draftitemid, // Draftid 624 $this->context->id, // context 625 'question', // component 626 'answer', // filarea 627 !empty($answer->id) ? (int) $answer->id : null, // itemid 628 $this->fileoptions, // options 629 $answer->answer // text. 630 ); 631 $question->answer[$key]['itemid'] = $draftitemid; 632 $question->answer[$key]['format'] = $answer->answerformat; 633 } else { 634 $question->answer[$key] = $answer->answer; 635 } 636 637 $question->fraction[$key] = 0 + $answer->fraction; 638 $question->feedback[$key] = array(); 639 640 // Evil hack alert. Formslib can store defaults in two ways for 641 // repeat elements: 642 // ->_defaultValues['fraction[0]'] and 643 // ->_defaultValues['fraction'][0]. 644 // The $repeatedoptions['fraction']['default'] = 0 bit above means 645 // that ->_defaultValues['fraction[0]'] has already been set, but we 646 // are using object notation here, so we will be setting 647 // ->_defaultValues['fraction'][0]. That does not work, so we have 648 // to unset ->_defaultValues['fraction[0]']. 649 unset($this->_form->_defaultValues["fraction[{$key}]"]); 650 651 // Prepare the feedback editor to display files in draft area. 652 $draftitemid = file_get_submitted_draft_itemid('feedback['.$key.']'); 653 $question->feedback[$key]['text'] = file_prepare_draft_area( 654 $draftitemid, // Draftid 655 $this->context->id, // context 656 'question', // component 657 'answerfeedback', // filarea 658 !empty($answer->id) ? (int) $answer->id : null, // itemid 659 $this->fileoptions, // options 660 $answer->feedback // text. 661 ); 662 $question->feedback[$key]['itemid'] = $draftitemid; 663 $question->feedback[$key]['format'] = $answer->feedbackformat; 664 $key++; 665 } 666 667 // Now process extra answer fields. 668 $extraanswerfields = question_bank::get_qtype($question->qtype)->extra_answer_fields(); 669 if (is_array($extraanswerfields)) { 670 // Omit table name. 671 array_shift($extraanswerfields); 672 $question = $this->data_preprocessing_extra_answer_fields($question, $extraanswerfields); 673 } 674 675 return $question; 676 } 677 678 /** 679 * Perform the necessary preprocessing for the extra answer fields. 680 * 681 * Questions that do something not trivial when editing extra answer fields 682 * will want to override this. 683 * @param object $question the data being passed to the form. 684 * @param array $extraanswerfields extra answer fields (without table name). 685 * @return object $question the modified data. 686 */ 687 protected function data_preprocessing_extra_answer_fields($question, $extraanswerfields) { 688 // Setting $question->$field[$key] won't work in PHP, so we need set an array of answer values to $question->$field. 689 // As we may have several extra fields with data for several answers in each, we use an array of arrays. 690 // Index in $extrafieldsdata is an extra answer field name, value - array of it's data for each answer. 691 $extrafieldsdata = array(); 692 // First, prepare an array if empty arrays for each extra answer fields data. 693 foreach ($extraanswerfields as $field) { 694 $extrafieldsdata[$field] = array(); 695 } 696 697 // Fill arrays with data from $question->options->answers. 698 $key = 0; 699 foreach ($question->options->answers as $answer) { 700 foreach ($extraanswerfields as $field) { 701 // See hack comment in {@link data_preprocessing_answers()}. 702 unset($this->_form->_defaultValues["{$field}[{$key}]"]); 703 $extrafieldsdata[$field][$key] = $this->data_preprocessing_extra_answer_field($answer, $field); 704 } 705 $key++; 706 } 707 708 // Set this data in the $question object. 709 foreach ($extraanswerfields as $field) { 710 $question->$field = $extrafieldsdata[$field]; 711 } 712 return $question; 713 } 714 715 /** 716 * Perfmorm preprocessing for particular extra answer field. 717 * 718 * Questions with non-trivial DB - form element relationship will 719 * want to override this. 720 * @param object $answer an answer object to get extra field from. 721 * @param string $field extra answer field name. 722 * @return field value to be set to the form. 723 */ 724 protected function data_preprocessing_extra_answer_field($answer, $field) { 725 return $answer->$field; 726 } 727 728 /** 729 * Perform the necessary preprocessing for the fields added by 730 * {@link add_combined_feedback_fields()}. 731 * @param object $question the data being passed to the form. 732 * @return object $question the modified data. 733 */ 734 protected function data_preprocessing_combined_feedback($question, 735 $withshownumcorrect = false) { 736 if (empty($question->options)) { 737 return $question; 738 } 739 740 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 741 foreach ($fields as $feedbackname) { 742 $draftid = file_get_submitted_draft_itemid($feedbackname); 743 $feedback = array(); 744 $feedback['text'] = file_prepare_draft_area( 745 $draftid, // Draftid 746 $this->context->id, // context 747 'question', // component 748 $feedbackname, // filarea 749 !empty($question->id) ? (int) $question->id : null, // itemid 750 $this->fileoptions, // options 751 $question->options->$feedbackname // text. 752 ); 753 $feedbackformat = $feedbackname . 'format'; 754 $feedback['format'] = $question->options->$feedbackformat; 755 $feedback['itemid'] = $draftid; 756 757 $question->$feedbackname = $feedback; 758 } 759 760 if ($withshownumcorrect) { 761 $question->shownumcorrect = $question->options->shownumcorrect; 762 } 763 764 return $question; 765 } 766 767 /** 768 * Perform the necessary preprocessing for the hint fields. 769 * @param object $question the data being passed to the form. 770 * @return object $question the modified data. 771 */ 772 protected function data_preprocessing_hints($question, $withclearwrong = false, 773 $withshownumpartscorrect = false) { 774 if (empty($question->hints)) { 775 return $question; 776 } 777 778 $key = 0; 779 foreach ($question->hints as $hint) { 780 $question->hint[$key] = array(); 781 782 // Prepare feedback editor to display files in draft area. 783 $draftitemid = file_get_submitted_draft_itemid('hint['.$key.']'); 784 $question->hint[$key]['text'] = file_prepare_draft_area( 785 $draftitemid, // Draftid 786 $this->context->id, // context 787 'question', // component 788 'hint', // filarea 789 !empty($hint->id) ? (int) $hint->id : null, // itemid 790 $this->fileoptions, // options 791 $hint->hint // text. 792 ); 793 $question->hint[$key]['itemid'] = $draftitemid; 794 $question->hint[$key]['format'] = $hint->hintformat; 795 $key++; 796 797 if ($withclearwrong) { 798 $question->hintclearwrong[] = $hint->clearwrong; 799 } 800 if ($withshownumpartscorrect) { 801 $question->hintshownumcorrect[] = $hint->shownumcorrect; 802 } 803 } 804 805 return $question; 806 } 807 808 public function validation($fromform, $files) { 809 global $DB; 810 811 $errors = parent::validation($fromform, $files); 812 if (empty($fromform['makecopy']) && isset($this->question->id) 813 && ($this->question->formoptions->canedit || 814 $this->question->formoptions->cansaveasnew) 815 && empty($fromform['usecurrentcat']) && !$this->question->formoptions->canmove) { 816 $errors['currentgrp'] = get_string('nopermissionmove', 'question'); 817 } 818 819 // Category. 820 if (empty($fromform['category'])) { 821 // User has provided an invalid category. 822 $errors['category'] = get_string('required'); 823 } 824 825 // Default mark. 826 if (array_key_exists('defaultmark', $fromform) && $fromform['defaultmark'] < 0) { 827 $errors['defaultmark'] = get_string('defaultmarkmustbepositive', 'question'); 828 } 829 830 // Can only have one idnumber per category. 831 if (strpos($fromform['category'], ',') !== false) { 832 list($category, $categorycontextid) = explode(',', $fromform['category']); 833 } else { 834 $category = $fromform['category']; 835 } 836 if (isset($fromform['idnumber']) && ((string) $fromform['idnumber'] !== '')) { 837 if (empty($fromform['usecurrentcat']) && !empty($fromform['categorymoveto'])) { 838 $categoryinfo = $fromform['categorymoveto']; 839 } else { 840 $categoryinfo = $fromform['category']; 841 } 842 list($categoryid, $notused) = explode(',', $categoryinfo); 843 $conditions = 'category = ? AND idnumber = ?'; 844 $params = [$categoryid, $fromform['idnumber']]; 845 if (!empty($this->question->id)) { 846 $conditions .= ' AND id <> ?'; 847 $params[] = $this->question->id; 848 } 849 if ($DB->record_exists_select('question', $conditions, $params)) { 850 $errors['idnumber'] = get_string('idnumbertaken', 'error'); 851 } 852 } 853 854 return $errors; 855 } 856 857 /** 858 * Override this in the subclass to question type name. 859 * @return the question type name, should be the same as the name() method 860 * in the question type class. 861 */ 862 public abstract function qtype(); 863 864 /** 865 * Returns an array of editor options with collapsed options turned off. 866 * @deprecated since 2.6 867 * @return array 868 */ 869 protected function get_non_collabsible_editor_options() { 870 debugging('get_non_collabsible_editor_options() is deprecated, use $this->editoroptions instead.', DEBUG_DEVELOPER); 871 return $this->editoroptions; 872 } 873 874 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body