MDL-16094 File storage conversion Quiz and Questions
[moodle.git] / question / type / edit_question_form.php
bloba0a5977070426594790a6087aff4292f1e05d646
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * A base class for question editing forms.
21 * @copyright &copy; 2006 The Open University
22 * @author T.J.Hunt@open.ac.uk
23 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
24 * @package questionbank
25 * @subpackage questiontypes
28 /**
29 * Form definition base class. This defines the common fields that
30 * all question types need. Question types should define their own
31 * class that inherits from this one, and implements the definition_inner()
32 * method.
34 * @package questionbank
35 * @subpackage questiontypes
37 class question_edit_form extends moodleform {
38 /**
39 * Question object with options and answers already loaded by get_question_options
40 * Be careful how you use this it is needed sometimes to set up the structure of the
41 * form in definition_inner but data is always loaded into the form with set_data.
43 * @var object
45 public $question;
46 public $contexts;
47 public $category;
48 public $categorycontext;
49 public $coursefilesid;
51 /** @var object current context */
52 public $context;
53 /** @var array html editor options */
54 public $editoroptions;
55 /** @var array options to preapre draft area */
56 public $fileoptions;
57 /** @var object instance of question type */
58 public $instance;
60 function question_edit_form($submiturl, $question, $category, $contexts, $formeditable = true){
61 global $DB;
63 $this->question = $question;
65 $this->contexts = $contexts;
67 $record = $DB->get_record('question_categories', array('id'=>$question->category), 'contextid');
68 $this->context = get_context_instance_by_id($record->contextid);
70 $this->editoroptions = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'context'=>$this->context);
71 $this->fileoptions = array('subdir'=>true, 'maxfiles'=>-1, 'maxbytes'=>-1);
73 $this->category = $category;
74 $this->categorycontext = get_context_instance_by_id($category->contextid);
75 //** *
76 //course id or site id depending on question cat context
77 $this->coursefilesid = get_filesdir_from_context(get_context_instance_by_id($category->contextid));
79 if (!empty($question->id)) {
80 $question->id = (int)$question->id;
81 //$this->instance = new
84 parent::moodleform($submiturl, null, 'post', '', null, $formeditable);
88 /**
89 * Build the form definition.
91 * This adds all the form fields that the default question type supports.
92 * If your question type does not support all these fields, then you can
93 * override this method and remove the ones you don't want with $mform->removeElement().
95 function definition() {
96 global $COURSE, $CFG, $DB;
98 $qtype = $this->qtype();
99 $langfile = "qtype_$qtype";
101 $mform =& $this->_form;
103 // Standard fields at the start of the form.
104 $mform->addElement('header', 'generalheader', get_string("general", 'form'));
106 if (!isset($this->question->id)){
107 //adding question
108 $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'),
109 array('contexts' => $this->contexts->having_cap('moodle/question:add')));
110 } elseif (!($this->question->formoptions->canmove || $this->question->formoptions->cansaveasnew)){
111 //editing question with no permission to move from category.
112 $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'),
113 array('contexts' => array($this->categorycontext)));
114 } elseif ($this->question->formoptions->movecontext){
115 //moving question to another context.
116 $mform->addElement('questioncategory', 'categorymoveto', get_string('category', 'quiz'),
117 array('contexts' => $this->contexts->having_cap('moodle/question:add')));
119 } else {
120 //editing question with permission to move from category or save as new q
121 $currentgrp = array();
122 $currentgrp[0] =& $mform->createElement('questioncategory', 'category', get_string('categorycurrent', 'question'),
123 array('contexts' => array($this->categorycontext)));
124 if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){
125 //not move only form
126 $currentgrp[1] =& $mform->createElement('checkbox', 'usecurrentcat', '', get_string('categorycurrentuse', 'question'));
127 $mform->setDefault('usecurrentcat', 1);
129 $currentgrp[0]->freeze();
130 $currentgrp[0]->setPersistantFreeze(false);
131 $mform->addGroup($currentgrp, 'currentgrp', get_string('categorycurrent', 'question'), null, false);
133 $mform->addElement('questioncategory', 'categorymoveto', get_string('categorymoveto', 'question'),
134 array('contexts' => array($this->categorycontext)));
135 if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){
136 //not move only form
137 $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked');
141 $mform->addElement('text', 'name', get_string('questionname', 'quiz'), array('size' => 50));
142 $mform->setType('name', PARAM_TEXT);
143 $mform->addRule('name', null, 'required', null, 'client');
145 $mform->addElement('editor', 'questiontext', get_string('questiontext', 'quiz'),
146 array('rows' => 15, 'course' => $this->coursefilesid), $this->editoroptions);
147 $mform->setType('questiontext', PARAM_RAW);
149 $mform->addElement('text', 'defaultgrade', get_string('defaultgrade', 'quiz'),
150 array('size' => 3));
151 $mform->setType('defaultgrade', PARAM_INT);
152 $mform->setDefault('defaultgrade', 1);
153 $mform->addRule('defaultgrade', null, 'required', null, 'client');
155 $mform->addElement('text', 'penalty', get_string('penaltyfactor', 'question'),
156 array('size' => 3));
157 $mform->setType('penalty', PARAM_NUMBER);
158 $mform->addRule('penalty', null, 'required', null, 'client');
159 $mform->addHelpButton('penalty', 'penaltyfactor', 'question');
160 $mform->setDefault('penalty', 0.1);
162 $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'quiz'),
163 array('rows' => 10, 'course' => $this->coursefilesid), $this->editoroptions);
164 $mform->setType('generalfeedback', PARAM_RAW);
165 $mform->addHelpButton('generalfeedback', 'generalfeedback', 'quiz');
167 // Any questiontype specific fields.
168 $this->definition_inner($mform);
170 if (!empty($CFG->usetags)) {
171 $mform->addElement('header', 'tagsheader', get_string('tags'));
172 $mform->addElement('tags', 'tags', get_string('tags'));
175 if (!empty($this->question->id)){
176 $mform->addElement('header', 'createdmodifiedheader', get_string('createdmodifiedheader', 'question'));
177 $a = new object();
178 if (!empty($this->question->createdby)){
179 $a->time = userdate($this->question->timecreated);
180 $a->user = fullname($DB->get_record('user', array('id' => $this->question->createdby)));
181 } else {
182 $a->time = get_string('unknown', 'question');
183 $a->user = get_string('unknown', 'question');
185 $mform->addElement('static', 'created', get_string('created', 'question'), get_string('byandon', 'question', $a));
186 if (!empty($this->question->modifiedby)){
187 $a = new object();
188 $a->time = userdate($this->question->timemodified);
189 $a->user = fullname($DB->get_record('user', array('id' => $this->question->modifiedby)));
190 $mform->addElement('static', 'modified', get_string('modified', 'question'), get_string('byandon', 'question', $a));
194 // Standard fields at the end of the form.
195 $mform->addElement('hidden', 'id');
196 $mform->setType('id', PARAM_INT);
198 $mform->addElement('hidden', 'qtype');
199 $mform->setType('qtype', PARAM_ALPHA);
201 $mform->addElement('hidden', 'inpopup');
202 $mform->setType('inpopup', PARAM_INT);
204 $mform->addElement('hidden', 'versioning');
205 $mform->setType('versioning', PARAM_BOOL);
207 $mform->addElement('hidden', 'movecontext');
208 $mform->setType('movecontext', PARAM_BOOL);
210 $mform->addElement('hidden', 'cmid');
211 $mform->setType('cmid', PARAM_INT);
212 $mform->setDefault('cmid', 0);
214 $mform->addElement('hidden', 'courseid');
215 $mform->setType('courseid', PARAM_INT);
216 $mform->setDefault('courseid', 0);
218 $mform->addElement('hidden', 'returnurl');
219 $mform->setType('returnurl', PARAM_LOCALURL);
220 $mform->setDefault('returnurl', 0);
222 $mform->addElement('hidden', 'appendqnumstring');
223 $mform->setType('appendqnumstring', PARAM_ALPHA);
224 $mform->setDefault('appendqnumstring', 0);
226 $buttonarray = array();
227 if (!empty($this->question->id)){
228 //editing / moving question
229 if ($this->question->formoptions->movecontext){
230 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('moveq', 'question'));
231 } elseif ($this->question->formoptions->canedit || $this->question->formoptions->canmove ||$this->question->formoptions->movecontext){
232 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
234 if ($this->question->formoptions->cansaveasnew){
235 $buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('makecopy', 'quiz'));
237 $buttonarray[] = &$mform->createElement('cancel');
238 } else {
239 // adding new question
240 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
241 $buttonarray[] = &$mform->createElement('cancel');
243 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
244 $mform->closeHeaderBefore('buttonar');
246 if ($this->question->formoptions->movecontext){
247 $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar'));
248 } elseif ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew))){
249 $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp'));
253 function validation($fromform, $files) {
254 $errors= parent::validation($fromform, $files);
255 if (empty($fromform->makecopy) && isset($this->question->id)
256 && ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew)
257 && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove) {
258 $errors['currentgrp'] = get_string('nopermissionmove', 'question');
260 return $errors;
264 * Add any question-type specific form fields.
266 * @param object $mform the form being built.
268 function definition_inner(&$mform) {
269 // By default, do nothing.
273 * Get the list of form elements to repeat, one for each answer.
274 * @param object $mform the form being built.
275 * @param $label the label to use for each option.
276 * @param $gradeoptions the possible grades for each answer.
277 * @param $repeatedoptions reference to array of repeated options to fill
278 * @param $answersoption reference to return the name of $question->options field holding an array of answers
279 * @return array of form fields.
281 function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
282 $repeated = array();
283 $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
284 $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50));
285 $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
286 $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'),
287 array('course' => $this->coursefilesid), $this->editoroptions);
288 $repeatedoptions['answer']['type'] = PARAM_RAW;
289 $repeatedoptions['fraction']['default'] = 0;
290 $answersoption = 'answers';
291 return $repeated;
295 * Add a set of form fields, obtained from get_per_answer_fields, to the form,
296 * one for each existing answer, with some blanks for some new ones.
297 * @param object $mform the form being built.
298 * @param $label the label to use for each option.
299 * @param $gradeoptions the possible grades for each answer.
300 * @param $minoptions the minimum number of answer blanks to display. Default QUESTION_NUMANS_START.
301 * @param $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD.
303 function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) {
304 $answersoption = '';
305 $repeatedoptions = array();
306 $repeated = $this->get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption);
308 if (isset($this->question->options)){
309 $countanswers = count($this->question->options->$answersoption);
310 } else {
311 $countanswers = 0;
313 if ($this->question->formoptions->repeatelements){
314 $repeatsatstart = max($minoptions, $countanswers + $addoptions);
315 } else {
316 $repeatsatstart = $countanswers;
319 $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', $addoptions, get_string('addmorechoiceblanks', 'qtype_multichoice'));
322 function set_data($question) {
323 global $QTYPES;
324 // prepare question text
325 $draftid = file_get_submitted_draft_itemid('questiontext');
327 if (!empty($question->questiontext)) {
328 $questiontext = $question->questiontext;
329 } else {
330 $questiontext = '';
332 $questiontext = file_prepare_draft_area($draftid, $this->context->id, 'question', 'questiontext', empty($question->id)?null:(int)$question->id, null, $questiontext);
334 $question->questiontext = array();
335 $question->questiontext['text'] = $questiontext;
336 $question->questiontext['format'] = empty($question->questiontextformat) ? editors_get_preferred_format() : $question->questiontextformat;
337 $question->questiontext['itemid'] = $draftid;
339 // prepare general feedback
340 $draftid = file_get_submitted_draft_itemid('generalfeedback');
342 if (empty($question->generalfeedback)) {
343 $question->generalfeedback = '';
346 $feedback = file_prepare_draft_area($draftid, $this->context->id, 'question', 'generalfeedback', empty($question->id)?null:(int)$question->id, null, $question->generalfeedback);
347 $question->generalfeedback = array();
348 $question->generalfeedback['text'] = $feedback;
349 $question->generalfeedback['format'] = empty($question->generalfeedbackformat) ? editors_get_preferred_format() : $question->generalfeedbackformat;
350 $question->generalfeedback['itemid'] = $draftid;
352 // Remove unnecessary trailing 0s form grade fields.
353 if (isset($question->defaultgrade)) {
354 $question->defaultgrade = 0 + $question->defaultgrade;
356 if (isset($question->penalty)) {
357 $question->penalty = 0 + $question->penalty;
360 // Set any options.
361 $extra_question_fields = $QTYPES[$question->qtype]->extra_question_fields();
362 if (is_array($extra_question_fields) && !empty($question->options)) {
363 array_shift($extra_question_fields);
364 foreach ($extra_question_fields as $field) {
365 if (!empty($question->options->$field)) {
366 $question->$field = $question->options->$field;
370 // subclass adds data_preprocessing code here
371 $question = $this->data_preprocessing($question);
372 parent::set_data($question);
376 * Any preprocessing needed for the settings form for the question type
378 * @param array $question - array to fill in with the default values
380 function data_preprocessing($question) {
381 return $question;
385 * Override this in the subclass to question type name.
386 * @return the question type name, should be the same as the name() method in the question type class.
388 function qtype() {
389 return '';