|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00030 abstract class question_wizard_form extends moodleform { 00034 protected function add_hidden_fields() { 00035 $mform = $this->_form; 00036 00037 $mform->addElement('hidden', 'id'); 00038 $mform->setType('id', PARAM_INT); 00039 00040 $mform->addElement('hidden', 'inpopup'); 00041 $mform->setType('inpopup', PARAM_INT); 00042 00043 $mform->addElement('hidden', 'cmid'); 00044 $mform->setType('cmid', PARAM_INT); 00045 00046 $mform->addElement('hidden', 'courseid'); 00047 $mform->setType('courseid', PARAM_INT); 00048 00049 $mform->addElement('hidden', 'returnurl'); 00050 $mform->setType('returnurl', PARAM_LOCALURL); 00051 00052 $mform->addElement('hidden', 'scrollpos'); 00053 $mform->setType('scrollpos', PARAM_INT); 00054 00055 $mform->addElement('hidden', 'appendqnumstring'); 00056 $mform->setType('appendqnumstring', PARAM_ALPHA); 00057 } 00058 } 00059 00069 abstract class question_edit_form extends question_wizard_form { 00070 const DEFAULT_NUM_HINTS = 2; 00071 00078 protected $question; 00079 00080 protected $contexts; 00081 protected $category; 00082 protected $categorycontext; 00083 00085 public $context; 00087 public $editoroptions; 00089 public $fileoptions; 00091 public $instance; 00092 00093 public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) { 00094 global $DB; 00095 00096 $this->question = $question; 00097 $this->contexts = $contexts; 00098 00099 $record = $DB->get_record('question_categories', 00100 array('id' => $question->category), 'contextid'); 00101 $this->context = get_context_instance_by_id($record->contextid); 00102 00103 $this->editoroptions = array('subdirs' => 1, 'maxfiles' => EDITOR_UNLIMITED_FILES, 00104 'context' => $this->context); 00105 $this->fileoptions = array('subdirs' => 1, 'maxfiles' => -1, 'maxbytes' => -1); 00106 00107 $this->category = $category; 00108 $this->categorycontext = get_context_instance_by_id($category->contextid); 00109 00110 parent::__construct($submiturl, null, 'post', '', null, $formeditable); 00111 } 00112 00120 protected function definition() { 00121 global $COURSE, $CFG, $DB; 00122 00123 $qtype = $this->qtype(); 00124 $langfile = "qtype_$qtype"; 00125 00126 $mform = $this->_form; 00127 00128 // Standard fields at the start of the form. 00129 $mform->addElement('header', 'generalheader', get_string("general", 'form')); 00130 00131 if (!isset($this->question->id)) { 00132 // Adding question 00133 $mform->addElement('questioncategory', 'category', get_string('category', 'question'), 00134 array('contexts' => $this->contexts->having_cap('moodle/question:add'))); 00135 } else if (!($this->question->formoptions->canmove || 00136 $this->question->formoptions->cansaveasnew)) { 00137 // Editing question with no permission to move from category. 00138 $mform->addElement('questioncategory', 'category', get_string('category', 'question'), 00139 array('contexts' => array($this->categorycontext))); 00140 } else if ($this->question->formoptions->movecontext) { 00141 // Moving question to another context. 00142 $mform->addElement('questioncategory', 'categorymoveto', 00143 get_string('category', 'question'), 00144 array('contexts' => $this->contexts->having_cap('moodle/question:add'))); 00145 00146 } else { 00147 // Editing question with permission to move from category or save as new q 00148 $currentgrp = array(); 00149 $currentgrp[0] = $mform->createElement('questioncategory', 'category', 00150 get_string('categorycurrent', 'question'), 00151 array('contexts' => array($this->categorycontext))); 00152 if ($this->question->formoptions->canedit || 00153 $this->question->formoptions->cansaveasnew) { 00154 //not move only form 00155 $currentgrp[1] = $mform->createElement('checkbox', 'usecurrentcat', '', 00156 get_string('categorycurrentuse', 'question')); 00157 $mform->setDefault('usecurrentcat', 1); 00158 } 00159 $currentgrp[0]->freeze(); 00160 $currentgrp[0]->setPersistantFreeze(false); 00161 $mform->addGroup($currentgrp, 'currentgrp', 00162 get_string('categorycurrent', 'question'), null, false); 00163 00164 $mform->addElement('questioncategory', 'categorymoveto', 00165 get_string('categorymoveto', 'question'), 00166 array('contexts' => array($this->categorycontext))); 00167 if ($this->question->formoptions->canedit || 00168 $this->question->formoptions->cansaveasnew) { 00169 //not move only form 00170 $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked'); 00171 } 00172 } 00173 00174 $mform->addElement('text', 'name', get_string('questionname', 'question'), 00175 array('size' => 50)); 00176 $mform->setType('name', PARAM_TEXT); 00177 $mform->addRule('name', null, 'required', null, 'client'); 00178 00179 $mform->addElement('editor', 'questiontext', get_string('questiontext', 'question'), 00180 array('rows' => 15), $this->editoroptions); 00181 $mform->setType('questiontext', PARAM_RAW); 00182 00183 $mform->addElement('text', 'defaultmark', get_string('defaultmark', 'question'), 00184 array('size' => 3)); 00185 $mform->setType('defaultmark', PARAM_FLOAT); 00186 $mform->setDefault('defaultmark', 1); 00187 $mform->addRule('defaultmark', null, 'required', null, 'client'); 00188 00189 $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'question'), 00190 array('rows' => 10), $this->editoroptions); 00191 $mform->setType('generalfeedback', PARAM_RAW); 00192 $mform->addHelpButton('generalfeedback', 'generalfeedback', 'question'); 00193 00194 // Any questiontype specific fields. 00195 $this->definition_inner($mform); 00196 00197 if (!empty($CFG->usetags)) { 00198 $mform->addElement('header', 'tagsheader', get_string('tags')); 00199 $mform->addElement('tags', 'tags', get_string('tags')); 00200 } 00201 00202 if (!empty($this->question->id)) { 00203 $mform->addElement('header', 'createdmodifiedheader', 00204 get_string('createdmodifiedheader', 'question')); 00205 $a = new stdClass(); 00206 if (!empty($this->question->createdby)) { 00207 $a->time = userdate($this->question->timecreated); 00208 $a->user = fullname($DB->get_record( 00209 'user', array('id' => $this->question->createdby))); 00210 } else { 00211 $a->time = get_string('unknown', 'question'); 00212 $a->user = get_string('unknown', 'question'); 00213 } 00214 $mform->addElement('static', 'created', get_string('created', 'question'), 00215 get_string('byandon', 'question', $a)); 00216 if (!empty($this->question->modifiedby)) { 00217 $a = new stdClass(); 00218 $a->time = userdate($this->question->timemodified); 00219 $a->user = fullname($DB->get_record( 00220 'user', array('id' => $this->question->modifiedby))); 00221 $mform->addElement('static', 'modified', get_string('modified', 'question'), 00222 get_string('byandon', 'question', $a)); 00223 } 00224 } 00225 00226 $this->add_hidden_fields(); 00227 00228 $mform->addElement('hidden', 'movecontext'); 00229 $mform->setType('movecontext', PARAM_BOOL); 00230 00231 $mform->addElement('hidden', 'qtype'); 00232 $mform->setType('qtype', PARAM_ALPHA); 00233 00234 $buttonarray = array(); 00235 if (!empty($this->question->id)) { 00236 // Editing / moving question 00237 if ($this->question->formoptions->movecontext) { 00238 $buttonarray[] = $mform->createElement('submit', 'submitbutton', 00239 get_string('moveq', 'question')); 00240 } else if ($this->question->formoptions->canedit || 00241 $this->question->formoptions->canmove || 00242 $this->question->formoptions->movecontext) { 00243 $buttonarray[] = $mform->createElement('submit', 'submitbutton', 00244 get_string('savechanges')); 00245 } 00246 if ($this->question->formoptions->cansaveasnew) { 00247 $buttonarray[] = $mform->createElement('submit', 'makecopy', 00248 get_string('makecopy', 'question')); 00249 } 00250 $buttonarray[] = $mform->createElement('cancel'); 00251 } else { 00252 // Adding new question 00253 $buttonarray[] = $mform->createElement('submit', 'submitbutton', 00254 get_string('savechanges')); 00255 $buttonarray[] = $mform->createElement('cancel'); 00256 } 00257 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 00258 $mform->closeHeaderBefore('buttonar'); 00259 00260 if ($this->question->formoptions->movecontext) { 00261 $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar')); 00262 } else if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || 00263 $this->question->formoptions->cansaveasnew))) { 00264 $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp')); 00265 } 00266 } 00267 00273 protected function definition_inner($mform) { 00274 // By default, do nothing. 00275 } 00276 00287 protected function get_per_answer_fields($mform, $label, $gradeoptions, 00288 &$repeatedoptions, &$answersoption) { 00289 $repeated = array(); 00290 $repeated[] = $mform->createElement('header', 'answerhdr', $label); 00291 $repeated[] = $mform->createElement('text', 'answer', 00292 get_string('answer', 'question'), array('size' => 80)); 00293 $repeated[] = $mform->createElement('select', 'fraction', 00294 get_string('grade'), $gradeoptions); 00295 $repeated[] = $mform->createElement('editor', 'feedback', 00296 get_string('feedback', 'question'), array('rows' => 5), $this->editoroptions); 00297 $repeatedoptions['answer']['type'] = PARAM_RAW; 00298 $repeatedoptions['fraction']['default'] = 0; 00299 $answersoption = 'answers'; 00300 return $repeated; 00301 } 00302 00313 protected function add_per_answer_fields(&$mform, $label, $gradeoptions, 00314 $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) { 00315 $answersoption = ''; 00316 $repeatedoptions = array(); 00317 $repeated = $this->get_per_answer_fields($mform, $label, $gradeoptions, 00318 $repeatedoptions, $answersoption); 00319 00320 if (isset($this->question->options)) { 00321 $countanswers = count($this->question->options->$answersoption); 00322 } else { 00323 $countanswers = 0; 00324 } 00325 if ($this->question->formoptions->repeatelements) { 00326 $repeatsatstart = max($minoptions, $countanswers + $addoptions); 00327 } else { 00328 $repeatsatstart = $countanswers; 00329 } 00330 00331 $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 00332 'noanswers', 'addanswers', $addoptions, 00333 get_string('addmorechoiceblanks', 'qtype_multichoice')); 00334 } 00335 00336 protected function add_combined_feedback_fields($withshownumpartscorrect = false) { 00337 $mform = $this->_form; 00338 00339 $mform->addElement('header', 'combinedfeedbackhdr', 00340 get_string('combinedfeedback', 'question')); 00341 00342 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 00343 foreach ($fields as $feedbackname) { 00344 $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'question'), 00345 array('rows' => 5), $this->editoroptions); 00346 $mform->setType($feedbackname, PARAM_RAW); 00347 00348 if ($withshownumpartscorrect && $feedbackname == 'partiallycorrectfeedback') { 00349 $mform->addElement('advcheckbox', 'shownumcorrect', 00350 get_string('options', 'question'), 00351 get_string('shownumpartscorrect', 'question')); 00352 } 00353 } 00354 } 00355 00356 protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) { 00357 $mform = $this->_form; 00358 00359 $repeated = array(); 00360 $repeated[] = $mform->createElement('header', 'hinthdr', get_string('hintn', 'question')); 00361 $repeated[] = $mform->createElement('editor', 'hint', get_string('hinttext', 'question'), 00362 array('rows' => 5), $this->editoroptions); 00363 $repeatedoptions['hint']['type'] = PARAM_RAW; 00364 00365 if ($withclearwrong) { 00366 $repeated[] = $mform->createElement('advcheckbox', 'hintclearwrong', 00367 get_string('options', 'question'), get_string('clearwrongparts', 'question')); 00368 } 00369 if ($withshownumpartscorrect) { 00370 $repeated[] = $mform->createElement('advcheckbox', 'hintshownumcorrect', '', 00371 get_string('shownumpartscorrect', 'question')); 00372 } 00373 00374 return array($repeated, $repeatedoptions); 00375 } 00376 00377 protected function add_interactive_settings($withclearwrong = false, 00378 $withshownumpartscorrect = false) { 00379 $mform = $this->_form; 00380 00381 $mform->addElement('header', 'multitriesheader', 00382 get_string('settingsformultipletries', 'question')); 00383 00384 $penalties = array( 00385 1.0000000, 00386 0.5000000, 00387 0.3333333, 00388 0.2500000, 00389 0.2000000, 00390 0.1000000, 00391 0.0000000 00392 ); 00393 if (!empty($this->question->penalty) && !in_array($this->question->penalty, $penalties)) { 00394 $penalties[] = $this->question->penalty; 00395 sort($penalties); 00396 } 00397 $penaltyoptions = array(); 00398 foreach ($penalties as $penalty) { 00399 $penaltyoptions["$penalty"] = (100 * $penalty) . '%'; 00400 } 00401 $mform->addElement('select', 'penalty', 00402 get_string('penaltyforeachincorrecttry', 'question'), $penaltyoptions); 00403 $mform->addRule('penalty', null, 'required', null, 'client'); 00404 $mform->addHelpButton('penalty', 'penaltyforeachincorrecttry', 'question'); 00405 $mform->setDefault('penalty', 0.3333333); 00406 00407 if (isset($this->question->hints)) { 00408 $counthints = count($this->question->hints); 00409 } else { 00410 $counthints = 0; 00411 } 00412 00413 if ($this->question->formoptions->repeatelements) { 00414 $repeatsatstart = max(self::DEFAULT_NUM_HINTS, $counthints); 00415 } else { 00416 $repeatsatstart = $counthints; 00417 } 00418 00419 list($repeated, $repeatedoptions) = $this->get_hint_fields( 00420 $withclearwrong, $withshownumpartscorrect); 00421 $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 00422 'numhints', 'addhint', 1, get_string('addanotherhint', 'question')); 00423 } 00424 00425 public function set_data($question) { 00426 question_bank::get_qtype($question->qtype)->set_default_options($question); 00427 00428 // prepare question text 00429 $draftid = file_get_submitted_draft_itemid('questiontext'); 00430 00431 if (!empty($question->questiontext)) { 00432 $questiontext = $question->questiontext; 00433 } else { 00434 $questiontext = ''; 00435 } 00436 $questiontext = file_prepare_draft_area($draftid, $this->context->id, 00437 'question', 'questiontext', empty($question->id) ? null : (int) $question->id, 00438 $this->fileoptions, $questiontext); 00439 00440 $question->questiontext = array(); 00441 $question->questiontext['text'] = $questiontext; 00442 $question->questiontext['format'] = empty($question->questiontextformat) ? 00443 editors_get_preferred_format() : $question->questiontextformat; 00444 $question->questiontext['itemid'] = $draftid; 00445 00446 // prepare general feedback 00447 $draftid = file_get_submitted_draft_itemid('generalfeedback'); 00448 00449 if (empty($question->generalfeedback)) { 00450 $question->generalfeedback = ''; 00451 } 00452 00453 $feedback = file_prepare_draft_area($draftid, $this->context->id, 00454 'question', 'generalfeedback', empty($question->id) ? null : (int) $question->id, 00455 $this->fileoptions, $question->generalfeedback); 00456 $question->generalfeedback = array(); 00457 $question->generalfeedback['text'] = $feedback; 00458 $question->generalfeedback['format'] = empty($question->generalfeedbackformat) ? 00459 editors_get_preferred_format() : $question->generalfeedbackformat; 00460 $question->generalfeedback['itemid'] = $draftid; 00461 00462 // Remove unnecessary trailing 0s form grade fields. 00463 if (isset($question->defaultgrade)) { 00464 $question->defaultgrade = 0 + $question->defaultgrade; 00465 } 00466 if (isset($question->penalty)) { 00467 $question->penalty = 0 + $question->penalty; 00468 } 00469 00470 // Set any options. 00471 $extraquestionfields = question_bank::get_qtype($question->qtype)->extra_question_fields(); 00472 if (is_array($extraquestionfields) && !empty($question->options)) { 00473 array_shift($extraquestionfields); 00474 foreach ($extraquestionfields as $field) { 00475 if (isset($question->options->$field)) { 00476 $question->$field = $question->options->$field; 00477 } 00478 } 00479 } 00480 00481 // subclass adds data_preprocessing code here 00482 $question = $this->data_preprocessing($question); 00483 00484 parent::set_data($question); 00485 } 00486 00493 protected function data_preprocessing($question) { 00494 return $question; 00495 } 00496 00503 protected function data_preprocessing_answers($question, $withanswerfiles = false) { 00504 if (empty($question->options->answers)) { 00505 return $question; 00506 } 00507 00508 $key = 0; 00509 foreach ($question->options->answers as $answer) { 00510 if ($withanswerfiles) { 00511 // Prepare the feedback editor to display files in draft area 00512 $draftitemid = file_get_submitted_draft_itemid('answer['.$key.']'); 00513 $question->answer[$key]['text'] = file_prepare_draft_area( 00514 $draftitemid, // draftid 00515 $this->context->id, // context 00516 'question', // component 00517 'answer', // filarea 00518 !empty($answer->id) ? (int) $answer->id : null, // itemid 00519 $this->fileoptions, // options 00520 $answer->answer // text 00521 ); 00522 $question->answer[$key]['itemid'] = $draftitemid; 00523 $question->answer[$key]['format'] = $answer->answerformat; 00524 } else { 00525 $question->answer[$key] = $answer->answer; 00526 } 00527 00528 $question->fraction[$key] = 0 + $answer->fraction; 00529 $question->feedback[$key] = array(); 00530 00531 // Evil hack alert. Formslib can store defaults in two ways for 00532 // repeat elements: 00533 // ->_defaultValues['fraction[0]'] and 00534 // ->_defaultValues['fraction'][0]. 00535 // The $repeatedoptions['fraction']['default'] = 0 bit above means 00536 // that ->_defaultValues['fraction[0]'] has already been set, but we 00537 // are using object notation here, so we will be setting 00538 // ->_defaultValues['fraction'][0]. That does not work, so we have 00539 // to unset ->_defaultValues['fraction[0]'] 00540 unset($this->_form->_defaultValues["fraction[$key]"]); 00541 00542 // Prepare the feedback editor to display files in draft area 00543 $draftitemid = file_get_submitted_draft_itemid('feedback['.$key.']'); 00544 $question->feedback[$key]['text'] = file_prepare_draft_area( 00545 $draftitemid, // draftid 00546 $this->context->id, // context 00547 'question', // component 00548 'answerfeedback', // filarea 00549 !empty($answer->id) ? (int) $answer->id : null, // itemid 00550 $this->fileoptions, // options 00551 $answer->feedback // text 00552 ); 00553 $question->feedback[$key]['itemid'] = $draftitemid; 00554 $question->feedback[$key]['format'] = $answer->feedbackformat; 00555 $key++; 00556 } 00557 return $question; 00558 } 00559 00566 protected function data_preprocessing_combined_feedback($question, 00567 $withshownumcorrect = false) { 00568 if (empty($question->options)) { 00569 return $question; 00570 } 00571 00572 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 00573 foreach ($fields as $feedbackname) { 00574 $draftid = file_get_submitted_draft_itemid($feedbackname); 00575 $feedback = array(); 00576 $feedback['text'] = file_prepare_draft_area( 00577 $draftid, // draftid 00578 $this->context->id, // context 00579 'question', // component 00580 $feedbackname, // filarea 00581 !empty($question->id) ? (int) $question->id : null, // itemid 00582 $this->fileoptions, // options 00583 $question->options->$feedbackname // text 00584 ); 00585 $feedbackformat = $feedbackname . 'format'; 00586 $feedback['format'] = $question->options->$feedbackformat; 00587 $feedback['itemid'] = $draftid; 00588 00589 $question->$feedbackname = $feedback; 00590 } 00591 00592 if ($withshownumcorrect) { 00593 $question->shownumcorrect = $question->options->shownumcorrect; 00594 } 00595 00596 return $question; 00597 } 00598 00604 protected function data_preprocessing_hints($question, $withclearwrong = false, 00605 $withshownumpartscorrect = false) { 00606 if (empty($question->hints)) { 00607 return $question; 00608 } 00609 00610 $key = 0; 00611 foreach ($question->hints as $hint) { 00612 $question->hint[$key] = array(); 00613 00614 // prepare feedback editor to display files in draft area 00615 $draftitemid = file_get_submitted_draft_itemid('hint['.$key.']'); 00616 $question->hint[$key]['text'] = file_prepare_draft_area( 00617 $draftitemid, // draftid 00618 $this->context->id, // context 00619 'question', // component 00620 'hint', // filarea 00621 !empty($hint->id) ? (int) $hint->id : null, // itemid 00622 $this->fileoptions, // options 00623 $hint->hint // text 00624 ); 00625 $question->hint[$key]['itemid'] = $draftitemid; 00626 $question->hint[$key]['format'] = $hint->hintformat; 00627 $key++; 00628 00629 if ($withclearwrong) { 00630 $question->hintclearwrong[] = $hint->clearwrong; 00631 } 00632 if ($withshownumpartscorrect) { 00633 $question->hintshownumcorrect[] = $hint->shownumcorrect; 00634 } 00635 } 00636 00637 return $question; 00638 } 00639 00640 public function validation($fromform, $files) { 00641 $errors = parent::validation($fromform, $files); 00642 if (empty($fromform->makecopy) && isset($this->question->id) 00643 && ($this->question->formoptions->canedit || 00644 $this->question->formoptions->cansaveasnew) 00645 && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove) { 00646 $errors['currentgrp'] = get_string('nopermissionmove', 'question'); 00647 } 00648 return $errors; 00649 } 00650 00656 public abstract function qtype(); 00657 }