3 * Page for editing questions
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))
10 * TODO: currently this still treats the quiz as special
11 * TODO: question versioning is not currently enabled
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
21 require_once(dirname(__FILE__
) . '/../config.php');
22 require_once('editlib.php'); // NOTE - is this correct? This is just about editing screens?
23 require_once($CFG->libdir
. '/filelib.php');
25 $id = optional_param('id', 0, PARAM_INT
); // question id
27 $qtype = optional_param('qtype', '', PARAM_FILE
);
28 $category = optional_param('category', 0, PARAM_INT
);
29 $inpopup = optional_param('inpopup', 0, PARAM_BOOL
);
31 $CFG->pagepath
= 'question/type/'.$qtype;
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);
41 if (! $question = get_record("question", "id", $id)) {
42 error("This question doesn't exist");
44 if (!empty($category)) {
45 $question->category
= $category;
47 if (! $category = get_record("question_categories", "id", $question->category
)) {
48 error("This question doesn't belong to a valid category!");
50 if (! $course = get_record("course", "id", $category->course
)) {
51 error("This question category doesn't belong to a valid course!");
54 $qtype = $question->qtype
;
55 if (!isset($QTYPES[$qtype])) {
56 $qtype = 'missingtype';
59 } else if ($category) { // only for creating new questions
60 if (! $category = get_record("question_categories", "id", $category)) {
61 error("This wasn't a valid category!");
63 if (! $course = get_record("course", "id", $category->course
)) {
64 error("This category doesn't belong to a valid course!");
67 $question->category
= $category->id
;
68 $question->qtype
= $qtype;
71 error("Must specify question id or category");
74 if (!isset($SESSION->returnurl
)) {
75 $SESSION->returnurl
= 'edit.php?courseid='.$course->id
;
78 // TODO: generalise this so it works for any activity
79 $contextquiz = isset($SESSION->modform
->instance
) ?
$SESSION->modform
->instance
: 0;
81 if (isset($_REQUEST['cancel'])) {
82 redirect($SESSION->returnurl
);
86 error("No question type was specified!");
87 } else if (!isset($QTYPES[$qtype])) {
88 error("Could not find question type: '$qtype'");
91 if (!file_exists("type/$qtype/editquestion.php")) {
92 redirect(str_ireplace('question.php', 'question2.php', me()));
95 require_login($course->id
, false);
96 $coursecontext = get_context_instance(CONTEXT_COURSE
, $course->id
);
97 require_capability('moodle/question:manage', $coursecontext);
100 if ($form = data_submitted() and confirm_sesskey()) {
102 if (isset($form->versioning
) && isset($question->id
) and false) { // disable versioning until it is fixed.
103 // use new code that handles whether to overwrite or copy a question
104 // and keeps track of the versions in the quiz_question_version table
106 // $replaceinquiz is an array with the ids of all quizzes in which
107 // the teacher has chosen to replace the old version
108 $replaceinquiz = array();
109 foreach($form as $key => $val) {
110 if ($tmp = quiz_parse_fieldname($key, 'q')) {
111 if ($tmp['mode'] == 'replace') {
112 $replaceinquiz[$tmp['id']] = $tmp['id'];
118 // $quizlist is an array with the ids of quizzes which use this question
120 if ($instances = get_records('quiz_question_instances', 'question', $question->id
)) {
121 foreach($instances as $instance) {
122 $quizlist[$instance->quiz
] = $instance->quiz
;
126 if (isset($form->makecopy
)) { // explicitly requested copies should be unhidden
127 $question->hidden
= 0;
130 // Logic to determine whether old version should be overwritten
131 $makecopy = isset($form->makecopy
) ||
(!$form->id
); unset($form->makecopy
);
135 // this should be improved to exclude teacher preview responses and empty responses
136 // the current code leaves many unneeded questions in the database
137 $hasresponses = record_exists('question_states', 'question', $form->id
) or
138 record_exists('question_states', 'originalquestion', $form->id
);
139 $replaceinall = ($quizlist == $replaceinquiz); // question is being replaced in all quizzes
140 $replaceold = !$hasresponses && $replaceinall;
143 $oldquestionid = false;
144 if (!$replaceold) { // create a new question
145 $oldquestionid = $question->id
;
147 if (!set_field("question", 'hidden', 1, 'id', $question->id
)) {
148 error("Could not hide question!");
151 unset($question->id
);
153 unset($makecopy, $hasresponses, $replaceinall, $replaceold);
154 $question = $QTYPES[$qtype]->save_question($question, $form, $course);
155 if(!isset($question->id
)) {
156 error("Failed to save the question!");
159 if(!empty($oldquestionid)) {
160 // create version entries for different quizzes
161 $version = new object();
162 $version->oldquestion
= $oldquestionid;
163 $version->newquestion
= $question->id
;
164 $version->userid
= $USER->id
;
165 $version->timestamp
= time();
167 foreach($replaceinquiz as $qid) {
168 $version->quiz
= $qid;
169 if(!insert_record("quiz_question_versions", $version)) {
170 error("Could not store version information of question $oldquestionid in quiz $qid!");
174 /// now update the question references in the quizzes
175 if (!empty($replaceinquiz) and $quizzes = get_records_list("quiz", "id", implode(',', $replaceinquiz))) {
177 foreach($quizzes as $quiz) {
178 $questionlist = ",$quiz->questions,"; // a little hack with the commas here. not nice but effective
179 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
180 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
181 if (!set_field("quiz", 'questions', $questionlist, 'id', $quiz->id
)) {
182 error("Could not update questionlist in quiz $quiz->id!");
185 // the quiz_question_instances table needs to be updated too (aah, the joys of duplication :)
186 if (!set_field('quiz_question_instances', 'question', $question->id
, 'quiz', $quiz->id
, 'question', $oldquestionid)) {
187 error("Could not update question instance!");
189 if (isset($SESSION->modform
) && (int)$SESSION->modform
->instance
=== (int)$quiz->id
) {
190 $SESSION->modform
->questions
= $questionlist;
191 $SESSION->modform
->grades
[$question->id
] = $SESSION->modform
->grades
[$oldquestionid];
192 unset($SESSION->modform
->grades
[$oldquestionid]);
196 // change question in attempts
197 if ($attempts = get_records_list('quiz_attempts', 'quiz', implode(',', $replaceinquiz))) {
198 foreach ($attempts as $attempt) {
200 // replace question id in $attempt->layout
201 $questionlist = ",$attempt->layout,"; // a little hack with the commas here. not nice but effective
202 $questionlist = str_replace(",$oldquestionid,", ",$question->id,", $questionlist);
203 $questionlist = substr($questionlist, 1, -1); // and get rid of the surrounding commas again
204 if (!set_field('quiz_attempts', 'layout', $questionlist, 'id', $attempt->id
)) {
205 error("Could not update layout in attempt $attempt->id!");
208 // set originalquestion in states
209 set_field('question_states', 'originalquestion', $oldquestionid, 'attempt', $attempt->uniqueid
, 'question', $question->id
, 'originalquestion', '0');
211 // replace question id in states
212 set_field('question_states', 'question', $question->id
, 'attempt', $attempt->uniqueid
, 'question', $oldquestionid);
214 // replace question id in sessions
215 set_field('question_sessions', 'questionid', $question->id
, 'attemptid', $attempt->uniqueid
, 'questionid', $oldquestionid);
219 // Now do anything question-type specific that is required to replace the question
220 // For example questions that use the question_answers table to hold part of their question will
221 // have to recode the answer ids in the states
222 $QTYPES[$question->qtype
]->change_states_question($oldquestionid, $question, $attempts);
227 // use the old code which simply overwrites old versions
228 // it is also used for creating new questions
230 if (isset($form->makecopy
)) {
231 $question->hidden
= 0; // explicitly requested copies should be unhidden
232 $question->id
= 0; // This will prompt save_question to create a new question
234 $question = $QTYPES[$qtype]->save_question($question, $form, $course);
235 $replaceinquiz = 'all';
238 if (empty($question->errors
) && $QTYPES[$qtype]->finished_edit_wizard($form)) {
239 // DISABLED AUTOMATIC REGRADING
240 // Automagically regrade all attempts (and states) in the affected quizzes
241 //if (!empty($replaceinquiz)) {
242 // $QTYPES[$question->qtype]->get_question_options($question);
243 // quiz_regrade_question_in_quizzes($question, $replaceinquiz);
246 $strsaved = get_string('changessaved');
248 notify($strsaved, '');
252 redirect($SESSION->returnurl
);
256 // TODO: remove restriction to quiz
257 $streditingquestion = get_string('editingquestion', 'quiz');
258 if (isset($SESSION->modform
->instance
)) {
259 $strediting = '<a href="'.$SESSION->returnurl
.'">'.get_string('editingquiz', 'quiz').'</a> -> '.
262 $strediting = '<a href="edit.php?courseid='.$course->id
.'">'.
263 get_string("editquestions", "quiz").'</a> -> '.$streditingquestion;
266 print_header_simple($streditingquestion, '', $strediting);
268 // prepare the grades selector drop-down used by many question types
269 $creategrades = get_grade_options();
270 $gradeoptions = $creategrades->gradeoptions
;
271 $gradeoptionsfull = $creategrades->gradeoptionsfull
;
273 // Initialise defaults if necessary.
274 if (empty($question->id
)) {
277 if (empty($question->name
)) {
278 $question->name
= "";
280 if (empty($question->questiontext
)) {
281 $question->questiontext
= "";
283 if (empty($question->image
)) {
284 $question->image
= "";
286 if (!isset($question->penalty
)) {
287 $question->penalty
= 0.1;
289 if (!isset($question->defaultgrade
)) {
290 $question->defaultgrade
= 1;
292 if (empty($question->generalfeedback
)) {
293 $question->generalfeedback
= "";
296 // Set up some richtext editing if necessary
297 if ($usehtmleditor = can_use_richtext_editor()) {
298 $defaultformat = FORMAT_HTML
;
300 $defaultformat = FORMAT_MOODLE
;
303 if (isset($question->errors
)) {
304 $err = $question->errors
;
307 // Print the question editing form
309 print_simple_box_start('center');
310 require_once('type/'.$qtype.'/editquestion.php');
311 print_simple_box_end();
313 if ($usehtmleditor) {
314 use_html_editor('questiontext');
317 print_footer($course);