Merge branch 'wip-mdl-30121-m22' of git://github.com/rajeshtaneja/moodle into MOODLE_...
[moodle.git] / mod / quiz / edit.php
blobcb7b7f5403c7bbe8f099b01802dc906a7d9beed5
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * Page to edit quizzes
21 * This page generally has two columns:
22 * The right column lists all available questions in a chosen category and
23 * allows them to be edited or more to be added. This column is only there if
24 * the quiz does not already have student attempts
25 * The left column lists all questions that have been added to the current quiz.
26 * The lecturer can add questions from the right hand list to the quiz or remove them
28 * The script also processes a number of actions:
29 * Actions affecting a quiz:
30 * up and down Changes the order of questions and page breaks
31 * addquestion Adds a single question to the quiz
32 * add Adds several selected questions to the quiz
33 * addrandom Adds a certain number of random questions to the quiz
34 * repaginate Re-paginates the quiz
35 * delete Removes a question from the quiz
36 * savechanges Saves the order and grades for questions in the quiz
38 * @package mod
39 * @subpackage quiz
40 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45 require_once('../../config.php');
46 require_once($CFG->dirroot . '/mod/quiz/editlib.php');
47 require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
48 require_once($CFG->dirroot . '/question/category_class.php');
51 /**
52 * Callback function called from question_list() function
53 * (which is called from showbank())
54 * Displays button in form with checkboxes for each question.
56 function module_specific_buttons($cmid, $cmoptions) {
57 global $OUTPUT;
58 $params = array(
59 'type' => 'submit',
60 'name' => 'add',
61 'value' => $OUTPUT->larrow() . ' ' . get_string('addtoquiz', 'quiz'),
63 if ($cmoptions->hasattempts) {
64 $params['disabled'] = 'disabled';
66 return html_writer::empty_tag('input', $params);
69 /**
70 * Callback function called from question_list() function
71 * (which is called from showbank())
73 function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmoptions) {
74 global $OUTPUT;
75 $out = '';
76 $catcontext = get_context_instance_by_id($category->contextid);
77 if (has_capability('moodle/question:useall', $catcontext)) {
78 if ($cmoptions->hasattempts) {
79 $disabled = ' disabled="disabled"';
80 } else {
81 $disabled = '';
83 $randomusablequestions =
84 question_bank::get_qtype('random')->get_available_questions_from_category(
85 $category->id, $recurse);
86 $maxrand = count($randomusablequestions);
87 if ($maxrand > 0) {
88 for ($i = 1; $i <= min(10, $maxrand); $i++) {
89 $randomcount[$i] = $i;
91 for ($i = 20; $i <= min(100, $maxrand); $i += 10) {
92 $randomcount[$i] = $i;
94 } else {
95 $randomcount[0] = 0;
96 $disabled = ' disabled="disabled"';
99 $out = '<strong><label for="menurandomcount">'.get_string('addrandomfromcategory', 'quiz').
100 '</label></strong><br />';
101 $attributes = array();
102 $attributes['disabled'] = $disabled ? 'disabled' : null;
103 $select = html_writer::select($randomcount, 'randomcount', '1', null, $attributes);
104 $out .= get_string('addrandom', 'quiz', $select);
105 $out .= '<input type="hidden" name="recurse" value="'.$recurse.'" />';
106 $out .= '<input type="hidden" name="categoryid" value="' . $category->id . '" />';
107 $out .= ' <input type="submit" name="addrandom" value="'.
108 get_string('addtoquiz', 'quiz').'"' . $disabled . ' />';
109 $out .= $OUTPUT->help_icon('addarandomquestion', 'quiz');
111 return $out;
114 //these params are only passed from page request to request while we stay on
115 //this page otherwise they would go in question_edit_setup
116 $quiz_reordertool = optional_param('reordertool', -1, PARAM_BOOL);
117 $quiz_qbanktool = optional_param('qbanktool', -1, PARAM_BOOL);
118 $scrollpos = optional_param('scrollpos', '', PARAM_INT);
120 list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
121 question_edit_setup('editq', '/mod/quiz/edit.php', true);
122 $quiz->questions = quiz_clean_layout($quiz->questions);
124 $defaultcategoryobj = question_make_default_categories($contexts->all());
125 $defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
127 if ($quiz_qbanktool > -1) {
128 $thispageurl->param('qbanktool', $quiz_qbanktool);
129 set_user_preference('quiz_qbanktool_open', $quiz_qbanktool);
130 } else {
131 $quiz_qbanktool = get_user_preferences('quiz_qbanktool_open', 0);
134 if ($quiz_reordertool > -1) {
135 $thispageurl->param('reordertool', $quiz_reordertool);
136 set_user_preference('quiz_reordertab', $quiz_reordertool);
137 } else {
138 $quiz_reordertool = get_user_preferences('quiz_reordertab', 0);
141 $canaddrandom = $contexts->have_cap('moodle/question:useall');
142 $canaddquestion = (bool) $contexts->having_add_and_use();
144 $quizhasattempts = quiz_has_attempts($quiz->id);
146 $PAGE->set_url($thispageurl);
148 $pagetitle = get_string('editingquiz', 'quiz');
149 if ($quiz_reordertool) {
150 $pagetitle = get_string('orderingquiz', 'quiz');
152 // Get the course object and related bits.
153 $course = $DB->get_record('course', array('id' => $quiz->course));
154 if (!$course) {
155 print_error('invalidcourseid', 'error');
158 $questionbank = new quiz_question_bank_view($contexts, $thispageurl, $course, $cm, $quiz);
159 $questionbank->set_quiz_has_attempts($quizhasattempts);
161 // Log this visit.
162 add_to_log($cm->course, 'quiz', 'editquestions',
163 "view.php?id=$cm->id", "$quiz->id", $cm->id);
165 // You need mod/quiz:manage in addition to question capabilities to access this page.
166 require_capability('mod/quiz:manage', $contexts->lowest());
168 if (empty($quiz->grades)) {
169 $quiz->grades = quiz_get_all_question_grades($quiz);
172 // Process commands ============================================================
173 if ($quiz->shufflequestions) {
174 // Strip page breaks before processing actions, so that re-ordering works
175 // as expected when shuffle questions is on.
176 $quiz->questions = quiz_repaginate($quiz->questions, 0);
179 // Get the list of question ids had their check-boxes ticked.
180 $selectedquestionids = array();
181 $params = (array) data_submitted();
182 foreach ($params as $key => $value) {
183 if (preg_match('!^s([0-9]+)$!', $key, $matches)) {
184 $selectedquestionids[] = $matches[1];
188 $afteractionurl = new moodle_url($thispageurl);
189 if ($scrollpos) {
190 $afteractionurl->param('scrollpos', $scrollpos);
192 if (($up = optional_param('up', false, PARAM_INT)) && confirm_sesskey()) {
193 $quiz->questions = quiz_move_question_up($quiz->questions, $up);
194 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
195 quiz_delete_previews($quiz);
196 redirect($afteractionurl);
199 if (($down = optional_param('down', false, PARAM_INT)) && confirm_sesskey()) {
200 $quiz->questions = quiz_move_question_down($quiz->questions, $down);
201 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
202 quiz_delete_previews($quiz);
203 redirect($afteractionurl);
206 if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
207 // Re-paginate the quiz
208 $questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage, PARAM_INT);
209 $quiz->questions = quiz_repaginate($quiz->questions, $questionsperpage );
210 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
211 quiz_delete_previews($quiz);
212 redirect($afteractionurl);
215 if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
216 // Add a single question to the current quiz
217 quiz_require_question_use($addquestion);
218 $addonpage = optional_param('addonpage', 0, PARAM_INT);
219 quiz_add_quiz_question($addquestion, $quiz, $addonpage);
220 quiz_delete_previews($quiz);
221 quiz_update_sumgrades($quiz);
222 $thispageurl->param('lastchanged', $addquestion);
223 redirect($afteractionurl);
226 if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
227 // Add selected questions to the current quiz
228 $rawdata = (array) data_submitted();
229 foreach ($rawdata as $key => $value) { // Parse input for question ids
230 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
231 $key = $matches[1];
232 quiz_require_question_use($key);
233 quiz_add_quiz_question($key, $quiz);
236 quiz_delete_previews($quiz);
237 quiz_update_sumgrades($quiz);
238 redirect($afteractionurl);
241 if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
242 // Add random questions to the quiz
243 $recurse = optional_param('recurse', 0, PARAM_BOOL);
244 $addonpage = optional_param('addonpage', 0, PARAM_INT);
245 $categoryid = required_param('categoryid', PARAM_INT);
246 $randomcount = required_param('randomcount', PARAM_INT);
247 quiz_add_random_questions($quiz, $addonpage, $categoryid, $randomcount, $recurse);
249 quiz_delete_previews($quiz);
250 quiz_update_sumgrades($quiz);
251 redirect($afteractionurl);
254 if (optional_param('addnewpagesafterselected', null, PARAM_CLEAN) &&
255 !empty($selectedquestionids) && confirm_sesskey()) {
256 foreach ($selectedquestionids as $questionid) {
257 $quiz->questions = quiz_add_page_break_after($quiz->questions, $questionid);
259 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
260 quiz_delete_previews($quiz);
261 redirect($afteractionurl);
264 $addpage = optional_param('addpage', false, PARAM_INT);
265 if ($addpage !== false && confirm_sesskey()) {
266 $quiz->questions = quiz_add_page_break_at($quiz->questions, $addpage);
267 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
268 quiz_delete_previews($quiz);
269 redirect($afteractionurl);
272 $deleteemptypage = optional_param('deleteemptypage', false, PARAM_INT);
273 if (($deleteemptypage !== false) && confirm_sesskey()) {
274 $quiz->questions = quiz_delete_empty_page($quiz->questions, $deleteemptypage);
275 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
276 quiz_delete_previews($quiz);
277 redirect($afteractionurl);
280 $remove = optional_param('remove', false, PARAM_INT);
281 if ($remove && confirm_sesskey()) {
282 // Remove a question from the quiz.
283 // We require the user to have the 'use' capability on the question,
284 // so that then can add it back if they remove the wrong one by mistake.
285 quiz_require_question_use($remove);
286 quiz_remove_question($quiz, $remove);
287 quiz_delete_previews($quiz);
288 quiz_update_sumgrades($quiz);
289 redirect($afteractionurl);
292 if (optional_param('quizdeleteselected', false, PARAM_BOOL) &&
293 !empty($selectedquestionids) && confirm_sesskey()) {
294 foreach ($selectedquestionids as $questionid) {
295 if (quiz_has_question_use($questionid)) {
296 quiz_remove_question($quiz, $questionid);
299 quiz_delete_previews($quiz);
300 quiz_update_sumgrades($quiz);
301 redirect($afteractionurl);
304 if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
305 $deletepreviews = false;
306 $recomputesummarks = false;
308 $oldquestions = explode(',', $quiz->questions); // the questions in the old order
309 $questions = array(); // for questions in the new order
310 $rawdata = (array) data_submitted();
311 $moveonpagequestions = array();
312 $moveselectedonpage = optional_param('moveselectedonpagetop', 0, PARAM_INT);
313 if (!$moveselectedonpage) {
314 $moveselectedonpage = optional_param('moveselectedonpagebottom', 0, PARAM_INT);
317 foreach ($rawdata as $key => $value) {
318 if (preg_match('!^g([0-9]+)$!', $key, $matches)) {
319 // Parse input for question -> grades
320 $questionid = $matches[1];
321 $quiz->grades[$questionid] = unformat_float($value);
322 quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz);
323 $deletepreviews = true;
324 $recomputesummarks = true;
326 } else if (preg_match('!^o(pg)?([0-9]+)$!', $key, $matches)) {
327 // Parse input for ordering info
328 $questionid = $matches[2];
329 // Make sure two questions don't overwrite each other. If we get a second
330 // question with the same position, shift the second one along to the next gap.
331 $value = clean_param($value, PARAM_INTEGER);
332 while (array_key_exists($value, $questions)) {
333 $value++;
335 if ($matches[1]) {
336 // This is a page-break entry.
337 $questions[$value] = 0;
338 } else {
339 $questions[$value] = $questionid;
341 $deletepreviews = true;
345 // If ordering info was given, reorder the questions
346 if ($questions) {
347 ksort($questions);
348 $questions[] = 0;
349 $quiz->questions = implode(',', $questions);
350 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
351 $deletepreviews = true;
354 //get a list of questions to move, later to be added in the appropriate
355 //place in the string
356 if ($moveselectedonpage) {
357 $questions = explode(',', $quiz->questions);
358 $newquestions = array();
359 //remove the questions from their original positions first
360 foreach ($questions as $questionid) {
361 if (!in_array($questionid, $selectedquestionids)) {
362 $newquestions[] = $questionid;
365 $questions = $newquestions;
367 //move to the end of the selected page
368 $pagebreakpositions = array_keys($questions, 0);
369 $numpages = count($pagebreakpositions);
371 // Ensure the target page number is in range.
372 for ($i = $moveselectedonpage; $i > $numpages; $i--) {
373 $questions[] = 0;
374 $pagebreakpositions[] = count($questions) - 1;
376 $moveselectedpos = $pagebreakpositions[$moveselectedonpage - 1];
378 // Do the move.
379 array_splice($questions, $moveselectedpos, 0, $selectedquestionids);
380 $quiz->questions = implode(',', $questions);
382 // Update the database.
383 $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
384 $deletepreviews = true;
387 // If rescaling is required save the new maximum.
388 $maxgrade = unformat_float(optional_param('maxgrade', -1, PARAM_RAW));
389 if ($maxgrade >= 0) {
390 quiz_set_grade($maxgrade, $quiz);
393 if ($deletepreviews) {
394 quiz_delete_previews($quiz);
396 if ($recomputesummarks) {
397 quiz_update_sumgrades($quiz);
398 quiz_update_all_attempt_sumgrades($quiz);
399 quiz_update_all_final_grades($quiz);
400 quiz_update_grades($quiz, 0, true);
402 redirect($afteractionurl);
405 $questionbank->process_actions($thispageurl, $cm);
407 // End of process commands =====================================================
409 $PAGE->requires->yui2_lib('container');
410 $PAGE->requires->yui2_lib('dragdrop');
411 $PAGE->requires->skip_link_to('questionbank',
412 get_string('skipto', 'access', get_string('questionbank', 'question')));
413 $PAGE->requires->skip_link_to('quizcontentsblock',
414 get_string('skipto', 'access', get_string('questionsinthisquiz', 'quiz')));
415 $PAGE->set_title($pagetitle);
416 $PAGE->set_heading($course->fullname);
417 $node = $PAGE->settingsnav->find('mod_quiz_edit', navigation_node::TYPE_SETTING);
418 if ($node) {
419 $node->make_active();
421 echo $OUTPUT->header();
423 // Initialise the JavaScript.
424 $quizeditconfig = new stdClass();
425 $quizeditconfig->url = $thispageurl->out(true, array('qbanktool' => '0'));
426 $quizeditconfig->dialoglisteners = array();
427 $numberoflisteners = max(quiz_number_of_pages($quiz->questions), 1);
428 for ($pageiter = 1; $pageiter <= $numberoflisteners; $pageiter++) {
429 $quizeditconfig->dialoglisteners[] = 'addrandomdialoglaunch_' . $pageiter;
431 $PAGE->requires->data_for_js('quiz_edit_config', $quizeditconfig);
432 $PAGE->requires->js('/question/qengine.js');
433 $PAGE->requires->js('/mod/quiz/edit.js');
434 $PAGE->requires->js_init_call('quiz_edit_init');
436 // Print the tabs to switch mode.
437 if ($quiz_reordertool) {
438 $currenttab = 'reorder';
439 } else {
440 $currenttab = 'edit';
442 $tabs = array(array(
443 new tabobject('edit', new moodle_url($thispageurl,
444 array('reordertool' => 0)), get_string('editingquiz', 'quiz')),
445 new tabobject('reorder', new moodle_url($thispageurl,
446 array('reordertool' => 1)), get_string('orderingquiz', 'quiz')),
448 print_tabs($tabs, $currenttab);
450 if ($quiz_qbanktool) {
451 $bankclass = '';
452 $quizcontentsclass = '';
453 } else {
454 $bankclass = 'collapsed ';
455 $quizcontentsclass = 'quizwhenbankcollapsed';
458 echo '<div class="questionbankwindow ' . $bankclass . 'block">';
459 echo '<div class="header"><div class="title"><h2>';
460 echo get_string('questionbankcontents', 'quiz') .
461 ' <a href="' . $thispageurl->out(true, array('qbanktool' => '1')) .
462 '" id="showbankcmd">[' . get_string('show').
463 ']</a>
464 <a href="' . $thispageurl->out(true, array('qbanktool' => '0')) .
465 '" id="hidebankcmd">[' . get_string('hide').
466 ']</a>';
467 echo '</h2></div></div><div class="content">';
469 echo '<span id="questionbank"></span>';
470 echo '<div class="container">';
471 echo '<div id="module" class="module">';
472 echo '<div class="bd">';
473 $questionbank->display('editq',
474 $pagevars['qpage'],
475 $pagevars['qperpage'],
476 $pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'],
477 $pagevars['qbshowtext']);
478 echo '</div>';
479 echo '</div>';
480 echo '</div>';
482 echo '</div></div>';
484 echo '<div class="quizcontents ' . $quizcontentsclass . '" id="quizcontentsblock">';
485 if ($quiz->shufflequestions) {
486 $repaginatingdisabledhtml = 'disabled="disabled"';
487 $repaginatingdisabled = true;
488 $quiz->questions = quiz_repaginate($quiz->questions, $quiz->questionsperpage);
489 } else {
490 $repaginatingdisabledhtml = '';
491 $repaginatingdisabled = false;
493 if ($quiz_reordertool) {
494 echo '<div class="repaginatecommand"><button id="repaginatecommand" ' .
495 $repaginatingdisabledhtml.'>'.
496 get_string('repaginatecommand', 'quiz').'...</button>';
497 echo '</div>';
500 if ($quiz_reordertool) {
501 echo $OUTPUT->heading_with_help(get_string('orderingquiz', 'quiz') . ': ' . $quiz->name,
502 'orderandpaging', 'quiz');
503 } else {
504 echo $OUTPUT->heading(get_string('editingquiz', 'quiz') . ': ' . $quiz->name, 2);
505 echo $OUTPUT->help_icon('editingquiz', 'quiz', get_string('basicideasofquiz', 'quiz'));
507 quiz_print_status_bar($quiz);
509 $tabindex = 0;
510 quiz_print_grading_form($quiz, $thispageurl, $tabindex);
512 $notifystrings = array();
513 if ($quizhasattempts) {
514 $reviewlink = quiz_attempt_summary_link_to_reports($quiz, $cm, $contexts->lowest());
515 $notifystrings[] = get_string('cannoteditafterattempts', 'quiz', $reviewlink);
517 if ($quiz->shufflequestions) {
518 $updateurl = new moodle_url("$CFG->wwwroot/course/mod.php",
519 array('return' => 'true', 'update' => $quiz->cmid, 'sesskey' => sesskey()));
520 $updatelink = '<a href="'.$updateurl->out().'">' . get_string('updatethis', '',
521 get_string('modulename', 'quiz')) . '</a>';
522 $notifystrings[] = get_string('shufflequestionsselected', 'quiz', $updatelink);
524 if (!empty($notifystrings)) {
525 echo $OUTPUT->box('<p>' . implode('</p><p>', $notifystrings) . '</p>', 'statusdisplay');
528 if ($quiz_reordertool) {
529 $perpage = array();
530 $perpage[0] = get_string('allinone', 'quiz');
531 for ($i = 1; $i <= 50; ++$i) {
532 $perpage[$i] = $i;
534 $gostring = get_string('go');
535 echo '<div id="repaginatedialog"><div class="hd">';
536 echo get_string('repaginatecommand', 'quiz');
537 echo '</div><div class="bd">';
538 echo '<form action="edit.php" method="post">';
539 echo '<fieldset class="invisiblefieldset">';
540 echo html_writer::input_hidden_params($thispageurl);
541 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
542 //YUI does not submit the value of the submit button so
543 //we need to add the value:
544 echo '<input type="hidden" name="repaginate" value="'.$gostring.'" />';
545 $attributes = array();
546 $attributes['disabled'] = $repaginatingdisabledhtml ? 'disabled' : null;
547 $select = html_writer::select(
548 $perpage, 'questionsperpage', $quiz->questionsperpage, null, $attributes);
549 print_string('repaginate', 'quiz', $select);
550 echo '<div class="quizquestionlistcontrols">';
551 echo ' <input type="submit" name="repaginate" value="'. $gostring . '" ' .
552 $repaginatingdisabledhtml.' />';
553 echo '</div></fieldset></form></div></div>';
556 if ($quiz_reordertool) {
557 echo '<div class="reorder">';
558 } else {
559 echo '<div class="editq">';
562 quiz_print_question_list($quiz, $thispageurl, true, $quiz_reordertool, $quiz_qbanktool,
563 $quizhasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom);
564 echo '</div>';
566 // Close <div class="quizcontents">:
567 echo '</div>';
569 if (!$quiz_reordertool && $canaddrandom) {
570 $randomform = new quiz_add_random_form(new moodle_url('/mod/quiz/addrandom.php'), $contexts);
571 $randomform->set_data(array(
572 'category' => $pagevars['cat'],
573 'returnurl' => str_replace($CFG->wwwroot, '', $thispageurl->out(false)),
574 'cmid' => $cm->id,
577 <div id="randomquestiondialog">
578 <div class="hd"><?php print_string('addrandomquestiontoquiz', 'quiz', $quiz->name); ?>
579 <span id="pagenumber"><!-- JavaScript will insert the page number here. -->
580 </span>
581 </div>
582 <div class="bd"><?php
583 $randomform->display();
584 ?></div>
585 </div>
586 <?php
588 echo $OUTPUT->footer();