Commit | Line | Data |
---|---|---|
83192608 | 1 | <?php |
f9b0500f TH |
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. | |
13 | // | |
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/>. | |
16 | ||
f4b879dd | 17 | |
ee1fb969 | 18 | /** |
f9b0500f TH |
19 | * This contains functions that are called from within the quiz module only |
20 | * Functions that are also called by core Moodle are in {@link lib.php} | |
21 | * This script also loads the code in {@link questionlib.php} which holds | |
22 | * the module-indpendent code for handling questions and which in turn | |
23 | * initialises all the questiontype classes. | |
f63a4ff2 | 24 | * |
ba643847 | 25 | * @package mod |
f9b0500f | 26 | * @subpackage quiz |
ba643847 TH |
27 | * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} |
28 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
f9b0500f TH |
29 | */ |
30 | ||
ee1fb969 | 31 | |
a17b297d | 32 | defined('MOODLE_INTERNAL') || die(); |
ee1fb969 | 33 | |
a17b297d | 34 | require_once($CFG->dirroot . '/mod/quiz/locallib.php'); |
f9b0500f | 35 | |
2a874d65 | 36 | define('NUM_QS_TO_SHOW_IN_RANDOM', 3); |
37 | ||
76cf77e4 TH |
38 | /** |
39 | * Verify that the question exists, and the user has permission to use it. | |
40 | * Does not return. Throws an exception if the question cannot be used. | |
41 | * @param int $questionid The id of the question. | |
42 | */ | |
43 | function quiz_require_question_use($questionid) { | |
44 | global $DB; | |
45 | $question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST); | |
46 | question_require_capability_on($question, 'use'); | |
47 | } | |
48 | ||
49 | /** | |
50 | * Verify that the question exists, and the user has permission to use it. | |
51 | * @param int $questionid The id of the question. | |
52 | * @return bool whether the user can use this question. | |
53 | */ | |
54 | function quiz_has_question_use($questionid) { | |
55 | global $DB; | |
56 | $question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST); | |
57 | return question_has_capability_on($question, 'use'); | |
58 | } | |
59 | ||
ee1fb969 | 60 | /** |
4299df1d | 61 | * Remove a question from a quiz |
62 | * @param object $quiz the quiz object. | |
63 | * @param int $questionid The id of the question to be deleted. | |
64 | */ | |
65 | function quiz_remove_question($quiz, $questionid) { | |
9cf4a18b | 66 | global $DB; |
ee1fb969 | 67 | |
4299df1d | 68 | $questionids = explode(',', $quiz->questions); |
69 | $key = array_search($questionid, $questionids); | |
70 | if ($key === false) { | |
71 | return; | |
ee1fb969 | 72 | } |
73 | ||
4299df1d | 74 | unset($questionids[$key]); |
75 | $quiz->questions = implode(',', $questionids); | |
76 | $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id)); | |
aac80ff3 TH |
77 | $DB->delete_records('quiz_question_instances', |
78 | array('quiz' => $quiz->instance, 'question' => $questionid)); | |
4299df1d | 79 | } |
80 | ||
81 | /** | |
82 | * Remove an empty page from the quiz layout. If that is not possible, do nothing. | |
83 | * @param string $layout the existinng layout, $quiz->questions. | |
f7970e3c | 84 | * @param int $index the position into $layout where the empty page should be removed. |
4299df1d | 85 | * @return the updated layout |
86 | */ | |
87 | function quiz_delete_empty_page($layout, $index) { | |
88 | $questionids = explode(',', $layout); | |
89 | ||
90 | if ($index < -1 || $index >= count($questionids) - 1) { | |
91 | return $layout; | |
ee1fb969 | 92 | } |
4299df1d | 93 | |
94 | if (($index >= 0 && $questionids[$index] != 0) || $questionids[$index + 1] != 0) { | |
95 | return $layout; // This was not an empty page. | |
96 | } | |
97 | ||
98 | unset($questionids[$index + 1]); | |
99 | ||
100 | return implode(',', $questionids); | |
ee1fb969 | 101 | } |
102 | ||
ee1fb969 | 103 | /** |
f5831eea | 104 | * Add a question to a quiz |
105 | * | |
106 | * Adds a question to a quiz by updating $quiz as well as the | |
107 | * quiz and quiz_question_instances tables. It also adds a page break | |
108 | * if required. | |
94dbfb3a TH |
109 | * @param int $id The id of the question to be added |
110 | * @param object $quiz The extended quiz object as used by edit.php | |
111 | * This is updated by this function | |
aac80ff3 TH |
112 | * @param int $page Which page in quiz to add the question on. If 0 (default), |
113 | * add at the end | |
f7970e3c | 114 | * @return bool false if the question was already in the quiz |
f5831eea | 115 | */ |
94dbfb3a | 116 | function quiz_add_quiz_question($id, $quiz, $page = 0) { |
9cf4a18b | 117 | global $DB; |
78634f1e | 118 | $questions = explode(',', quiz_clean_layout($quiz->questions)); |
ee1fb969 | 119 | if (in_array($id, $questions)) { |
120 | return false; | |
121 | } | |
122 | ||
9e83f3d1 | 123 | // Remove ending page break if it is not needed. |
ee1fb969 | 124 | if ($breaks = array_keys($questions, 0)) { |
9e83f3d1 | 125 | // Determine location of the last two page breaks. |
ee1fb969 | 126 | $end = end($breaks); |
127 | $last = prev($breaks); | |
128 | $last = $last ? $last : -1; | |
78634f1e | 129 | if (!$quiz->questionsperpage || (($end - $last - 1) < $quiz->questionsperpage)) { |
ee1fb969 | 130 | array_pop($questions); |
131 | } | |
132 | } | |
f5831eea | 133 | if (is_int($page) && $page >= 1) { |
134 | $numofpages = quiz_number_of_pages($quiz->questions); | |
135 | if ($numofpages<$page) { | |
9e83f3d1 | 136 | // The page specified does not exist in quiz. |
f5831eea | 137 | $page = 0; |
138 | } else { | |
9e83f3d1 TH |
139 | // Add ending page break - the following logic requires doing |
140 | // this at this point. | |
fa583f5f | 141 | $questions[] = 0; |
f5831eea | 142 | $currentpage = 1; |
143 | $addnow = false; | |
144 | foreach ($questions as $question) { | |
145 | if ($question == 0) { | |
fa583f5f | 146 | $currentpage++; |
9e83f3d1 TH |
147 | // The current page is the one after the one we want to add on, |
148 | // so we add the question before adding the current page. | |
f5831eea | 149 | if ($currentpage == $page + 1) { |
150 | $questions_new[] = $id; | |
fa583f5f | 151 | } |
152 | } | |
f5831eea | 153 | $questions_new[] = $question; |
fa583f5f | 154 | } |
f5831eea | 155 | $questions = $questions_new; |
fa583f5f | 156 | } |
157 | } | |
f5831eea | 158 | if ($page == 0) { |
9e83f3d1 | 159 | // Add question. |
fa583f5f | 160 | $questions[] = $id; |
9e83f3d1 | 161 | // Add ending page break. |
fa583f5f | 162 | $questions[] = 0; |
163 | } | |
ee1fb969 | 164 | |
9e83f3d1 | 165 | // Save new questionslist in database. |
f5831eea | 166 | $quiz->questions = implode(',', $questions); |
f685e830 | 167 | $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id)); |
ee1fb969 | 168 | |
f9b0500f | 169 | // Add the new question instance. |
0ff4bd08 | 170 | $instance = new stdClass(); |
f9b0500f TH |
171 | $instance->quiz = $quiz->id; |
172 | $instance->question = $id; | |
173 | $instance->grade = $DB->get_field('question', 'defaultmark', array('id' => $id)); | |
174 | $DB->insert_record('quiz_question_instances', $instance); | |
ee1fb969 | 175 | } |
176 | ||
aac80ff3 TH |
177 | function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, |
178 | $includesubcategories) { | |
f9b0500f | 179 | global $DB; |
94dbfb3a TH |
180 | |
181 | $category = $DB->get_record('question_categories', array('id' => $categoryid)); | |
182 | if (!$category) { | |
183 | print_error('invalidcategoryid', 'error'); | |
184 | } | |
185 | ||
d197ea43 | 186 | $catcontext = context::instance_by_id($category->contextid); |
94dbfb3a TH |
187 | require_capability('moodle/question:useall', $catcontext); |
188 | ||
189 | // Find existing random questions in this category that are | |
190 | // not used by any quiz. | |
191 | if ($existingquestions = $DB->get_records_sql( | |
25a03faa | 192 | "SELECT q.id, q.qtype FROM {question} q |
f9b0500f | 193 | WHERE qtype = 'random' |
94dbfb3a TH |
194 | AND category = ? |
195 | AND " . $DB->sql_compare_text('questiontext') . " = ? | |
aac80ff3 TH |
196 | AND NOT EXISTS ( |
197 | SELECT * | |
198 | FROM {quiz_question_instances} | |
199 | WHERE question = q.id) | |
94dbfb3a TH |
200 | ORDER BY id", array($category->id, $includesubcategories))) { |
201 | // Take as many of these as needed. | |
202 | while (($existingquestion = array_shift($existingquestions)) && $number > 0) { | |
203 | quiz_add_quiz_question($existingquestion->id, $quiz, $addonpage); | |
204 | $number -= 1; | |
205 | } | |
206 | } | |
207 | ||
208 | if ($number <= 0) { | |
209 | return; | |
210 | } | |
211 | ||
212 | // More random questions are needed, create them. | |
94dbfb3a | 213 | for ($i = 0; $i < $number; $i += 1) { |
fd214b59 TH |
214 | $form = new stdClass(); |
215 | $form->questiontext = array('text' => $includesubcategories, 'format' => 0); | |
94dbfb3a | 216 | $form->category = $category->id . ',' . $category->contextid; |
fd214b59 TH |
217 | $form->defaultmark = 1; |
218 | $form->hidden = 1; | |
9e83f3d1 | 219 | $form->stamp = make_unique_id_code(); // Set the unique code (not to be changed). |
0ff4bd08 | 220 | $question = new stdClass(); |
f9b0500f TH |
221 | $question->qtype = 'random'; |
222 | $question = question_bank::get_qtype('random')->save_question($question, $form); | |
94dbfb3a TH |
223 | if (!isset($question->id)) { |
224 | print_error('cannotinsertrandomquestion', 'quiz'); | |
225 | } | |
226 | quiz_add_quiz_question($question->id, $quiz, $addonpage); | |
227 | } | |
228 | } | |
229 | ||
4299df1d | 230 | /** |
231 | * Add a page break after at particular position$. | |
232 | * @param string $layout the existinng layout, $quiz->questions. | |
f7970e3c | 233 | * @param int $index the position into $layout where the empty page should be removed. |
4299df1d | 234 | * @return the updated layout |
235 | */ | |
236 | function quiz_add_page_break_at($layout, $index) { | |
237 | $questionids = explode(',', $layout); | |
238 | if ($index < 0 || $index >= count($questionids)) { | |
239 | return $layout; | |
240 | } | |
241 | ||
242 | array_splice($questionids, $index, 0, '0'); | |
243 | ||
244 | return implode(',', $questionids); | |
245 | } | |
246 | ||
247 | /** | |
248 | * Add a page break after a particular question. | |
249 | * @param string $layout the existinng layout, $quiz->questions. | |
f7970e3c | 250 | * @param int $qustionid the question to add the page break after. |
4299df1d | 251 | * @return the updated layout |
252 | */ | |
253 | function quiz_add_page_break_after($layout, $questionid) { | |
254 | $questionids = explode(',', $layout); | |
255 | $key = array_search($questionid, $questionids); | |
256 | if ($key === false || !$questionid) { | |
257 | return $layout; | |
258 | } | |
259 | ||
260 | array_splice($questionids, $key + 1, 0, '0'); | |
261 | ||
262 | return implode(',', $questionids); | |
263 | } | |
264 | ||
265 | /** | |
266 | * Update the database after $quiz->questions has been changed. For example, | |
267 | * this deletes preview attempts and updates $quiz->sumgrades. | |
268 | * @param $quiz the quiz object. | |
269 | */ | |
270 | function quiz_save_new_layout($quiz) { | |
271 | global $DB; | |
272 | $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id)); | |
4299df1d | 273 | quiz_delete_previews($quiz); |
18dff757 | 274 | quiz_update_sumgrades($quiz); |
4299df1d | 275 | } |
276 | ||
ee1fb969 | 277 | /** |
f5831eea | 278 | * Save changes to question instance |
279 | * | |
280 | * Saves changes to the question grades in the quiz_question_instances table. | |
281 | * It does not update 'sumgrades' in the quiz table. | |
f9b0500f | 282 | * |
f7970e3c TH |
283 | * @param int grade The maximal grade for the question |
284 | * @param int $questionid The id of the question | |
285 | * @param int $quizid The id of the quiz to update / add the instances for. | |
f5831eea | 286 | */ |
f9b0500f | 287 | function quiz_update_question_instance($grade, $questionid, $quiz) { |
fd214b59 TH |
288 | global $DB; |
289 | $instance = $DB->get_record('quiz_question_instances', array('quiz' => $quiz->id, | |
f9b0500f TH |
290 | 'question' => $questionid)); |
291 | $slot = quiz_get_slot_for_question($quiz, $questionid); | |
292 | ||
293 | if (!$instance || !$slot) { | |
294 | throw new coding_exception('Attempt to change the grade of a quesion not in the quiz.'); | |
ee1fb969 | 295 | } |
f9b0500f TH |
296 | |
297 | if (abs($grade - $instance->grade) < 1e-7) { | |
298 | // Grade has not changed. Nothing to do. | |
299 | return; | |
300 | } | |
301 | ||
302 | $instance->grade = $grade; | |
303 | $DB->update_record('quiz_question_instances', $instance); | |
6b5f24d3 | 304 | question_engine::set_max_mark_in_attempts(new qubaids_for_quiz($quiz->id), |
f9b0500f | 305 | $slot, $grade); |
ee1fb969 | 306 | } |
307 | ||
4299df1d | 308 | // Private function used by the following two. |
309 | function _quiz_move_question($layout, $questionid, $shift) { | |
310 | if (!$questionid || !($shift == 1 || $shift == -1)) { | |
311 | return $layout; | |
312 | } | |
313 | ||
314 | $questionids = explode(',', $layout); | |
315 | $key = array_search($questionid, $questionids); | |
316 | if ($key === false) { | |
317 | return $layout; | |
318 | } | |
319 | ||
320 | $otherkey = $key + $shift; | |
321 | if ($otherkey < 0 || $otherkey >= count($questionids) - 1) { | |
322 | return $layout; | |
323 | } | |
324 | ||
325 | $temp = $questionids[$otherkey]; | |
326 | $questionids[$otherkey] = $questionids[$key]; | |
327 | $questionids[$key] = $temp; | |
328 | ||
329 | return implode(',', $questionids); | |
330 | } | |
331 | ||
332 | /** | |
333 | * Move a particular question one space earlier in the $quiz->questions list. | |
334 | * If that is not possible, do nothing. | |
335 | * @param string $layout the existinng layout, $quiz->questions. | |
f7970e3c | 336 | * @param int $questionid the id of a question. |
4299df1d | 337 | * @return the updated layout |
338 | */ | |
339 | function quiz_move_question_up($layout, $questionid) { | |
340 | return _quiz_move_question($layout, $questionid, -1); | |
341 | } | |
342 | ||
343 | /** | |
344 | * Move a particular question one space later in the $quiz->questions list. | |
345 | * If that is not possible, do nothing. | |
346 | * @param string $layout the existinng layout, $quiz->questions. | |
f7970e3c | 347 | * @param int $questionid the id of a question. |
4299df1d | 348 | * @return the updated layout |
349 | */ | |
350 | function quiz_move_question_down($layout, $questionid) { | |
351 | return _quiz_move_question($layout, $questionid, +1); | |
352 | } | |
353 | ||
ee1fb969 | 354 | /** |
f5831eea | 355 | * Prints a list of quiz questions for the edit.php main view for edit |
356 | * ($reordertool = false) and order and paging ($reordertool = true) tabs | |
357 | * | |
f5831eea | 358 | * @param object $quiz This is not the standard quiz object used elsewhere but |
359 | * it contains the quiz layout in $quiz->questions and the grades in | |
360 | * $quiz->grades | |
76cf77e4 | 361 | * @param moodle_url $pageurl The url of the current page with the parameters required |
f5831eea | 362 | * for links returning to the current page, as a moodle_url object |
f7970e3c TH |
363 | * @param bool $allowdelete Indicates whether the delete icons should be displayed |
364 | * @param bool $reordertool Indicates whether the reorder tool should be displayed | |
365 | * @param bool $quiz_qbanktool Indicates whether the question bank should be displayed | |
366 | * @param bool $hasattempts Indicates whether the quiz has attempts | |
76cf77e4 TH |
367 | * @param object $defaultcategoryobj |
368 | * @param bool $canaddquestion is the user able to add and use questions anywere? | |
369 | * @param bool $canaddrandom is the user able to add random questions anywere? | |
f5831eea | 370 | */ |
f2956c98 | 371 | function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool, |
76cf77e4 | 372 | $quiz_qbanktool, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom) { |
cef18275 | 373 | global $CFG, $DB, $OUTPUT; |
f5831eea | 374 | $strorder = get_string('order'); |
375 | $strquestionname = get_string('questionname', 'quiz'); | |
5127e52d | 376 | $strmaxmark = get_string('markedoutof', 'question'); |
ee1fb969 | 377 | $strremove = get_string('remove', 'quiz'); |
f5831eea | 378 | $stredit = get_string('edit'); |
379 | $strview = get_string('view'); | |
380 | $straction = get_string('action'); | |
381 | $strmove = get_string('move'); | |
382 | $strmoveup = get_string('moveup'); | |
383 | $strmovedown = get_string('movedown'); | |
384 | $strsave = get_string('save', 'quiz'); | |
385 | $strreorderquestions = get_string('reorderquestions', 'quiz'); | |
386 | ||
387 | $strselectall = get_string('selectall', 'quiz'); | |
388 | $strselectnone = get_string('selectnone', 'quiz'); | |
389 | $strtype = get_string('type', 'quiz'); | |
390 | $strpreview = get_string('preview', 'quiz'); | |
ee1fb969 | 391 | |
477c217f | 392 | if ($quiz->questions) { |
fa583f5f | 393 | list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions)); |
f9b0500f TH |
394 | $params[] = $quiz->id; |
395 | $questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.grade as maxmark | |
396 | FROM {question} q | |
397 | JOIN {question_categories} qc ON qc.id = q.category | |
398 | JOIN {quiz_question_instances} qqi ON qqi.question = q.id | |
399 | WHERE q.id $usql AND qqi.quiz = ?", $params); | |
477c217f | 400 | } else { |
401 | $questions = array(); | |
ee1fb969 | 402 | } |
403 | ||
f5831eea | 404 | $layout = quiz_clean_layout($quiz->questions); |
fa583f5f | 405 | $order = explode(',', $layout); |
f5831eea | 406 | $lastindex = count($order) - 1; |
fa583f5f | 407 | |
3e10e429 | 408 | $disabled = ''; |
409 | $pagingdisabled = ''; | |
477c217f | 410 | if ($hasattempts) { |
f5831eea | 411 | $disabled = 'disabled="disabled"'; |
fa583f5f | 412 | } |
3e10e429 | 413 | if ($hasattempts || $quiz->shufflequestions) { |
f5831eea | 414 | $pagingdisabled = 'disabled="disabled"'; |
ee68029a | 415 | } |
ee1fb969 | 416 | |
f5831eea | 417 | $reordercontrolssetdefaultsubmit = '<div style="display:none;">' . |
418 | '<input type="submit" name="savechanges" value="' . | |
3e10e429 | 419 | $strreorderquestions . '" ' . $pagingdisabled . ' /></div>'; |
f5831eea | 420 | $reordercontrols1 = '<div class="addnewpagesafterselected">' . |
421 | '<input type="submit" name="addnewpagesafterselected" value="' . | |
422 | get_string('addnewpagesafterselected', 'quiz') . '" ' . | |
423 | $pagingdisabled . ' /></div>'; | |
7b161e12 | 424 | $reordercontrols1 .= '<div class="quizdeleteselected">' . |
425 | '<input type="submit" name="quizdeleteselected" ' . | |
426 | 'onclick="return confirm(\'' . | |
427 | get_string('areyousureremoveselected', 'quiz') . '\');" value="' . | |
428 | get_string('removeselected', 'quiz') . '" ' . $disabled . ' /></div>'; | |
fa583f5f | 429 | |
3211569a | 430 | $a = '<input name="moveselectedonpagetop" type="text" size="2" ' . |
f5831eea | 431 | $pagingdisabled . ' />'; |
3211569a | 432 | $b = '<input name="moveselectedonpagebottom" type="text" size="2" ' . |
14bf4574 | 433 | $pagingdisabled . ' />'; |
f5831eea | 434 | |
435 | $reordercontrols2top = '<div class="moveselectedonpage">' . | |
0465ef6e | 436 | '<label>' . get_string('moveselectedonpage', 'quiz', $a) . '</label>' . |
f5831eea | 437 | '<input type="submit" name="savechanges" value="' . |
438 | $strmove . '" ' . $pagingdisabled . ' />' . ' | |
439 | <br /><input type="submit" name="savechanges" value="' . | |
3e10e429 | 440 | $strreorderquestions . '" /></div>'; |
f5831eea | 441 | $reordercontrols2bottom = '<div class="moveselectedonpage">' . |
442 | '<input type="submit" name="savechanges" value="' . | |
3e10e429 | 443 | $strreorderquestions . '" /><br />' . |
0465ef6e | 444 | '<label>' . get_string('moveselectedonpage', 'quiz', $b) . '</label>' . |
f5831eea | 445 | '<input type="submit" name="savechanges" value="' . |
446 | $strmove . '" ' . $pagingdisabled . ' /> ' . '</div>'; | |
447 | ||
448 | $reordercontrols3 = '<a href="javascript:select_all_in(\'FORM\', null, ' . | |
449 | '\'quizquestions\');">' . | |
450 | $strselectall . '</a> /'; | |
451 | $reordercontrols3.= ' <a href="javascript:deselect_all_in(\'FORM\', ' . | |
452 | 'null, \'quizquestions\');">' . | |
453 | $strselectnone . '</a>'; | |
454 | ||
455 | $reordercontrolstop = '<div class="reordercontrols">' . | |
456 | $reordercontrolssetdefaultsubmit . | |
457 | $reordercontrols1 . $reordercontrols2top . $reordercontrols3 . "</div>"; | |
458 | $reordercontrolsbottom = '<div class="reordercontrols">' . | |
459 | $reordercontrolssetdefaultsubmit . | |
460 | $reordercontrols2bottom . $reordercontrols1 . $reordercontrols3 . "</div>"; | |
461 | ||
462 | if ($reordertool) { | |
fa583f5f | 463 | echo '<form method="post" action="edit.php" id="quizquestions"><div>'; |
464 | ||
6ea66ff3 | 465 | echo html_writer::input_hidden_params($pageurl); |
f5831eea | 466 | echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />'; |
fa583f5f | 467 | |
468 | echo $reordercontrolstop; | |
9cf4a18b | 469 | } |
9aa82ed6 | 470 | |
9e83f3d1 | 471 | // The current question ordinal (no descriptions). |
fa583f5f | 472 | $qno = 1; |
9e83f3d1 | 473 | // The current question (includes questions and descriptions). |
f5831eea | 474 | $questioncount = 0; |
9e83f3d1 | 475 | // The current page number in iteration. |
fa583f5f | 476 | $pagecount = 0; |
477 | ||
f5831eea | 478 | $pageopen = false; |
fa583f5f | 479 | |
a530d4a9 | 480 | $returnurl = $pageurl->out_as_local_url(false); |
f5831eea | 481 | $questiontotalcount = count($order); |
fa583f5f | 482 | |
f9b0500f | 483 | foreach ($order as $count => $qnum) { |
ee1fb969 | 484 | |
f5831eea | 485 | $reordercheckbox = ''; |
486 | $reordercheckboxlabel = ''; | |
487 | $reordercheckboxlabelclose = ''; | |
fa583f5f | 488 | |
9e83f3d1 | 489 | // If the questiontype is missing change the question type. |
f9b0500f TH |
490 | if ($qnum && !array_key_exists($qnum, $questions)) { |
491 | $fakequestion = new stdClass(); | |
068384ce TH |
492 | $fakequestion->id = $qnum; |
493 | $fakequestion->category = 0; | |
f9b0500f | 494 | $fakequestion->qtype = 'missingtype'; |
068384ce TH |
495 | $fakequestion->name = get_string('missingquestion', 'quiz'); |
496 | $fakequestion->questiontext = ' '; | |
497 | $fakequestion->questiontextformat = FORMAT_HTML; | |
498 | $fakequestion->length = 1; | |
f9b0500f TH |
499 | $questions[$qnum] = $fakequestion; |
500 | $quiz->grades[$qnum] = 0; | |
501 | ||
fd214b59 | 502 | } else if ($qnum && !question_bank::qtype_exists($questions[$qnum]->qtype)) { |
4eda4eec | 503 | $questions[$qnum]->qtype = 'missingtype'; |
504 | } | |
f9b0500f | 505 | |
4299df1d | 506 | if ($qnum != 0 || ($qnum == 0 && !$pageopen)) { |
9e83f3d1 | 507 | // This is either a question or a page break after another (no page is currently open). |
4299df1d | 508 | if (!$pageopen) { |
9e83f3d1 | 509 | // If no page is open, start display of a page. |
fa583f5f | 510 | $pagecount++; |
f5831eea | 511 | echo '<div class="quizpage"><span class="pagetitle">' . |
512 | get_string('page') . ' ' . $pagecount . | |
fa583f5f | 513 | '</span><div class="pagecontent">'; |
4299df1d | 514 | $pageopen = true; |
fa583f5f | 515 | } |
f9b0500f | 516 | if ($qnum == 0 && $count < $questiontotalcount) { |
4299df1d | 517 | // This is the second successive page break. Tell the user the page is empty. |
fa583f5f | 518 | echo '<div class="pagestatus">'; |
f5831eea | 519 | print_string('noquestionsonpage', 'quiz'); |
fa583f5f | 520 | echo '</div>'; |
5be23736 | 521 | if ($allowdelete) { |
fa583f5f | 522 | echo '<div class="quizpagedelete">'; |
aac80ff3 TH |
523 | echo $OUTPUT->action_icon($pageurl->out(true, |
524 | array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())), | |
fd214b59 | 525 | new pix_icon('t/delete', $strremove), |
aac80ff3 TH |
526 | new component_action('click', |
527 | 'M.core_scroll_manager.save_scroll_action'), | |
fd214b59 | 528 | array('title' => $strremove)); |
fa583f5f | 529 | echo '</div>'; |
530 | } | |
531 | } | |
532 | ||
5be23736 | 533 | if ($qnum != 0) { |
fa583f5f | 534 | $question = $questions[$qnum]; |
fb6dcdab TH |
535 | $questionparams = array( |
536 | 'returnurl' => $returnurl, | |
537 | 'cmid' => $quiz->cmid, | |
538 | 'id' => $question->id); | |
539 | $questionurl = new moodle_url('/question/question.php', | |
fa583f5f | 540 | $questionparams); |
541 | $questioncount++; | |
4eda4eec | 542 | |
9e83f3d1 | 543 | // This is an actual question. |
aac80ff3 | 544 | ?> |
fa583f5f | 545 | <div class="question"> |
944efb3e | 546 | <div class="questioncontainer <?php echo $question->qtype; ?>"> |
fa583f5f | 547 | <div class="qnum"> |
aac80ff3 TH |
548 | <?php |
549 | $reordercheckbox = ''; | |
550 | $reordercheckboxlabel = ''; | |
551 | $reordercheckboxlabelclose = ''; | |
552 | if ($reordertool) { | |
553 | $reordercheckbox = '<input type="checkbox" name="s' . $question->id . | |
554 | '" id="s' . $question->id . '" />'; | |
555 | $reordercheckboxlabel = '<label for="s' . $question->id . '">'; | |
556 | $reordercheckboxlabelclose = '</label>'; | |
557 | } | |
8418e5b7 WO |
558 | if ($question->length == 0) { |
559 | $qnodisplay = get_string('infoshort', 'quiz'); | |
560 | } else if ($quiz->shufflequestions) { | |
561 | $qnodisplay = '?'; | |
562 | } else { | |
563 | if ($qno > 999 || ($reordertool && $qno > 99)) { | |
564 | $qnodisplay = html_writer::tag('small', $qno); | |
aac80ff3 | 565 | } else { |
8418e5b7 | 566 | $qnodisplay = $qno; |
aac80ff3 | 567 | } |
aac80ff3 | 568 | $qno += $question->length; |
fa583f5f | 569 | } |
8418e5b7 WO |
570 | echo $reordercheckboxlabel . $qnodisplay . $reordercheckboxlabelclose . |
571 | $reordercheckbox; | |
fa583f5f | 572 | |
aac80ff3 | 573 | ?> |
fa583f5f | 574 | </div> |
575 | <div class="content"> | |
576 | <div class="questioncontrols"> | |
577 | <?php | |
aac80ff3 TH |
578 | if ($count != 0) { |
579 | if (!$hasattempts) { | |
580 | $upbuttonclass = ''; | |
581 | if ($count >= $lastindex - 1) { | |
582 | $upbuttonclass = 'upwithoutdown'; | |
583 | } | |
584 | echo $OUTPUT->action_icon($pageurl->out(true, | |
585 | array('up' => $question->id, 'sesskey'=>sesskey())), | |
586 | new pix_icon('t/up', $strmoveup), | |
587 | new component_action('click', | |
588 | 'M.core_scroll_manager.save_scroll_action'), | |
589 | array('title' => $strmoveup)); | |
fa583f5f | 590 | } |
ee1fb969 | 591 | |
ee1fb969 | 592 | } |
aac80ff3 TH |
593 | if ($count < $lastindex - 1) { |
594 | if (!$hasattempts) { | |
595 | echo $OUTPUT->action_icon($pageurl->out(true, | |
596 | array('down' => $question->id, 'sesskey'=>sesskey())), | |
597 | new pix_icon('t/down', $strmovedown), | |
598 | new component_action('click', | |
599 | 'M.core_scroll_manager.save_scroll_action'), | |
600 | array('title' => $strmovedown)); | |
601 | } | |
602 | } | |
068384ce | 603 | if ($allowdelete && ($question->qtype == 'missingtype' || |
aac80ff3 | 604 | question_has_capability_on($question, 'use', $question->category))) { |
9e83f3d1 | 605 | // Remove from quiz, not question delete. |
aac80ff3 TH |
606 | if (!$hasattempts) { |
607 | echo $OUTPUT->action_icon($pageurl->out(true, | |
608 | array('remove' => $question->id, 'sesskey'=>sesskey())), | |
609 | new pix_icon('t/delete', $strremove), | |
610 | new component_action('click', | |
611 | 'M.core_scroll_manager.save_scroll_action'), | |
612 | array('title' => $strremove)); | |
613 | } | |
fa583f5f | 614 | } |
fa583f5f | 615 | ?> |
616 | </div><?php | |
068384ce | 617 | if (!in_array($question->qtype, array('description', 'missingtype')) && !$reordertool) { |
aac80ff3 | 618 | ?> |
fa583f5f | 619 | <div class="points"> |
fd214b59 | 620 | <form method="post" action="edit.php" class="quizsavegradesform"><div> |
fa583f5f | 621 | <fieldset class="invisiblefieldset" style="display: block;"> |
5127e52d | 622 | <label for="<?php echo "inputq$question->id" ?>"><?php echo $strmaxmark; ?></label>:<br /> |
973d2660 | 623 | <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" /> |
6ea66ff3 | 624 | <?php echo html_writer::input_hidden_params($pageurl); ?> |
fa583f5f | 625 | <input type="hidden" name="savechanges" value="save" /> |
aac80ff3 TH |
626 | <?php |
627 | echo '<input type="text" name="g' . $question->id . | |
628 | '" id="inputq' . $question->id . | |
629 | '" size="' . ($quiz->decimalpoints + 2) . | |
630 | '" value="' . (0 + $quiz->grades[$qnum]) . | |
631 | '" tabindex="' . ($lastindex + $qno) . '" />'; | |
632 | ?> | |
3fd9edf0 | 633 | <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?>" /> |
fa583f5f | 634 | </fieldset> |
aac80ff3 TH |
635 | <?php |
636 | if ($question->qtype == 'random') { | |
637 | echo '<a href="' . $questionurl->out() . | |
638 | '" class="configurerandomquestion">' . | |
639 | get_string("configurerandomquestion", "quiz") . '</a>'; | |
640 | } | |
f4b879dd | 641 | |
aac80ff3 | 642 | ?> |
fa583f5f | 643 | </div> |
644 | </form> | |
645 | ||
646 | </div> | |
aac80ff3 TH |
647 | <?php |
648 | } else if ($reordertool) { | |
649 | if ($qnum) { | |
650 | ?> | |
fa583f5f | 651 | <div class="qorder"> |
aac80ff3 | 652 | <?php |
4b737784 | 653 | echo '<label class="accesshide" for="o' . $question->id . '">' . |
0465ef6e | 654 | get_string('questionposition', 'quiz', $qnodisplay) . '</label>'; |
aac80ff3 | 655 | echo '<input type="text" name="o' . $question->id . |
0465ef6e | 656 | '" id="o' . $question->id . '"' . |
aac80ff3 TH |
657 | '" size="2" value="' . (10*$count + 10) . |
658 | '" tabindex="' . ($lastindex + $qno) . '" />'; | |
659 | ?> | |
fa583f5f | 660 | </div> |
aac80ff3 TH |
661 | <?php |
662 | } | |
fa583f5f | 663 | } |
aac80ff3 | 664 | ?> |
fa583f5f | 665 | <div class="questioncontentcontainer"> |
aac80ff3 | 666 | <?php |
9e83f3d1 | 667 | if ($question->qtype == 'random') { // It is a random question. |
aac80ff3 TH |
668 | if (!$reordertool) { |
669 | quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool); | |
670 | } else { | |
671 | quiz_print_randomquestion_reordertool($question, $pageurl, $quiz); | |
672 | } | |
9e83f3d1 | 673 | } else { // It is a single question. |
aac80ff3 TH |
674 | if (!$reordertool) { |
675 | quiz_print_singlequestion($question, $returnurl, $quiz); | |
676 | } else { | |
677 | quiz_print_singlequestion_reordertool($question, $returnurl, $quiz); | |
678 | } | |
fa583f5f | 679 | } |
fa583f5f | 680 | ?> |
681 | </div> | |
682 | </div> | |
683 | </div> | |
684 | </div> | |
685 | ||
aac80ff3 | 686 | <?php |
fa583f5f | 687 | } |
ee1fb969 | 688 | } |
9e83f3d1 | 689 | // A page break: end the existing page. |
f5831eea | 690 | if ($qnum == 0) { |
691 | if ($pageopen) { | |
aac80ff3 TH |
692 | if (!$reordertool && !($quiz->shufflequestions && |
693 | $count < $questiontotalcount - 1)) { | |
fa583f5f | 694 | quiz_print_pagecontrols($quiz, $pageurl, $pagecount, |
76cf77e4 | 695 | $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom); |
f9b0500f | 696 | } else if ($count < $questiontotalcount - 1) { |
9e83f3d1 TH |
697 | // Do not include the last page break for reordering |
698 | // to avoid creating a new extra page in the end. | |
3e10e429 | 699 | echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' . |
f5831eea | 700 | (10*$count + 10) . '" />'; |
fa583f5f | 701 | } |
702 | echo "</div></div>"; | |
703 | ||
3e10e429 | 704 | if (!$reordertool && !$quiz->shufflequestions) { |
39e37019 | 705 | echo $OUTPUT->container_start('addpage'); |
aac80ff3 TH |
706 | $url = new moodle_url($pageurl->out_omit_querystring(), |
707 | array('cmid' => $quiz->cmid, 'courseid' => $quiz->course, | |
708 | 'addpage' => $count, 'sesskey' => sesskey())); | |
fd214b59 TH |
709 | echo $OUTPUT->single_button($url, get_string('addpagehere', 'quiz'), 'post', |
710 | array('disabled' => $hasattempts, | |
aac80ff3 TH |
711 | 'actions' => array(new component_action('click', |
712 | 'M.core_scroll_manager.save_scroll_action')))); | |
39e37019 | 713 | echo $OUTPUT->container_end(); |
fa583f5f | 714 | } |
f5831eea | 715 | $pageopen = false; |
fa583f5f | 716 | $count++; |
717 | } | |
ee1fb969 | 718 | } |
ee1fb969 | 719 | |
fa583f5f | 720 | } |
f5831eea | 721 | if ($reordertool) { |
fa583f5f | 722 | echo $reordercontrolsbottom; |
723 | echo '</div></form>'; | |
724 | } | |
fa583f5f | 725 | } |
726 | ||
727 | /** | |
728 | * Print all the controls for adding questions directly into the | |
729 | * specific page in the edit tab of edit.php | |
730 | * | |
76cf77e4 TH |
731 | * @param object $quiz This is not the standard quiz object used elsewhere but |
732 | * it contains the quiz layout in $quiz->questions and the grades in | |
733 | * $quiz->grades | |
734 | * @param moodle_url $pageurl The url of the current page with the parameters required | |
735 | * for links returning to the current page, as a moodle_url object | |
736 | * @param int $page the current page number. | |
737 | * @param bool $hasattempts Indicates whether the quiz has attempts | |
738 | * @param object $defaultcategoryobj | |
739 | * @param bool $canaddquestion is the user able to add and use questions anywere? | |
740 | * @param bool $canaddrandom is the user able to add random questions anywere? | |
fa583f5f | 741 | */ |
76cf77e4 TH |
742 | function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, |
743 | $defaultcategoryobj, $canaddquestion, $canaddrandom) { | |
825116fb | 744 | global $CFG, $OUTPUT; |
3e10e429 | 745 | static $randombuttoncount = 0; |
746 | $randombuttoncount++; | |
fa583f5f | 747 | echo '<div class="pagecontrols">'; |
cd120b23 | 748 | |
9e83f3d1 | 749 | // Get the current context. |
c492a78e | 750 | $thiscontext = context_course::instance($quiz->course); |
fa583f5f | 751 | $contexts = new question_edit_contexts($thiscontext); |
cd120b23 | 752 | |
753 | // Get the default category. | |
94dbfb3a | 754 | list($defaultcategoryid) = explode(',', $pageurl->param('cat')); |
f2956c98 TH |
755 | if (empty($defaultcategoryid)) { |
756 | $defaultcategoryid = $defaultcategoryobj->id; | |
757 | } | |
cd120b23 | 758 | |
76cf77e4 | 759 | if ($canaddquestion) { |
9e83f3d1 | 760 | // Create the url the question page will return to. |
76cf77e4 TH |
761 | $returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page)); |
762 | ||
763 | // Print a button linking to the choose question type page. | |
764 | $returnurladdtoquiz = $returnurladdtoquiz->out_as_local_url(false); | |
765 | $newquestionparams = array('returnurl' => $returnurladdtoquiz, | |
766 | 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion'); | |
767 | create_new_question_button($defaultcategoryid, $newquestionparams, | |
768 | get_string('addaquestion', 'quiz'), | |
769 | get_string('createquestionandadd', 'quiz'), $hasattempts); | |
770 | } | |
cd120b23 | 771 | |
fa583f5f | 772 | if ($hasattempts) { |
773 | $disabled = 'disabled="disabled"'; | |
774 | } else { | |
775 | $disabled = ''; | |
776 | } | |
76cf77e4 | 777 | if ($canaddrandom) { |
fa583f5f | 778 | ?> |
fa583f5f | 779 | <div class="singlebutton"> |
aac80ff3 TH |
780 | <form class="randomquestionform" action="<?php echo $CFG->wwwroot; |
781 | ?>/mod/quiz/addrandom.php" method="get"> | |
fa583f5f | 782 | <div> |
aac80ff3 TH |
783 | <input type="hidden" class="addonpage_formelement" name="addonpage" value="<?php |
784 | echo $page; ?>" /> | |
fa583f5f | 785 | <input type="hidden" name="cmid" value="<?php echo $quiz->cmid; ?>" /> |
786 | <input type="hidden" name="courseid" value="<?php echo $quiz->course; ?>" /> | |
aac80ff3 TH |
787 | <input type="hidden" name="category" value="<?php |
788 | echo $pageurl->param('cat'); ?>" /> | |
789 | <input type="hidden" name="returnurl" value="<?php | |
790 | echo s(str_replace($CFG->wwwroot, '', $pageurl->out(false))); ?>" /> | |
791 | <input type="submit" id="addrandomdialoglaunch_<?php | |
792 | echo $randombuttoncount; ?>" value="<?php | |
793 | echo get_string('addarandomquestion', 'quiz'); ?>" <?php | |
794 | echo " $disabled"; ?> /> | |
fa583f5f | 795 | </div> |
796 | </form> | |
797 | </div> | |
76cf77e4 TH |
798 | <?php echo $OUTPUT->help_icon('addarandomquestion', 'quiz'); |
799 | } | |
fa583f5f | 800 | echo "\n</div>"; |
801 | } | |
fa583f5f | 802 | |
fa583f5f | 803 | /** |
804 | * Print a given single question in quiz for the edit tab of edit.php. | |
805 | * Meant to be used from quiz_print_question_list() | |
f5831eea | 806 | * |
414d5bfb | 807 | * @param object $question A question object from the database questions table |
2a874d65 | 808 | * @param object $returnurl The url to get back to this page, for example after editing. |
414d5bfb | 809 | * @param object $quiz The quiz in the context of which the question is being displayed |
fa583f5f | 810 | */ |
f5831eea | 811 | function quiz_print_singlequestion($question, $returnurl, $quiz) { |
068384ce | 812 | echo '<div class="singlequestion ' . $question->qtype . '">'; |
aac80ff3 TH |
813 | echo quiz_question_edit_button($quiz->cmid, $question, $returnurl, |
814 | quiz_question_tostring($question) . ' '); | |
2a874d65 | 815 | echo '<span class="questiontype">'; |
5cc021a0 | 816 | echo print_question_icon($question); |
f9b0500f | 817 | echo ' ' . question_bank::get_qtype_name($question->qtype) . '</span>'; |
aac80ff3 TH |
818 | echo '<span class="questionpreview">' . |
819 | quiz_question_preview_button($quiz, $question, true) . '</span>'; | |
2a874d65 | 820 | echo "</div>\n"; |
fa583f5f | 821 | } |
822 | /** | |
823 | * Print a given random question in quiz for the edit tab of edit.php. | |
824 | * Meant to be used from quiz_print_question_list() | |
f5831eea | 825 | * |
414d5bfb | 826 | * @param object $question A question object from the database questions table |
827 | * @param object $questionurl The url of the question editing page as a moodle_url object | |
828 | * @param object $quiz The quiz in the context of which the question is being displayed | |
f7970e3c | 829 | * @param bool $quiz_qbanktool Indicate to this function if the question bank window open |
fa583f5f | 830 | */ |
f5831eea | 831 | function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktool) { |
f9b0500f | 832 | global $DB, $OUTPUT; |
fa583f5f | 833 | echo '<div class="quiz_randomquestion">'; |
834 | ||
aac80ff3 TH |
835 | if (!$category = $DB->get_record('question_categories', |
836 | array('id' => $question->category))) { | |
825116fb | 837 | echo $OUTPUT->notification('Random question category not found!'); |
fa583f5f | 838 | return; |
839 | } | |
840 | ||
841 | echo '<div class="randomquestionfromcategory">'; | |
5cc021a0 | 842 | echo print_question_icon($question); |
3cac440b | 843 | print_random_option_icon($question); |
8e84d978 | 844 | echo ' ' . get_string('randomfromcategory', 'quiz') . '</div>'; |
fa583f5f | 845 | |
0ff4bd08 | 846 | $a = new stdClass(); |
92e01ab7 | 847 | $a->arrow = $OUTPUT->rarrow(); |
2a874d65 | 848 | $strshowcategorycontents = get_string('showcategorycontents', 'quiz', $a); |
f4b879dd | 849 | |
b9bc2019 | 850 | $openqbankurl = $pageurl->out(true, array('qbanktool' => 1, |
2a874d65 | 851 | 'cat' => $category->id . ',' . $category->contextid)); |
852 | $linkcategorycontents = ' <a href="' . $openqbankurl . '">' . $strshowcategorycontents . '</a>'; | |
fa583f5f | 853 | |
2a874d65 | 854 | echo '<div class="randomquestioncategory">'; |
aac80ff3 TH |
855 | echo '<a href="' . $openqbankurl . '" title="' . $strshowcategorycontents . '">' . |
856 | $category->name . '</a>'; | |
857 | echo '<span class="questionpreview">' . | |
858 | quiz_question_preview_button($quiz, $question, true) . '</span>'; | |
2a874d65 | 859 | echo '</div>'; |
fa583f5f | 860 | |
fd214b59 | 861 | $questionids = question_bank::get_qtype('random')->get_available_questions_from_category( |
2a874d65 | 862 | $category->id, $question->questiontext == '1', '0'); |
863 | $questioncount = count($questionids); | |
fa583f5f | 864 | |
865 | echo '<div class="randomquestionqlist">'; | |
2a874d65 | 866 | if ($questioncount == 0) { |
9e83f3d1 | 867 | // No questions in category, give an error plus instructions. |
fa583f5f | 868 | echo '<span class="error">'; |
2a874d65 | 869 | print_string('noquestionsnotinuse', 'quiz'); |
9cdcf826 | 870 | echo '</span>'; |
fa583f5f | 871 | echo '<br />'; |
9cdcf826 | 872 | |
9e83f3d1 | 873 | // Embed the link into the string with instructions. |
0ff4bd08 | 874 | $a = new stdClass(); |
9cdcf826 | 875 | $a->catname = '<strong>' . $category->name . '</strong>'; |
2a874d65 | 876 | $a->link = $linkcategorycontents; |
f5831eea | 877 | echo get_string('addnewquestionsqbank', 'quiz', $a); |
537daa79 | 878 | |
2a874d65 | 879 | } else { |
9e83f3d1 | 880 | // Category has questions. |
f4b879dd | 881 | |
9e83f3d1 | 882 | // Get a sample from the database. |
fd214b59 | 883 | $questionidstoshow = array_slice($questionids, 0, NUM_QS_TO_SHOW_IN_RANDOM); |
2a874d65 | 884 | $questionstoshow = $DB->get_records_list('question', 'id', $questionidstoshow, |
25a03faa | 885 | '', 'id, qtype, name, questiontext, questiontextformat'); |
9cdcf826 | 886 | |
9e83f3d1 | 887 | // Then list them. |
2a874d65 | 888 | echo '<ul>'; |
889 | foreach ($questionstoshow as $question) { | |
890 | echo '<li>' . quiz_question_tostring($question, true) . '</li>'; | |
891 | } | |
f4b879dd | 892 | |
9e83f3d1 | 893 | // Finally display the total number. |
2a874d65 | 894 | echo '<li class="totalquestionsinrandomqcategory">'; |
895 | if ($questioncount > NUM_QS_TO_SHOW_IN_RANDOM) { | |
896 | echo '... '; | |
897 | } | |
898 | print_string('totalquestionsinrandomqcategory', 'quiz', $questioncount); | |
899 | echo ' ' . $linkcategorycontents; | |
900 | echo '</li>'; | |
901 | echo '</ul>'; | |
ee1fb969 | 902 | } |
903 | ||
2a874d65 | 904 | echo '</div>'; |
fa583f5f | 905 | echo '<div class="randomquestioncategorycount">'; |
2a874d65 | 906 | echo '</div>'; |
907 | echo '</div>'; | |
fa583f5f | 908 | } |
ee1fb969 | 909 | |
fa583f5f | 910 | /** |
911 | * Print a given single question in quiz for the reordertool tab of edit.php. | |
912 | * Meant to be used from quiz_print_question_list() | |
f5831eea | 913 | * |
414d5bfb | 914 | * @param object $question A question object from the database questions table |
915 | * @param object $questionurl The url of the question editing page as a moodle_url object | |
916 | * @param object $quiz The quiz in the context of which the question is being displayed | |
fa583f5f | 917 | */ |
f5831eea | 918 | function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) { |
068384ce | 919 | echo '<div class="singlequestion ' . $question->qtype . '">'; |
2a874d65 | 920 | echo '<label for="s' . $question->id . '">'; |
5cc021a0 | 921 | echo print_question_icon($question); |
2a874d65 | 922 | echo ' ' . quiz_question_tostring($question); |
923 | echo '</label>'; | |
924 | echo '<span class="questionpreview">' . | |
925 | quiz_question_action_icons($quiz, $quiz->cmid, $question, $returnurl) . '</span>'; | |
926 | echo "</div>\n"; | |
fa583f5f | 927 | } |
3cac440b | 928 | |
fa583f5f | 929 | /** |
930 | * Print a given random question in quiz for the reordertool tab of edit.php. | |
931 | * Meant to be used from quiz_print_question_list() | |
f5831eea | 932 | * |
414d5bfb | 933 | * @param object $question A question object from the database questions table |
934 | * @param object $questionurl The url of the question editing page as a moodle_url object | |
935 | * @param object $quiz The quiz in the context of which the question is being displayed | |
fa583f5f | 936 | */ |
f5831eea | 937 | function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) { |
f9b0500f | 938 | global $DB, $OUTPUT; |
fa583f5f | 939 | |
2a874d65 | 940 | // Load the category, and the number of available questions in it. |
fa583f5f | 941 | if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) { |
825116fb | 942 | echo $OUTPUT->notification('Random question category not found!'); |
fa583f5f | 943 | return; |
944 | } | |
aac80ff3 TH |
945 | $questioncount = count(question_bank::get_qtype( |
946 | 'random')->get_available_questions_from_category( | |
2a874d65 | 947 | $category->id, $question->questiontext == '1', '0')); |
fa583f5f | 948 | |
f5831eea | 949 | $reordercheckboxlabel = '<label for="s' . $question->id . '">'; |
2a874d65 | 950 | $reordercheckboxlabelclose = '</label>'; |
951 | ||
952 | echo '<div class="quiz_randomquestion">'; | |
953 | echo '<div class="randomquestionfromcategory">'; | |
fa583f5f | 954 | echo $reordercheckboxlabel; |
5cc021a0 | 955 | echo print_question_icon($question); |
3cac440b | 956 | print_random_option_icon($question); |
fa583f5f | 957 | |
2a874d65 | 958 | if ($questioncount == 0) { |
fa583f5f | 959 | echo '<span class="error">'; |
2a874d65 | 960 | print_string('empty', 'quiz'); |
fa583f5f | 961 | echo '</span> '; |
962 | } | |
963 | ||
2a874d65 | 964 | print_string('random', 'quiz'); |
fa583f5f | 965 | echo ": $reordercheckboxlabelclose</div>"; |
966 | ||
967 | echo '<div class="randomquestioncategory">'; | |
2a874d65 | 968 | echo $reordercheckboxlabel . $category->name . $reordercheckboxlabelclose; |
3814fb96 | 969 | echo '<span class="questionpreview">'; |
2a874d65 | 970 | echo quiz_question_preview_button($quiz, $question, false); |
fa583f5f | 971 | echo '</span>'; |
972 | echo "</div>"; | |
973 | ||
fa583f5f | 974 | echo '<div class="randomquestioncategorycount">'; |
2a874d65 | 975 | echo '</div>'; |
976 | echo '</div>'; | |
fa583f5f | 977 | } |
3cac440b | 978 | |
979 | /** | |
980 | * Print an icon to indicate the 'include subcategories' state of a random question. | |
981 | * @param $question the random question. | |
982 | */ | |
983 | function print_random_option_icon($question) { | |
5d3b9994 | 984 | global $OUTPUT; |
3cac440b | 985 | if (!empty($question->questiontext)) { |
986 | $icon = 'withsubcat'; | |
987 | $tooltip = get_string('randomwithsubcat', 'quiz'); | |
988 | } else { | |
989 | $icon = 'nosubcat'; | |
990 | $tooltip = get_string('randomnosubcat', 'quiz'); | |
991 | } | |
b5d0cafc | 992 | echo '<img src="' . $OUTPUT->pix_url('i/' . $icon) . '" alt="' . |
8e84d978 | 993 | $tooltip . '" title="' . $tooltip . '" class="uihint" />'; |
3cac440b | 994 | } |
995 | ||
fa583f5f | 996 | /** |
997 | * Creates a textual representation of a question for display. | |
f5831eea | 998 | * |
414d5bfb | 999 | * @param object $question A question object from the database questions table |
f7970e3c TH |
1000 | * @param bool $showicon If true, show the question's icon with the question. False by default. |
1001 | * @param bool $showquestiontext If true (default), show question text after question name. | |
414d5bfb | 1002 | * If false, show only question name. |
f7970e3c | 1003 | * @param bool $return If true (default), return the output. If false, print it. |
fa583f5f | 1004 | */ |
aac80ff3 TH |
1005 | function quiz_question_tostring($question, $showicon = false, |
1006 | $showquestiontext = true, $return = true) { | |
f9b0500f TH |
1007 | global $COURSE; |
1008 | $result = ''; | |
1009 | $result .= '<span class="questionname">'; | |
1010 | if ($showicon) { | |
1011 | $result .= print_question_icon($question, true); | |
1012 | echo ' '; | |
1013 | } | |
1014 | $result .= shorten_text(format_string($question->name), 200) . '</span>'; | |
1015 | if ($showquestiontext) { | |
0ff4bd08 | 1016 | $formatoptions = new stdClass(); |
f9b0500f TH |
1017 | $formatoptions->noclean = true; |
1018 | $formatoptions->para = false; | |
1019 | $questiontext = strip_tags(format_text($question->questiontext, | |
1020 | $question->questiontextformat, | |
1021 | $formatoptions, $COURSE->id)); | |
1022 | $questiontext = shorten_text($questiontext, 200); | |
1023 | $result .= '<span class="questiontext">'; | |
1024 | if (!empty($questiontext)) { | |
1025 | $result .= $questiontext; | |
f5831eea | 1026 | } else { |
f9b0500f TH |
1027 | $result .= '<span class="error">'; |
1028 | $result .= get_string('questiontextisempty', 'quiz'); | |
1029 | $result .= '</span>'; | |
fa583f5f | 1030 | } |
f9b0500f TH |
1031 | $result .= '</span>'; |
1032 | } | |
1033 | if ($return) { | |
1034 | return $result; | |
1035 | } else { | |
1036 | echo $result; | |
1037 | } | |
fa583f5f | 1038 | } |
96241873 | 1039 | |
971c6fca | 1040 | /** |
1041 | * A column type for the add this question to the quiz. | |
f7970e3c TH |
1042 | * |
1043 | * @copyright 2009 Tim Hunt | |
1044 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
971c6fca | 1045 | */ |
1046 | class question_bank_add_to_quiz_action_column extends question_bank_action_column_base { | |
1047 | protected $stradd; | |
1048 | ||
1049 | public function init() { | |
1050 | parent::init(); | |
1051 | $this->stradd = get_string('addtoquiz', 'quiz'); | |
1052 | } | |
1053 | ||
1054 | public function get_name() { | |
1055 | return 'addtoquizaction'; | |
1056 | } | |
1057 | ||
1058 | protected function display_content($question, $rowclasses) { | |
76cf77e4 TH |
1059 | if (!question_has_capability_on($question, 'use')) { |
1060 | return; | |
1061 | } | |
9e83f3d1 | 1062 | // For RTL languages: switch right and left arrows. |
971c6fca | 1063 | if (right_to_left()) { |
453b28d8 | 1064 | $movearrow = 't/removeright'; |
971c6fca | 1065 | } else { |
453b28d8 | 1066 | $movearrow = 't/moveleft'; |
971c6fca | 1067 | } |
1068 | $this->print_icon($movearrow, $this->stradd, $this->qbank->add_to_quiz_url($question->id)); | |
1069 | } | |
1070 | ||
1071 | public function get_required_fields() { | |
1072 | return array('q.id'); | |
1073 | } | |
1074 | } | |
1075 | ||
1076 | /** | |
1077 | * A column type for the name followed by the start of the question text. | |
f7970e3c TH |
1078 | * |
1079 | * @copyright 2009 Tim Hunt | |
1080 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
971c6fca | 1081 | */ |
1082 | class question_bank_question_name_text_column extends question_bank_question_name_column { | |
1083 | public function get_name() { | |
1084 | return 'questionnametext'; | |
1085 | } | |
1086 | ||
1087 | protected function display_content($question, $rowclasses) { | |
1088 | echo '<div>'; | |
1089 | $labelfor = $this->label_for($question); | |
1090 | if ($labelfor) { | |
1091 | echo '<label for="' . $labelfor . '">'; | |
1092 | } | |
1093 | echo quiz_question_tostring($question, false, true, true); | |
1094 | if ($labelfor) { | |
1095 | echo '</label>'; | |
1096 | } | |
1097 | echo '</div>'; | |
1098 | } | |
1099 | ||
1100 | public function get_required_fields() { | |
1101 | $fields = parent::get_required_fields(); | |
1102 | $fields[] = 'q.questiontext'; | |
e4d1d5e0 | 1103 | $fields[] = 'q.questiontextformat'; |
971c6fca | 1104 | return $fields; |
1105 | } | |
1106 | } | |
1107 | ||
f4b879dd | 1108 | /** |
1109 | * Subclass to customise the view of the question bank for the quiz editing screen. | |
f7970e3c TH |
1110 | * |
1111 | * @copyright 2009 Tim Hunt | |
1112 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
f4b879dd | 1113 | */ |
1114 | class quiz_question_bank_view extends question_bank_view { | |
1115 | protected $quizhasattempts = false; | |
612106b3 TH |
1116 | /** @var object the quiz settings. */ |
1117 | protected $quiz = false; | |
1118 | ||
1119 | /** | |
1120 | * Constructor | |
1121 | * @param question_edit_contexts $contexts | |
1122 | * @param moodle_url $pageurl | |
1123 | * @param object $course course settings | |
1124 | * @param object $cm activity settings. | |
1125 | * @param object $quiz quiz settings. | |
1126 | */ | |
1127 | public function __construct($contexts, $pageurl, $course, $cm, $quiz) { | |
1128 | parent::__construct($contexts, $pageurl, $course, $cm); | |
1129 | $this->quiz = $quiz; | |
1130 | } | |
96241873 | 1131 | |
cd120b23 | 1132 | protected function known_field_types() { |
1133 | $types = parent::known_field_types(); | |
971c6fca | 1134 | $types[] = new question_bank_add_to_quiz_action_column($this); |
1135 | $types[] = new question_bank_question_name_text_column($this); | |
1136 | return $types; | |
1137 | } | |
1138 | ||
1139 | protected function wanted_columns() { | |
aac80ff3 TH |
1140 | return array('addtoquizaction', 'checkbox', 'qtype', 'questionnametext', |
1141 | 'editaction', 'previewaction'); | |
f4b879dd | 1142 | } |
fa583f5f | 1143 | |
f262c4cb | 1144 | /** |
d2c9169a RW |
1145 | * Specify the column heading |
1146 | * | |
1147 | * @return string Column name for the heading | |
1148 | */ | |
1149 | protected function heading_column() { | |
1150 | return 'questionnametext'; | |
1151 | } | |
1152 | ||
29ded2d6 | 1153 | protected function default_sort() { |
ad3b37cf TH |
1154 | $this->requiredcolumns['qtype'] = $this->knowncolumntypes['qtype']; |
1155 | $this->requiredcolumns['questionnametext'] = $this->knowncolumntypes['questionnametext']; | |
29ded2d6 TH |
1156 | return array('qtype' => 1, 'questionnametext' => 1); |
1157 | } | |
1158 | ||
f4b879dd | 1159 | /** |
1160 | * Let the question bank display know whether the quiz has been attempted, | |
1161 | * hence whether some bits of UI, like the add this question to the quiz icon, | |
1162 | * should be displayed. | |
f7970e3c | 1163 | * @param bool $quizhasattempts whether the quiz has attempts. |
f4b879dd | 1164 | */ |
1165 | public function set_quiz_has_attempts($quizhasattempts) { | |
1166 | $this->quizhasattempts = $quizhasattempts; | |
971c6fca | 1167 | if ($quizhasattempts && isset($this->visiblecolumns['addtoquizaction'])) { |
1168 | unset($this->visiblecolumns['addtoquizaction']); | |
1169 | } | |
1170 | } | |
1171 | ||
612106b3 TH |
1172 | public function preview_question_url($question) { |
1173 | return quiz_question_preview_url($this->quiz, $question); | |
e4d1d5e0 | 1174 | } |
1175 | ||
971c6fca | 1176 | public function add_to_quiz_url($questionid) { |
1177 | global $CFG; | |
8afba50b PS |
1178 | $params = $this->baseurl->params(); |
1179 | $params['addquestion'] = $questionid; | |
1180 | $params['sesskey'] = sesskey(); | |
1181 | return new moodle_url('/mod/quiz/edit.php', $params); | |
f4b879dd | 1182 | } |
fa583f5f | 1183 | |
b72b4137 TH |
1184 | public function display($tabname, $page, $perpage, $cat, |
1185 | $recurse, $showhidden, $showquestiontext) { | |
3b1d5cc4 | 1186 | global $OUTPUT; |
5bd22790 | 1187 | if ($this->process_actions_needing_ui()) { |
5b5e5ab2 | 1188 | return; |
1189 | } | |
f4b879dd | 1190 | |
971c6fca | 1191 | // Display the current category. |
1192 | if (!$category = $this->get_current_category($cat)) { | |
5b5e5ab2 | 1193 | return; |
fa583f5f | 1194 | } |
971c6fca | 1195 | $this->print_category_info($category); |
5b5e5ab2 | 1196 | |
3b1d5cc4 | 1197 | echo $OUTPUT->box_start('generalbox questionbank'); |
5b5e5ab2 | 1198 | |
5bd22790 | 1199 | $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname), |
971c6fca | 1200 | $this->baseurl, $cat); |
5b5e5ab2 | 1201 | |
9e83f3d1 | 1202 | // Continues with list of questions. |
aac80ff3 TH |
1203 | $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname), |
1204 | $this->baseurl, $cat, $this->cm, $recurse, $page, | |
b72b4137 | 1205 | $perpage, $showhidden, $showquestiontext, |
5bd22790 | 1206 | $this->contexts->having_cap('moodle/question:add')); |
5b5e5ab2 | 1207 | |
971c6fca | 1208 | $this->display_options($recurse, $showhidden, $showquestiontext); |
3b1d5cc4 | 1209 | echo $OUTPUT->box_end(); |
f4b879dd | 1210 | } |
5b5e5ab2 | 1211 | |
971c6fca | 1212 | protected function print_choose_category_message($categoryandcontext) { |
3b1d5cc4 | 1213 | global $OUTPUT; |
1214 | echo $OUTPUT->box_start('generalbox questionbank'); | |
aac80ff3 TH |
1215 | $this->display_category_form($this->contexts->having_one_edit_tab_cap('edit'), |
1216 | $this->baseurl, $categoryandcontext); | |
971c6fca | 1217 | echo "<p style=\"text-align:center;\"><b>"; |
0030db53 | 1218 | print_string('selectcategoryabove', 'question'); |
971c6fca | 1219 | echo "</b></p>"; |
3b1d5cc4 | 1220 | echo $OUTPUT->box_end(); |
5bd22790 | 1221 | } |
1222 | ||
971c6fca | 1223 | protected function print_category_info($category) { |
0ff4bd08 | 1224 | $formatoptions = new stdClass(); |
971c6fca | 1225 | $formatoptions->noclean = true; |
1226 | $strcategory = get_string('category', 'quiz'); | |
f5831eea | 1227 | echo '<div class="categoryinfo"><div class="categorynamefieldcontainer">' . |
971c6fca | 1228 | $strcategory; |
1229 | echo ': <span class="categorynamefield">'; | |
22cebed5 | 1230 | echo shorten_text(strip_tags(format_string($category->name)), 60); |
aac80ff3 TH |
1231 | echo '</span></div><div class="categoryinfofieldcontainer">' . |
1232 | '<span class="categoryinfofield">'; | |
22cebed5 | 1233 | echo shorten_text(strip_tags(format_text($category->info, $category->infoformat, |
f5831eea | 1234 | $formatoptions, $this->course->id)), 200); |
971c6fca | 1235 | echo '</span></div></div>'; |
5bd22790 | 1236 | } |
1237 | ||
92e6e128 | 1238 | protected function display_options($recurse, $showhidden, $showquestiontext) { |
971c6fca | 1239 | echo '<form method="get" action="edit.php" id="displayoptions">'; |
1240 | echo "<fieldset class='invisiblefieldset'>"; | |
aac80ff3 | 1241 | echo html_writer::input_hidden_params($this->baseurl, |
e20ad67d | 1242 | array('recurse', 'showhidden', 'qbshowtext')); |
92e6e128 | 1243 | $this->display_category_form_checkbox('recurse', $recurse, |
aac80ff3 | 1244 | get_string('includesubcategories', 'question')); |
92e6e128 | 1245 | $this->display_category_form_checkbox('showhidden', $showhidden, |
aac80ff3 TH |
1246 | get_string('showhidden', 'question')); |
1247 | echo '<noscript><div class="centerpara"><input type="submit" value="' . | |
1248 | get_string('go') . '" />'; | |
971c6fca | 1249 | echo '</div></noscript></fieldset></form>'; |
5bd22790 | 1250 | } |
8bccba71 | 1251 | } |
f4b879dd | 1252 | |
8bccba71 | 1253 | /** |
1254 | * Prints the form for setting a quiz' overall grade | |
f5831eea | 1255 | * |
414d5bfb | 1256 | * @param object $quiz The quiz object of the quiz in question |
f5831eea | 1257 | * @param object $pageurl The url of the current page with the parameters required |
414d5bfb | 1258 | * for links returning to the current page, as a moodle_url object |
f7970e3c TH |
1259 | * @param int $tabindex The tabindex to start from for the form elements created |
1260 | * @return int The tabindex from which the calling page can continue, that is, | |
414d5bfb | 1261 | * the last value used +1. |
8bccba71 | 1262 | */ |
f5831eea | 1263 | function quiz_print_grading_form($quiz, $pageurl, $tabindex) { |
cef18275 | 1264 | global $OUTPUT; |
f5831eea | 1265 | $strsave = get_string('save', 'quiz'); |
fd214b59 | 1266 | echo '<form method="post" action="edit.php" class="quizsavegradesform"><div>'; |
8bccba71 | 1267 | echo '<fieldset class="invisiblefieldset" style="display: block;">'; |
f5831eea | 1268 | echo "<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\" />"; |
6ea66ff3 | 1269 | echo html_writer::input_hidden_params($pageurl); |
aac80ff3 TH |
1270 | $a = '<input type="text" id="inputmaxgrade" name="maxgrade" size="' . |
1271 | ($quiz->decimalpoints + 2) . '" tabindex="' . $tabindex | |
f5831eea | 1272 | . '" value="' . quiz_format_grade($quiz, $quiz->grade) . '" />'; |
1273 | echo '<label for="inputmaxgrade">' . get_string('maximumgradex', '', $a) . "</label>"; | |
1274 | echo '<input type="hidden" name="savechanges" value="save" />'; | |
1275 | echo '<input type="submit" value="' . $strsave . '" />'; | |
8bccba71 | 1276 | echo '</fieldset>'; |
1277 | echo "</div></form>\n"; | |
f5831eea | 1278 | return $tabindex + 1; |
8bccba71 | 1279 | } |
f4b879dd | 1280 | |
8bccba71 | 1281 | /** |
1282 | * Print the status bar | |
1283 | * | |
414d5bfb | 1284 | * @param object $quiz The quiz object of the quiz in question |
8bccba71 | 1285 | */ |
f5831eea | 1286 | function quiz_print_status_bar($quiz) { |
8bccba71 | 1287 | global $CFG; |
c5da295b TH |
1288 | |
1289 | $bits = array(); | |
1290 | ||
1291 | $bits[] = html_writer::tag('span', | |
5127e52d | 1292 | get_string('totalmarksx', 'quiz', quiz_format_grade($quiz, $quiz->sumgrades)), |
c5da295b TH |
1293 | array('class' => 'totalpoints')); |
1294 | ||
1295 | $bits[] = html_writer::tag('span', | |
1296 | get_string('numquestionsx', 'quiz', quiz_number_of_questions_in_quiz($quiz->questions)), | |
1297 | array('class' => 'numberofquestions')); | |
1298 | ||
e476804c | 1299 | $timenow = time(); |
c5da295b TH |
1300 | |
1301 | // Exact open and close dates for the tool-tip. | |
1302 | $dates = array(); | |
e476804c | 1303 | if ($quiz->timeopen > 0) { |
1304 | if ($timenow > $quiz->timeopen) { | |
940b5fbb | 1305 | $dates[] = get_string('quizopenedon', 'quiz', userdate($quiz->timeopen)); |
e476804c | 1306 | } else { |
940b5fbb | 1307 | $dates[] = get_string('quizwillopen', 'quiz', userdate($quiz->timeopen)); |
e476804c | 1308 | } |
1309 | } | |
1310 | if ($quiz->timeclose > 0) { | |
1311 | if ($timenow > $quiz->timeclose) { | |
940b5fbb | 1312 | $dates[] = get_string('quizclosed', 'quiz', userdate($quiz->timeclose)); |
e476804c | 1313 | } else { |
940b5fbb | 1314 | $dates[] = get_string('quizcloseson', 'quiz', userdate($quiz->timeclose)); |
e476804c | 1315 | } |
1316 | } | |
1317 | if (empty($dates)) { | |
1318 | $dates[] = get_string('alwaysavailable', 'quiz'); | |
1319 | } | |
c5da295b TH |
1320 | $tooltip = implode(', ', $dates);; |
1321 | ||
1322 | // Brief summary on the page. | |
1323 | if ($timenow < $quiz->timeopen) { | |
1324 | $currentstatus = get_string('quizisclosedwillopen', 'quiz', | |
72bbb091 | 1325 | userdate($quiz->timeopen, get_string('strftimedatetimeshort', 'langconfig'))); |
c5da295b TH |
1326 | } else if ($quiz->timeclose && $timenow <= $quiz->timeclose) { |
1327 | $currentstatus = get_string('quizisopenwillclose', 'quiz', | |
1328 | userdate($quiz->timeclose, get_string('strftimedatetimeshort', 'langconfig'))); | |
1329 | } else if ($quiz->timeclose && $timenow > $quiz->timeclose) { | |
1330 | $currentstatus = get_string('quizisclosed', 'quiz'); | |
1331 | } else { | |
1332 | $currentstatus = get_string('quizisopen', 'quiz'); | |
1333 | } | |
940b5fbb | 1334 | |
c5da295b TH |
1335 | $bits[] = html_writer::tag('span', $currentstatus, |
1336 | array('class' => 'quizopeningstatus', 'title' => implode(', ', $dates))); | |
1337 | ||
1338 | echo html_writer::tag('div', implode(' | ', $bits), array('class' => 'statusbar')); | |
8bccba71 | 1339 | } |