Updated rqp scripts to work with the separate question scripts in moodle/question
[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
29 $id = optional_param('id'); // question id
30
31 $qtype = optional_param('qtype');
32 $category = optional_param('category');
33
34 // a qtype > 99 means a remote question
35 if ($qtype > 99) {
36 $typeid = $qtype - 100;
37 $qtype = RQP;
38 }
39
e586cfb4 40 // TODO: generalise this so it works for any activity
41 $contextquiz = optional_param('contextquiz', 0, PARAM_INT); // the quiz from which this question is being edited
42 $editurl = ($contextquiz ? $CFG->wwwroot.'/mod/quiz/edit.php' : 'edit.php';
516cf3eb 43
44 if (isset($_REQUEST['cancel'])) {
e586cfb4 45 redirect($editurl);
516cf3eb 46 }
47
48 if(!empty($id) && isset($_REQUEST['hide']) && confirm_sesskey()) {
49 if(!set_field('quiz_questions', 'hidden', $_REQUEST['hide'], 'id', $id)) {
50 error("Faild to hide the question.");
51 }
e586cfb4 52 redirect($editurl);
516cf3eb 53 }
54
55 if ($id) {
56 if (! $question = get_record("quiz_questions", "id", $id)) {
57 error("This question doesn't exist");
58 }
59 if (!empty($category)) {
60 $question->category = $category;
61 }
62 if (! $category = get_record("quiz_categories", "id", $question->category)) {
63 error("This question doesn't belong to a valid category!");
64 }
65 if (! $course = get_record("course", "id", $category->course)) {
66 error("This question category doesn't belong to a valid course!");
67 }
68
69 $qtype = $question->qtype;
70
71
72 } else if ($category) { // only for creating new questions
73 if (! $category = get_record("quiz_categories", "id", $category)) {
74 error("This wasn't a valid category!");
75 }
76 if (! $course = get_record("course", "id", $category->course)) {
77 error("This category doesn't belong to a valid course!");
78 }
79
80 $question->category = $category->id;
81 $question->qtype = $qtype;
82
83 } else {
84 error("Must specify question id or category");
85 }
86
87 if (empty($qtype)) {
88 error("No question type was specified!");
89 } else if (!isset($QUIZ_QTYPES[$qtype])) {
90 error("Could not find question type: '$qtype'");
91 }
92
93 require_login($course->id, false);
94
95 if (!isteacheredit($course->id)) {
96 error("You can't modify these questions!");
97 }
98
e586cfb4 99 // TODO: remove restriction to quiz
516cf3eb 100 $streditingquestion = get_string('editingquestion', 'quiz');
101 if (isset($SESSION->modform->instance)) {
102 $strediting = '<a href="edit.php">'.get_string('editingquiz', 'quiz').'</a> -> '.
103 $streditingquestion;
104 } else {
105 $strediting = '<a href="edit.php?courseid='.$course->id.'">'.
106 get_string("editquestions", "quiz").'</a> -> '.$streditingquestion;
107 }
108
109 print_header_simple("$streditingquestion", "", $strediting);
110
111 if (isset($_REQUEST['delete'])) {
112 if (isset($confirm) and confirm_sesskey()) {
113 if ($confirm == md5($delete)) {
114 if (record_exists('quiz_question_instances', 'question', $question->id) or
115 record_exists('quiz_states', 'originalquestion', $question->id)) {
116 if (!set_field('quiz_questions', 'hidden', 1, 'id', $delete)) {
117 error('Was not able to hide question');
118 }
119 } else {
120 if (!delete_records("quiz_questions", "id", $question->id)) {
121 error("An error occurred trying to delete question (id $question->id)");
122 }
123 if (!delete_records("quiz_questions", "parent", $question->id)) {
124 error("An error occurred trying to delete question (id $question->id)");
125 }
126 }
127 redirect("edit.php");
128 } else {
129 error("Confirmation string was incorrect");
130 }
131
132 } else {
e586cfb4 133 // TODO: check for other modules using this question
516cf3eb 134 if ($quiznames = quizzes_question_used($id)) {
135 $a->questionname = $question->name;
136 $a->quiznames = implode(', ', $quiznames);
137 notify(get_string('questioninuse', 'quiz', $a));
138 }
139
140 notice_yesno(get_string("deletequestioncheck", "quiz", $question->name),
141 "question.php?sesskey=$USER->sesskey&amp;id=$question->id&amp;delete=$delete&amp;confirm=".md5($delete), "edit.php");
142 }
143 print_footer($course);
144 exit;
145 }
146
147 if ($form = data_submitted() and confirm_sesskey()) {
148
149 if (isset($form->versioning) && isset($question->id) and false) { // disable versioning until it is fixed.
150 // use new code that handles whether to overwrite or copy a question
151 // and keeps track of the versions in the quiz_question_version table
152
153 // $replaceinquiz is an array with the ids of all quizzes in which
154 // the teacher has chosen to replace the old version
155 $replaceinquiz = array();
156 foreach($form as $key => $val) {
157 if ($tmp = quiz_parse_fieldname($key, 'q')) {
158 if ($tmp['mode'] == 'replace') {
159 $replaceinquiz[$tmp['id']] = $tmp['id'];
160 unset($form->$key);
161 }
162 }
163 }
164
165 // $quizlist is an array with the ids of quizzes which use this question
166 $quizlist = array();
167 if ($instances = get_records('quiz_question_instances', 'question', $question->id)) {
168 foreach($instances as $instance) {
169 $quizlist[$instance->quiz] = $instance->quiz;
170 }
171 }
172
173 if (isset($form->makecopy)) { // explicitly requested copies should be unhidden
174 $question->hidden = 0;
175 }
176
177 // Logic to determine whether old version should be overwritten
178 $makecopy = isset($form->makecopy) || (!$form->id); unset($form->makecopy);
179 if ($makecopy) {
180 $replaceold = false;
181 } else {
182 // this should be improved to exclude teacher preview responses and empty responses
183 // the current code leaves many unneeded questions in the database
184 $hasresponses = record_exists('quiz_states', 'question', $form->id) or
185 record_exists('quiz_states', 'originalquestion', $form->id);
186 $replaceinall = ($quizlist == $replaceinquiz); // question is being replaced in all quizzes
187 $replaceold = !$hasresponses && $replaceinall;
188 }
189
190 if (!$replaceold) { // create a new question
191 $oldquestionid = $question->id;
192 if (!$makecopy) {
193 if (!set_field("quiz_questions", 'hidden', 1, 'id', $question->id)) {
194 error("Could not hide question!");
195 }
196 }
197 unset($question->id);
198 }
199 unset($makecopy, $hasresponses, $replaceinall, $replaceold);
200 $question = $QUIZ_QTYPES[$qtype]->save_question($question, $form, $course);
201 if(!isset($question->id)) {
202 error("Failed to save the question!");
203 }
204
205 if(isset($oldquestionid)) {
206 // create version entries for different quizzes
207 $version = new object();
208 $version->oldquestion = $oldquestionid;
209 $version->newquestion = $question->id;
210 $version->userid = $USER->id;
211 $version->timestamp = time();
212
213 foreach($replaceinquiz as $qid) {
214 $version->quiz = $qid;
215 if(!insert_record("quiz_question_versions", $version)) {
216 error("Could not store version information of question $oldquestionid in quiz $qid!");
217 }
218 }
219
220 /// now update the question references in the quizzes
221 if (!empty($replaceinquiz) and $quizzes = get_records_list("quiz", "id", implode(',', $replaceinquiz))) {
222
223 foreach($quizzes as $quiz) {
224 $questionlist = ",$quiz->questions,"; // a little hack with the commas here. not nice but effective
225 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
226 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
227 if (!set_field("quiz", 'questions', $questionlist, 'id', $quiz->id)) {
228 error("Could not update questionlist in quiz $quiz->id!");
229 }
230
231 // the quiz_question_instances table needs to be updated too (aah, the joys of duplication :)
232 if (!set_field('quiz_question_instances', 'question', $question->id, 'quiz', $quiz->id, 'question', $oldquestionid)) {
233 error("Could not update question instance!");
234 }
235 if (isset($SESSION->modform) && (int)$SESSION->modform->instance === (int)$quiz->id) {
236 $SESSION->modform->questions = $questionlist;
237 $SESSION->modform->grades[$question->id] = $SESSION->modform->grades[$oldquestionid];
238 unset($SESSION->modform->grades[$oldquestionid]);
239 }
240 }
241
242 // change question in attempts
243 if ($attempts = get_records_list('quiz_attempts', 'quiz', implode(',', $replaceinquiz))) {
244 foreach ($attempts as $attempt) {
245
246 // replace question id in $attempt->layout
247 $questionlist = ",$attempt->layout,"; // a little hack with the commas here. not nice but effective
248 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
249 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
250 if (!set_field('quiz_attempts', 'layout', $questionlist, 'id', $attempt->id)) {
251 error("Could not update layout in attempt $attempt->id!");
252 }
253
254 // set originalquestion in states
255 set_field('quiz_states', 'originalquestion', $oldquestionid, 'attempt', $attempt->uniqueid, 'question', $question->id, 'originalquestion', '0');
256
257 // replace question id in states
258 set_field('quiz_states', 'question', $question->id, 'attempt', $attempt->uniqueid, 'question', $oldquestionid);
259
260 // replace question id in sessions
261 set_field('question_sessions', 'questionid', $question->id, 'attemptid', $attempt->uniqueid, 'questionid', $oldquestionid);
262
263 }
264
265 // Now do anything question-type specific that is required to replace the question
266 // For example questions that use the quiz_answers table to hold part of their question will
267 // have to recode the answer ids in the states
268 $QUIZ_QTYPES[$question->qtype]->change_states_question($oldquestionid, $question, $attempts);
269 }
270 }
271 }
272 } else {
273 // use the old code which simply overwrites old versions
274 // it is also used for creating new questions
275 $question = $QUIZ_QTYPES[$qtype]->save_question($question, $form, $course);
276 $replaceinquiz = 'all';
277 }
278
279 if (empty($question->errors) && $QUIZ_QTYPES[$qtype]->finished_edit_wizard($form)) {
280 // DISABLED AUTOMATIC REGRADING
281 // Automagically regrade all attempts (and states) in the affected quizzes
282 //if (!empty($replaceinquiz)) {
283 // $QUIZ_QTYPES[$question->qtype]->get_question_options($question);
284 // quiz_regrade_question_in_quizzes($question, $replaceinquiz);
285 //}
e586cfb4 286 redirect($editurl);
516cf3eb 287 }
288 }
289
e586cfb4 290 // prepare the grades selector drop-down used by many question types
516cf3eb 291 $grades = array(1,0.9,0.8,0.75,0.70,0.66666,0.60,0.50,0.40,0.33333,0.30,0.25,0.20,0.16666,0.142857,0.125,0.11111,0.10,0.05,0);
292 foreach ($grades as $grade) {
293 $percentage = 100 * $grade;
294 $neggrade = -$grade;
295 $gradeoptions["$grade"] = "$percentage %";
296 $gradeoptionsfull["$grade"] = "$percentage %";
297 $gradeoptionsfull["$neggrade"] = -$percentage." %";
298 }
299 $gradeoptionsfull["0"] = $gradeoptions["0"] = get_string("none");
300
301 arsort($gradeoptions, SORT_NUMERIC);
302 arsort($gradeoptionsfull, SORT_NUMERIC);
303
304 if (!$categories = quiz_get_category_menu($course->id, false)) {
305 error("No categories!");
306 }
307
308
309 make_upload_directory("$course->id"); // Just in case
310 $coursefiles = get_directory_list("$CFG->dataroot/$course->id", $CFG->moddata);
311 foreach ($coursefiles as $filename) {
312 if (mimeinfo("icon", $filename) == "image.gif") {
313 $images["$filename"] = $filename;
314 }
315 }
316
317 // Print the question editing form
318
319 if (empty($question->id)) {
320 $question->id = "";
321 }
322 if (empty($question->name)) {
323 $question->name = "";
324 }
325 if (empty($question->questiontext)) {
326 $question->questiontext = "";
327 }
328 if (empty($question->image)) {
329 $question->image = "";
330 }
331 if (!isset($question->penalty)) {
332 $question->penalty = 0.1;
333 }
334 if (!isset($question->defaultgrade)) {
335 $question->defaultgrade = 1;
336 }
337
338 // Set up some Richtext editing if necessary
339 if ($usehtmleditor = can_use_richtext_editor()) {
340 $defaultformat = FORMAT_HTML;
341 } else {
342 $defaultformat = FORMAT_MOODLE;
343 }
344
345 if (isset($question->errors)) {
346 $err = $question->errors;
347 }
348
349 echo '<br />';
350 print_simple_box_start('center');
351 require_once('questiontypes/'.$QUIZ_QTYPES[$qtype]->name().'/editquestion.php');
352 print_simple_box_end();
353
354 if ($usehtmleditor) {
355 use_html_editor('questiontext');
356 }
357
358 print_footer($course);
359
360?>