Merged from STABLE
[moodle.git] / question / question.php
CommitLineData
516cf3eb 1<?php // $Id$
2/*
3* Page for editing questions
4*
5* This page shows the question editing form or processes the following actions:
6* - create new question (category, qtype)
7* - edit question (id, contextquiz (optional))
516cf3eb 8* - cancel (cancel)
e586cfb4 9*
54b3626b 10* TODO: currently this still treats the quiz as special
11* TODO: question versioning is not currently enabled
e586cfb4 12*
516cf3eb 13* @version $Id$
14* @author Martin Dougiamas and many others. This has recently been extensively
15* rewritten by members of the Serving Mathematics project
16* {@link http://maths.york.ac.uk/serving_maths}
17* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
e586cfb4 18* @package question
516cf3eb 19*/
20
e586cfb4 21 require_once("../config.php");
4267f47b 22 // NOTE - is this correct? This is just about editing screens?
23 require_once( "editlib.php" );
516cf3eb 24 require_once($CFG->libdir.'/filelib.php');
25
18cbc968 26 $id = optional_param('id', 0, PARAM_INT); // question id
516cf3eb 27
18cbc968 28 $qtype = optional_param('qtype', '', PARAM_FILE);
29 $category = optional_param('category', 0, PARAM_INT);
516cf3eb 30
18cbc968 31 // rqp questions set the type to rqp_nn where nn is the rqp_type id
32 if (substr($qtype, 0, 4) == 'rqp_') {
33 $typeid = (int) substr($qtype, 4);
34 $qtype = 'rqp';
516cf3eb 35 }
36
516cf3eb 37 if ($id) {
4f48fb42 38 if (! $question = get_record("question", "id", $id)) {
516cf3eb 39 error("This question doesn't exist");
40 }
41 if (!empty($category)) {
42 $question->category = $category;
43 }
dc1f00de 44 if (! $category = get_record("question_categories", "id", $question->category)) {
516cf3eb 45 error("This question doesn't belong to a valid category!");
46 }
47 if (! $course = get_record("course", "id", $category->course)) {
48 error("This question category doesn't belong to a valid course!");
49 }
50
51 $qtype = $question->qtype;
4eda4eec 52 if (!isset($QTYPES[$qtype])) {
53 $qtype = 'missingtype';
54 }
516cf3eb 55
516cf3eb 56 } else if ($category) { // only for creating new questions
dc1f00de 57 if (! $category = get_record("question_categories", "id", $category)) {
516cf3eb 58 error("This wasn't a valid category!");
59 }
60 if (! $course = get_record("course", "id", $category->course)) {
61 error("This category doesn't belong to a valid course!");
62 }
63
64 $question->category = $category->id;
65 $question->qtype = $qtype;
66
67 } else {
68 error("Must specify question id or category");
69 }
70
2662cf46 71 if (!isset($SESSION->returnurl)) {
72 $SESSION->returnurl = 'edit.php?courseid='.$course->id;
73 }
74
05054049 75 // TODO: generalise this so it works for any activity
2662cf46 76 $contextquiz = isset($SESSION->modform->instance) ? $SESSION->modform->instance : 0;
05054049 77
78 if (isset($_REQUEST['cancel'])) {
2662cf46 79 redirect($SESSION->returnurl);
05054049 80 }
81
516cf3eb 82 if (empty($qtype)) {
83 error("No question type was specified!");
f02c6f01 84 } else if (!isset($QTYPES[$qtype])) {
516cf3eb 85 error("Could not find question type: '$qtype'");
86 }
87
88 require_login($course->id, false);
89
90 if (!isteacheredit($course->id)) {
91 error("You can't modify these questions!");
92 }
93
e586cfb4 94 // TODO: remove restriction to quiz
516cf3eb 95 $streditingquestion = get_string('editingquestion', 'quiz');
96 if (isset($SESSION->modform->instance)) {
2662cf46 97 $strediting = '<a href="'.$SESSION->returnurl.'">'.get_string('editingquiz', 'quiz').'</a> -> '.
516cf3eb 98 $streditingquestion;
99 } else {
100 $strediting = '<a href="edit.php?courseid='.$course->id.'">'.
101 get_string("editquestions", "quiz").'</a> -> '.$streditingquestion;
102 }
103
104 print_header_simple("$streditingquestion", "", $strediting);
105
516cf3eb 106 if ($form = data_submitted() and confirm_sesskey()) {
107
108 if (isset($form->versioning) && isset($question->id) and false) { // disable versioning until it is fixed.
109 // use new code that handles whether to overwrite or copy a question
110 // and keeps track of the versions in the quiz_question_version table
111
112 // $replaceinquiz is an array with the ids of all quizzes in which
113 // the teacher has chosen to replace the old version
114 $replaceinquiz = array();
115 foreach($form as $key => $val) {
116 if ($tmp = quiz_parse_fieldname($key, 'q')) {
117 if ($tmp['mode'] == 'replace') {
118 $replaceinquiz[$tmp['id']] = $tmp['id'];
119 unset($form->$key);
120 }
121 }
122 }
123
124 // $quizlist is an array with the ids of quizzes which use this question
125 $quizlist = array();
126 if ($instances = get_records('quiz_question_instances', 'question', $question->id)) {
127 foreach($instances as $instance) {
128 $quizlist[$instance->quiz] = $instance->quiz;
129 }
130 }
131
132 if (isset($form->makecopy)) { // explicitly requested copies should be unhidden
133 $question->hidden = 0;
134 }
135
136 // Logic to determine whether old version should be overwritten
137 $makecopy = isset($form->makecopy) || (!$form->id); unset($form->makecopy);
138 if ($makecopy) {
139 $replaceold = false;
140 } else {
141 // this should be improved to exclude teacher preview responses and empty responses
142 // the current code leaves many unneeded questions in the database
4f48fb42 143 $hasresponses = record_exists('question_states', 'question', $form->id) or
144 record_exists('question_states', 'originalquestion', $form->id);
516cf3eb 145 $replaceinall = ($quizlist == $replaceinquiz); // question is being replaced in all quizzes
146 $replaceold = !$hasresponses && $replaceinall;
147 }
148
b01a2fcd 149 $oldquestionid = false;
516cf3eb 150 if (!$replaceold) { // create a new question
151 $oldquestionid = $question->id;
152 if (!$makecopy) {
4f48fb42 153 if (!set_field("question", 'hidden', 1, 'id', $question->id)) {
516cf3eb 154 error("Could not hide question!");
155 }
156 }
157 unset($question->id);
158 }
159 unset($makecopy, $hasresponses, $replaceinall, $replaceold);
f02c6f01 160 $question = $QTYPES[$qtype]->save_question($question, $form, $course);
516cf3eb 161 if(!isset($question->id)) {
162 error("Failed to save the question!");
163 }
164
b01a2fcd 165 if(!empty($oldquestionid)) {
516cf3eb 166 // create version entries for different quizzes
167 $version = new object();
168 $version->oldquestion = $oldquestionid;
169 $version->newquestion = $question->id;
170 $version->userid = $USER->id;
171 $version->timestamp = time();
172
173 foreach($replaceinquiz as $qid) {
174 $version->quiz = $qid;
175 if(!insert_record("quiz_question_versions", $version)) {
176 error("Could not store version information of question $oldquestionid in quiz $qid!");
177 }
178 }
179
180 /// now update the question references in the quizzes
181 if (!empty($replaceinquiz) and $quizzes = get_records_list("quiz", "id", implode(',', $replaceinquiz))) {
182
183 foreach($quizzes as $quiz) {
184 $questionlist = ",$quiz->questions,"; // a little hack with the commas here. not nice but effective
185 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
186 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
187 if (!set_field("quiz", 'questions', $questionlist, 'id', $quiz->id)) {
188 error("Could not update questionlist in quiz $quiz->id!");
189 }
190
191 // the quiz_question_instances table needs to be updated too (aah, the joys of duplication :)
192 if (!set_field('quiz_question_instances', 'question', $question->id, 'quiz', $quiz->id, 'question', $oldquestionid)) {
193 error("Could not update question instance!");
194 }
195 if (isset($SESSION->modform) && (int)$SESSION->modform->instance === (int)$quiz->id) {
196 $SESSION->modform->questions = $questionlist;
197 $SESSION->modform->grades[$question->id] = $SESSION->modform->grades[$oldquestionid];
198 unset($SESSION->modform->grades[$oldquestionid]);
199 }
200 }
201
202 // change question in attempts
203 if ($attempts = get_records_list('quiz_attempts', 'quiz', implode(',', $replaceinquiz))) {
204 foreach ($attempts as $attempt) {
205
206 // replace question id in $attempt->layout
207 $questionlist = ",$attempt->layout,"; // a little hack with the commas here. not nice but effective
208 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
209 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
210 if (!set_field('quiz_attempts', 'layout', $questionlist, 'id', $attempt->id)) {
211 error("Could not update layout in attempt $attempt->id!");
212 }
213
214 // set originalquestion in states
4f48fb42 215 set_field('question_states', 'originalquestion', $oldquestionid, 'attempt', $attempt->uniqueid, 'question', $question->id, 'originalquestion', '0');
516cf3eb 216
217 // replace question id in states
4f48fb42 218 set_field('question_states', 'question', $question->id, 'attempt', $attempt->uniqueid, 'question', $oldquestionid);
516cf3eb 219
220 // replace question id in sessions
221 set_field('question_sessions', 'questionid', $question->id, 'attemptid', $attempt->uniqueid, 'questionid', $oldquestionid);
222
223 }
224
225 // Now do anything question-type specific that is required to replace the question
dc1f00de 226 // For example questions that use the question_answers table to hold part of their question will
516cf3eb 227 // have to recode the answer ids in the states
f02c6f01 228 $QTYPES[$question->qtype]->change_states_question($oldquestionid, $question, $attempts);
516cf3eb 229 }
230 }
231 }
232 } else {
233 // use the old code which simply overwrites old versions
234 // it is also used for creating new questions
76de2cdf 235
236 if (isset($form->makecopy)) {
237 $question->hidden = 0; // explicitly requested copies should be unhidden
238 $question->id = 0; // This will prompt save_question to create a new question
239 }
f02c6f01 240 $question = $QTYPES[$qtype]->save_question($question, $form, $course);
516cf3eb 241 $replaceinquiz = 'all';
242 }
243
f02c6f01 244 if (empty($question->errors) && $QTYPES[$qtype]->finished_edit_wizard($form)) {
516cf3eb 245 // DISABLED AUTOMATIC REGRADING
246 // Automagically regrade all attempts (and states) in the affected quizzes
247 //if (!empty($replaceinquiz)) {
f02c6f01 248 // $QTYPES[$question->qtype]->get_question_options($question);
516cf3eb 249 // quiz_regrade_question_in_quizzes($question, $replaceinquiz);
250 //}
2662cf46 251 redirect($SESSION->returnurl);
516cf3eb 252 }
253 }
254
e586cfb4 255 // prepare the grades selector drop-down used by many question types
b4db945e 256 $creategrades = get_grade_options();
257 $gradeoptions = $creategrades->gradeoptions;
258 $gradeoptionsfull = $creategrades->gradeoptionsfull;
516cf3eb 259
dc1f00de 260 if (!$categories = question_category_menu($course->id, false)) {
516cf3eb 261 error("No categories!");
262 }
263
264
265 make_upload_directory("$course->id"); // Just in case
266 $coursefiles = get_directory_list("$CFG->dataroot/$course->id", $CFG->moddata);
267 foreach ($coursefiles as $filename) {
268 if (mimeinfo("icon", $filename) == "image.gif") {
269 $images["$filename"] = $filename;
270 }
271 }
272
273 // Print the question editing form
274
275 if (empty($question->id)) {
276 $question->id = "";
277 }
278 if (empty($question->name)) {
279 $question->name = "";
280 }
281 if (empty($question->questiontext)) {
282 $question->questiontext = "";
283 }
284 if (empty($question->image)) {
285 $question->image = "";
286 }
287 if (!isset($question->penalty)) {
288 $question->penalty = 0.1;
289 }
290 if (!isset($question->defaultgrade)) {
291 $question->defaultgrade = 1;
292 }
293
294 // Set up some Richtext editing if necessary
295 if ($usehtmleditor = can_use_richtext_editor()) {
296 $defaultformat = FORMAT_HTML;
297 } else {
298 $defaultformat = FORMAT_MOODLE;
299 }
300
301 if (isset($question->errors)) {
302 $err = $question->errors;
303 }
304
305 echo '<br />';
306 print_simple_box_start('center');
4eda4eec 307 require_once('type/'.$qtype.'/editquestion.php');
516cf3eb 308 print_simple_box_end();
309
310 if ($usehtmleditor) {
311 use_html_editor('questiontext');
312 }
313
314 print_footer($course);
315
316?>