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