5 * This page generally has two columns:
6 * The right column lists all available questions in a chosen category and
7 * allows them to be edited or more to be added. This column is only there if
8 * the quiz does not already have student attempts
9 * The left column lists all questions that have been added to the current quiz.
10 * The lecturer can add questions from the right hand list to the quiz or remove them
12 * The script also processes a number of actions:
13 * Actions affecting a quiz:
14 * up and down Changes the order of questions and page breaks
15 * addquestion Adds a single question to the quiz
16 * add Adds several selected questions to the quiz
17 * addrandom Adds a certain number of random questions to the quiz
18 * repaginate Re-paginates the quiz
19 * delete Removes a question from the quiz
20 * savechanges Saves the order and grades for questions in the quiz
22 * @author Martin Dougiamas and many others. This has recently been extensively
23 * rewritten by Gustav Delius and other members of the Serving Mathematics project
24 * {@link http://maths.york.ac.uk/serving_maths}
25 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
28 require_once("../../config.php");
29 require_once($CFG->dirroot
.'/mod/quiz/editlib.php');
32 * Callback function called from question_list() function (which is called from showbank())
33 * Displays action icon as first action for each question.
35 function module_specific_actions($pageurl, $questionid, $cmid, $canuse){
38 // for RTL languages: switch right and left arrows /****/
39 if (right_to_left()) {
40 $movearrow = 'removeright.gif';
42 $movearrow = 'moveleft.gif';
44 $straddtoquiz = get_string("addtoquiz", "quiz");
45 $out = "<a title=\"$straddtoquiz\" href=\"edit.php?".$pageurl->get_query_string()."&addquestion=$questionid&sesskey=".sesskey()."\"><img
46 src=\"$CFG->pixpath/t/$movearrow\" alt=\"$straddtoquiz\" /></a> ";
53 * Callback function called from question_list() function (which is called from showbank())
54 * Displays button in form with checkboxes for each question.
56 function module_specific_buttons($cmid){
58 $straddtoquiz = get_string("addtoquiz", "quiz");
59 $out = "<input type=\"submit\" name=\"add\" value=\"{$THEME->larrow} $straddtoquiz\" />\n";
65 * Callback function called from question_list() function (which is called from showbank())
67 function module_specific_controls($totalnumber, $recurse, $category, $cmid){
68 $catcontext = get_context_instance_by_id($category->contextid
);
69 if (has_capability('moodle/question:useall', $catcontext)){
70 for ($i = 1;$i <= min(10, $totalnumber); $i++
) {
71 $randomcount[$i] = $i;
73 for ($i = 20;$i <= min(100, $totalnumber); $i +
= 10) {
74 $randomcount[$i] = $i;
77 $out .= get_string('addrandom', 'quiz', choose_from_menu($randomcount, 'randomcount', '1', '', '', '', true));
78 $out .= '<input type="hidden" name="recurse" value="'.$recurse.'" />';
79 $out .= "<input type=\"hidden\" name=\"categoryid\" value=\"$category->id\" />";
80 $out .= ' <input type="submit" name="addrandom" value="'. get_string('add') .'" />';
81 $out .= helpbutton('random', get_string('random', 'quiz'), 'quiz', true, false, '', true);
88 list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = question_edit_setup('editq', true);
90 //these params are only passed from page request to request while we stay on this page
91 //otherwise they would go in question_edit_setup
92 $quiz_showbreaks = optional_param('showbreaks', -1, PARAM_BOOL
);
93 $quiz_reordertool = optional_param('reordertool', 0, PARAM_BOOL
);
94 if ($quiz_showbreaks > -1) {
95 $thispageurl->param('showbreaks', $quiz_showbreaks);
97 $quiz_showbreaks = ($CFG->quiz_questionsperpage
< 2) ?
0 : 1;
99 if ($quiz_reordertool != 0) {
100 $thispageurl->param('reordertool', $quiz_reordertool);
103 $strquizzes = get_string('modulenameplural', 'quiz');
104 $strquiz = get_string('modulename', 'quiz');
105 $streditingquestions = get_string('editquestions', "quiz");
106 $streditingquiz = get_string('editinga', 'moodle', $strquiz);
108 // Get the course object and related bits.
109 if (! $course = get_record("course", "id", $quiz->course
)) {
110 error("This course doesn't exist");
114 add_to_log($cm->course
, 'quiz', 'editquestions',
115 "view.php?id=$cm->id", "$quiz->id", $cm->id
);
117 //you need mod/quiz:manage in addition to question capabilities to access this page.
118 require_capability('mod/quiz:manage', $contexts->lowest());
120 if (isset($quiz->instance
)
121 && empty($quiz->grades
)){ // Construct an array to hold all the grades.
122 $quiz->grades
= quiz_get_all_question_grades($quiz);
126 /// Now, check for commands on this page and modify variables as necessary
127 // If any edit action makes a sifnificant change to the structure of the quiz, then we
128 // will need to delete all preview attempts.
129 $significantchangemade = false;
131 if (($up = optional_param('up', false, PARAM_INT
)) !== false and confirm_sesskey()) { /// Move the given question up a slot
132 $questions = explode(",", $quiz->questions
);
133 if ($up > 0 and isset($questions[$up])) {
134 $prevkey = ($questions[$up-1] == 0) ?
$up-2 : $up-1;
135 $swap = $questions[$prevkey];
136 $questions[$prevkey] = $questions[$up];
137 $questions[$up] = $swap;
138 $quiz->questions
= implode(",", $questions);
139 // Always have a page break at the end
140 $quiz->questions
= $quiz->questions
. ',0';
141 // Avoid duplicate page breaks
142 $quiz->questions
= str_replace(',0,0', ',0', $quiz->questions
);
143 if (!set_field('quiz', 'questions', $quiz->questions
, 'id', $quiz->instance
)) {
144 error('Could not save question list');
146 $significantchangemade = true;
150 if (($down = optional_param('down', false, PARAM_INT
)) !== false and confirm_sesskey()) { /// Move the given question down a slot
151 $questions = explode(",", $quiz->questions
);
152 if ($down < count($questions)) {
153 $nextkey = ($questions[$down+
1] == 0) ?
$down+
2 : $down+
1;
154 $swap = $questions[$nextkey];
155 $questions[$nextkey] = $questions[$down];
156 $questions[$down] = $swap;
157 $quiz->questions
= implode(",", $questions);
158 // Avoid duplicate page breaks
159 $quiz->questions
= str_replace(',0,0', ',0', $quiz->questions
);
160 if (!set_field('quiz', 'questions', $quiz->questions
, 'id', $quiz->instance
)) {
161 error('Could not save question list');
163 $significantchangemade = true;
167 if (($addquestion = optional_param('addquestion', 0, PARAM_INT
)) and confirm_sesskey()) { /// Add a single question to the current quiz
168 quiz_add_quiz_question($addquestion, $quiz);
169 $significantchangemade = true;
172 if (optional_param('add', false, PARAM_BOOL
) and confirm_sesskey()) { /// Add selected questions to the current quiz
173 $rawdata = (array) data_submitted();
174 foreach ($rawdata as $key => $value) { // Parse input for question ids
175 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
177 quiz_add_quiz_question($key, $quiz);
180 $significantchangemade = true;
183 if (optional_param('addrandom', false, PARAM_BOOL
) and confirm_sesskey()) { /// Add random questions to the quiz
184 $recurse = optional_param('recurse', 0, PARAM_BOOL
);
185 $categoryid = required_param('categoryid', PARAM_INT
);
186 $randomcount = required_param('randomcount', PARAM_INT
);
188 if (! $category = get_record('question_categories', 'id', $categoryid)) {
189 error('Category ID is incorrect');
191 $catcontext = get_context_instance_by_id($category->contextid
);
192 require_capability('moodle/question:useall', $catcontext);
193 $category->name
= addslashes($category->name
);
194 // Find existing random questions in this category that are not used by any quiz.
195 if ($existingquestions = get_records_sql(
196 "SELECT * FROM " . $CFG->prefix
. "question q
197 WHERE qtype = '" . RANDOM
. "'
198 AND category = $category->id
199 AND " . sql_compare_text('questiontext') . " = '$recurse'
200 AND NOT EXISTS (SELECT * FROM " . $CFG->prefix
. "quiz_question_instances WHERE question = q.id)
202 // Take as many of these as needed.
203 while (($existingquestion = array_shift($existingquestions)) and $randomcount > 0) {
204 quiz_add_quiz_question($existingquestion->id
, $quiz);
209 // If more are needed, create them.
210 if ($randomcount > 0) {
211 $form->questiontext
= $recurse; // we use the questiontext field to store the info
212 // on whether to include questions in subcategories
213 $form->questiontextformat
= 0;
215 $form->defaultgrade
= 1;
217 for ($i = 0; $i < $randomcount; $i++
) {
218 $form->category
= "$category->id,$category->contextid";
219 $form->stamp
= make_unique_id_code(); // Set the unique code (not to be changed)
220 $question = new stdClass
;
221 $question->qtype
= RANDOM
;
222 $question = $QTYPES[RANDOM
]->save_question($question, $form, $course);
223 if(!isset($question->id
)) {
224 error('Could not insert new random question!');
226 quiz_add_quiz_question($question->id
, $quiz);
229 $significantchangemade = true;
232 if (optional_param('repaginate', false, PARAM_BOOL
) and confirm_sesskey()) { /// Re-paginate the quiz
233 $questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage
, PARAM_INT
);
234 if ($questionsperpage != $quiz->questionsperpage
) {
235 $quiz->questionsperpage
= $questionsperpage;
236 if (!set_field('quiz', 'questionsperpage', $quiz->questionsperpage
, 'id', $quiz->id
)) {
237 error('Could not save number of questions per page');
240 $quiz->questions
= quiz_repaginate($quiz->questions
, $quiz->questionsperpage
);
241 if (!set_field('quiz', 'questions', $quiz->questions
, 'id', $quiz->id
)) {
242 error('Could not save layout');
244 $significantchangemade = true;
246 if (($delete = optional_param('delete', false, PARAM_INT
)) !== false and confirm_sesskey()) { /// Remove a question from the quiz
247 quiz_delete_quiz_question($delete, $quiz);
248 $significantchangemade = true;
251 if (optional_param('savechanges', false, PARAM_BOOL
) and confirm_sesskey()) {
252 /// We need to save the new ordering (if given) and the new grades
253 $oldquestions = explode(",", $quiz->questions
); // the questions in the old order
254 $questions = array(); // for questions in the new order
255 $rawgrades = (array) data_submitted();
256 unset($quiz->grades
);
257 foreach ($rawgrades as $key => $value) {
258 /// Parse input for question -> grades
259 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
261 $quiz->grades
[$key] = $value;
262 quiz_update_question_instance($quiz->grades
[$key], $key, $quiz->instance
);
264 /// Parse input for ordering info
265 } elseif (preg_match('!^o([0-9]+)$!', $key, $matches)) {
267 // Make sure two questions don't overwrite each other. If we get a second
268 // question with the same position, shift the second one along to the next gap.
269 while (array_key_exists($value, $questions)) {
272 $questions[$value] = $oldquestions[$key];
276 // If ordering info was given, reorder the questions
279 // Make sure that the quiz does not start with a page break.
280 while (reset($questions) == '0') {
281 array_shift($questions);
283 $quiz->questions
= implode(",", $questions);
284 // Always have a page break at the end
285 $quiz->questions
= $quiz->questions
. ',0';
286 // Avoid duplicate page breaks
287 while (strpos($quiz->questions
, ',0,0')) {
288 $quiz->questions
= str_replace(',0,0', ',0', $quiz->questions
);
290 if (!set_field('quiz', 'questions', $quiz->questions
, 'id', $quiz->instance
)) {
291 error('Could not save question list');
295 // If rescaling is required save the new maximum
296 $maxgrade = optional_param('maxgrade', -1, PARAM_NUMBER
);
297 if ($maxgrade >= 0) {
298 if (!quiz_set_grade($maxgrade, $quiz)) {
299 error('Could not set a new maximum grade for the quiz');
302 $significantchangemade = true;
305 /// Delete any teacher preview attempts if the quiz has been modified
306 if ($significantchangemade) {
307 $previewattempts = get_records_select('quiz_attempts',
308 'quiz = ' . $quiz->id
. ' AND preview = 1');
309 if ($previewattempts) {
310 foreach ($previewattempts as $attempt) {
311 quiz_delete_attempt($attempt, $quiz);
316 question_showbank_actions($thispageurl, $cm);
318 /// all commands have been dealt with, now print the page
320 // Print basic page layout.
321 if (isset($quiz->instance
) and record_exists_select('quiz_attempts', "quiz = '$quiz->instance' AND preview = '0'")){
322 // one column layout with table of questions used in this quiz
323 $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest())
324 ?
update_module_button($cm->id
, $course->id
, get_string('modulename', 'quiz'))
326 $navigation = build_navigation($streditingquiz, $cm);
327 print_header_simple($streditingquiz, '', $navigation, "", "",
328 true, $strupdatemodule);
330 $currenttab = 'edit';
337 echo "<div class=\"quizattemptcounts\">\n";
338 echo '<a href="report.php?mode=overview&id=' . $cm->id
. '">' .
339 quiz_num_attempt_summary($quiz, $cm) . '</a><br />' .
340 get_string('cannoteditafterattempts', 'quiz');
343 $sumgrades = quiz_print_question_list($quiz, $thispageurl, false, $quiz_showbreaks, $quiz_reordertool);
344 if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $quiz->instance
)) {
345 error('Failed to set sumgrades');
349 print_footer($course);
353 // two column layout with quiz info in left column
354 $strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest())
355 ?
update_module_button($cm->id
, $course->id
, get_string('modulename', 'quiz'))
357 $navigation = build_navigation($streditingquiz, $cm);
358 print_header_simple($streditingquiz, '', $navigation, "", "", true, $strupdatemodule);
360 $currenttab = 'edit';
365 echo '<table border="0" style="width:100%" cellpadding="2" cellspacing="0">';
366 echo '<tr><td style="width:50%" valign="top">';
367 print_box_start('generalbox quizquestions');
368 print_heading(get_string('questionsinthisquiz', 'quiz'), '', 2);
370 $sumgrades = quiz_print_question_list($quiz, $thispageurl, true, $quiz_showbreaks, $quiz_reordertool);
371 if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $quiz->instance
)) {
372 error('Failed to set sumgrades');
377 echo '</td><td style="width:50%" valign="top">';
379 question_showbank('editq', $contexts, $thispageurl, $cm, $pagevars['qpage'], $pagevars['qperpage'], $pagevars['qsortorder'], $pagevars['qsortorderdecoded'],
380 $pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'], $pagevars['showquestiontext']);
385 print_footer($course);