curl now mandatory
[moodle.git] / mod / quiz / lib.php
CommitLineData
8cc86111 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
84e628a0 17
ee1fb969 18/**
84e628a0 19 * Library of functions for the quiz module.
20 *
21 * This contains functions that are called also from outside the quiz module
22 * Functions that are only called by the quiz module itself are in {@link locallib.php}
23 *
8cc86111 24 * @package mod-quiz
25 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
84e628a0 27 */
730fd187 28
8cc86111 29/** Require {@link eventslib.php} */
84e628a0 30require_once($CFG->libdir . '/eventslib.php');
f81a8247
SH
31/** Require {@link calendar/lib.php} */
32require_once($CFG->dirroot . '/calendar/lib.php');
8966a111 33
75cd257b 34/// CONSTANTS ///////////////////////////////////////////////////////////////////
35
e2249afe 36/**#@+
37 * Options determining how the grades from individual attempts are combined to give
38 * the overall grade for a user
39 */
84e628a0 40define('QUIZ_GRADEHIGHEST', 1);
41define('QUIZ_GRADEAVERAGE', 2);
42define('QUIZ_ATTEMPTFIRST', 3);
43define('QUIZ_ATTEMPTLAST', 4);
e2249afe 44/**#@-*/
45
84e628a0 46define('QUIZ_MAX_ATTEMPT_OPTION', 10);
47define('QUIZ_MAX_QPP_OPTION', 50);
48define('QUIZ_MAX_DECIMAL_OPTION', 5);
49define('QUIZ_MAX_Q_DECIMAL_OPTION', 7);
50
75cd257b 51/**#@+
52 * The different review options are stored in the bits of $quiz->review
53 * These constants help to extract the options
b159da78 54 *
00719c02 55 * This is more of a mess than you might think necessary, because originally
56 * it was though that 3x6 bits were enough, but then they ran out. PHP integers
57 * are only reliably 32 bits signed, so the simplest solution was then to
b159da78 58 * add 4x3 more bits.
75cd257b 59 */
60/**
00719c02 61 * The first 6 + 4 bits refer to the time immediately after the attempt
75cd257b 62 */
00719c02 63define('QUIZ_REVIEW_IMMEDIATELY', 0x3c003f);
75cd257b 64/**
00719c02 65 * the next 6 + 4 bits refer to the time after the attempt but while the quiz is open
75cd257b 66 */
00719c02 67define('QUIZ_REVIEW_OPEN', 0x3c00fc0);
75cd257b 68/**
00719c02 69 * the final 6 + 4 bits refer to the time after the quiz closes
75cd257b 70 */
00719c02 71define('QUIZ_REVIEW_CLOSED', 0x3c03f000);
75cd257b 72
73// within each group of 6 bits we determine what should be shown
00719c02 74define('QUIZ_REVIEW_RESPONSES', 1*0x1041); // Show responses
75define('QUIZ_REVIEW_SCORES', 2*0x1041); // Show scores
76define('QUIZ_REVIEW_FEEDBACK', 4*0x1041); // Show question feedback
77define('QUIZ_REVIEW_ANSWERS', 8*0x1041); // Show correct answers
75cd257b 78// Some handling of worked solutions is already in the code but not yet fully supported
79// and not switched on in the user interface.
00719c02 80define('QUIZ_REVIEW_SOLUTIONS', 16*0x1041); // Show solutions
81define('QUIZ_REVIEW_GENERALFEEDBACK',32*0x1041); // Show question general feedback
82define('QUIZ_REVIEW_OVERALLFEEDBACK', 1*0x4440000); // Show quiz overall feedback
83// Multipliers 2*0x4440000, 4*0x4440000 and 8*0x4440000 are still available
75cd257b 84/**#@-*/
85
86/**
87 * If start and end date for the quiz are more than this many seconds apart
88 * they will be represented by two separate events in the calendar
89 */
00719c02 90define("QUIZ_MAX_EVENT_LENGTH", 5*24*60*60); // 5 days maximum
ee1fb969 91
a5e1f35c 92/// FUNCTIONS ///////////////////////////////////////////////////////////////////
730fd187 93
920b93d1 94/**
95 * Given an object containing all the necessary data,
7cac0c4b 96 * (defined by the form in mod_form.php) this function
920b93d1 97 * will create a new instance and return the id number
98 * of the new instance.
a23f0aaf 99 *
8cc86111 100 * @global object
920b93d1 101 * @param object $quiz the data that came from the form.
212b7b8c 102 * @return mixed the id of the new instance on success,
103 * false or a string error message on failure.
920b93d1 104 */
730fd187 105function quiz_add_instance($quiz) {
c18269c7 106 global $DB;
730fd187 107
920b93d1 108 // Process the options from the form.
109 $quiz->created = time();
bc569413 110 $quiz->questions = '';
212b7b8c 111 $result = quiz_process_options($quiz);
112 if ($result && is_string($result)) {
113 return $result;
114 }
6f797013 115
920b93d1 116 // Try to store it in the database.
eeab18f0 117 $quiz->id = $DB->insert_record('quiz', $quiz);
7bd1aa1d 118
920b93d1 119 // Do the processing required after an add or an update.
120 quiz_after_add_or_update($quiz);
a23f0aaf 121
7bd1aa1d 122 return $quiz->id;
730fd187 123}
124
920b93d1 125/**
126 * Given an object containing all the necessary data,
7cac0c4b 127 * (defined by the form in mod_form.php) this function
920b93d1 128 * will update an existing instance with new data.
a23f0aaf 129 *
8cc86111 130 * @global stdClass
131 * @global object
920b93d1 132 * @param object $quiz the data that came from the form.
212b7b8c 133 * @return mixed true on success, false or a string error message on failure.
920b93d1 134 */
eeab18f0 135function quiz_update_instance($quiz, $mform) {
136 global $CFG, $DB;
730fd187 137
920b93d1 138 // Process the options from the form.
212b7b8c 139 $result = quiz_process_options($quiz);
140 if ($result && is_string($result)) {
141 return $result;
142 }
ee1fb969 143
eeab18f0 144 // Repaginate, if asked to.
145 if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) {
146 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
147 $quiz->questions = $DB->get_field('quiz', 'questions', array('id' => $quiz->instance));
148 $quiz->questions = quiz_repaginate($quiz->questions, $quiz->questionsperpage);
149 }
150 unset($quiz->repaginatenow);
151
920b93d1 152 // Update the database.
730fd187 153 $quiz->id = $quiz->instance;
eeab18f0 154 $DB->update_record('quiz', $quiz);
730fd187 155
920b93d1 156 // Do the processing required after an add or an update.
157 quiz_after_add_or_update($quiz);
ee1fb969 158
920b93d1 159 // Delete any previous preview attempts
53004e48 160 quiz_delete_previews($quiz);
d2f308c0 161
7bd1aa1d 162 return true;
730fd187 163}
164
8cc86111 165/**
166 * Given an ID of an instance of this module,
167 * this function will permanently delete the instance
168 * and any data that depends on it.
169 *
170 * @global object
171 * @param int $id
172 * @return bool
173 */
730fd187 174function quiz_delete_instance($id) {
c18269c7 175 global $DB;
730fd187 176
53004e48 177 if (!$quiz = $DB->get_record('quiz', array('id' => $id))) {
730fd187 178 return false;
179 }
180
53004e48 181 quiz_delete_all_attempts($quiz);
990650f9 182 quiz_delete_all_overrides($quiz);
730fd187 183
53004e48 184 $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id));
185 $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
730fd187 186
53004e48 187 $events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id));
188 foreach($events as $event) {
f81a8247
SH
189 $event = calendar_event::load($event);
190 $event->delete();
b2a3cd2d 191 }
192
d6dd2108 193 quiz_grade_item_delete($quiz);
53004e48 194 $DB->delete_records('quiz', array('id' => $quiz->id));
d6dd2108 195
53004e48 196 return true;
197}
198
990650f9
TH
199/**
200 * Deletes a quiz override from the database and clears any corresponding calendar events
201 *
202 * @param object $quiz The quiz object.
203 * @param integer $overrideid The id of the override being deleted
204 * @return bool true on success
205 */
206function quiz_delete_override($quiz, $overrideid) {
207 global $DB;
208
209 if (!$override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) {
210 return false;
211 }
212 $groupid = empty($override->groupid)? 0 : $override->groupid;
213 $userid = empty($override->userid)? 0 : $override->userid;
214
215 // Delete the events
216 $events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id, 'groupid'=>$groupid, 'userid'=>$userid));
217 foreach($events as $event) {
218 $eventold = calendar_event::load($event);
219 $eventold->delete();
220 }
221
222 $DB->delete_records('quiz_overrides', array('id' => $overrideid));
223 return true;
224}
225
226/**
227 * Deletes all quiz overrides from the database and clears any corresponding calendar events
228 *
229 * @param object $quiz The quiz object.
230 */
231function quiz_delete_all_overrides($quiz) {
232 global $DB;
233
234 $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id');
235 foreach ($overrides as $override) {
236 quiz_delete_override($quiz, $override->id);
237 }
238}
239
240/**
241 * Updates a quiz object with override information for a user.
242 *
243 * Algorithm: For each quiz setting, if there is a matching user-specific override,
244 * then use that otherwise, if there are group-specific overrides, return the most
245 * lenient combination of them. If neither applies, leave the quiz setting unchanged.
246 *
247 * Special case: if there is more than one password that applies to the user, then
248 * quiz->extrapasswords will contain an array of strings giving the remaining
249 * passwords.
250 *
251 * @param object $quiz The quiz object.
252 * @param integer $userid The userid.
253 * @return object $quiz The updated quiz object.
254 */
255function quiz_update_effective_access($quiz, $userid) {
256 global $DB;
257
258 // check for user override
259 $override = $DB->get_record('quiz_overrides', array('quiz' => $quiz->id, 'userid' => $userid));
260
261 if (!$override) {
262 $override = new stdclass;
263 $override->timeopen = null;
264 $override->timeclose = null;
265 $override->timelimit = null;
266 $override->attempts = null;
267 $override->password = null;
268 }
269
270 // check for group overrides
271 $groupings = groups_get_user_groups($quiz->course, $userid);
990650f9 272
7bc488dc 273 if (!empty($groupings[0])) {
990650f9 274 // Select all overrides that apply to the User's groups
7bc488dc 275 list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0]));
990650f9
TH
276 $sql = "SELECT * FROM {quiz_overrides}
277 WHERE groupid $extra AND quiz = ?";
ac250ad5 278 $params[] = $quiz->id;
990650f9
TH
279 $records = $DB->get_records_sql($sql, $params);
280
281 // Combine the overrides
282 $opens = array();
283 $closes = array();
284 $limits = array();
285 $attempts = array();
286 $passwords = array();
287
288 foreach ($records as $gpoverride) {
289 if (isset($gpoverride->timeopen)) {
290 $opens[] = $gpoverride->timeopen;
291 }
292 if (isset($gpoverride->timeclose)) {
293 $closes[] = $gpoverride->timeclose;
294 }
295 if (isset($gpoverride->timelimit)) {
296 $limits[] = $gpoverride->timelimit;
297 }
298 if (isset($gpoverride->attempts)) {
299 $attempts[] = $gpoverride->attempts;
300 }
301 if (isset($gpoverride->password)) {
302 $passwords[] = $gpoverride->password;
303 }
304 }
305 // If there is a user override for a setting, ignore the group override
306 if (is_null($override->timeopen) && count($opens)) {
307 $override->timeopen = min($opens);
308 }
309 if (is_null($override->timeclose) && count($closes)) {
310 $override->timeclose = max($closes);
311 }
312 if (is_null($override->timelimit) && count($limits)) {
313 $override->timelimit = max($limits);
314 }
315 if (is_null($override->attempts) && count($attempts)) {
316 $override->attempts = max($attempts);
317 }
318 if (is_null($override->password) && count($passwords)) {
319 $override->password = array_shift($passwords);
320 if (count($passwords)) {
321 $override->extrapasswords = $passwords;
322 }
323 }
324
325 }
326
327 // merge with quiz defaults
328 $keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password', 'extrapasswords');
329 foreach ($keys as $key) {
330 if (isset($override->{$key})) {
331 $quiz->{$key} = $override->{$key};
332 }
333 }
334
335 return $quiz;
336}
337
53004e48 338/**
339 * Delete all the attempts belonging to a quiz.
8cc86111 340 *
341 * @global stdClass
342 * @global object
343 * @param object $quiz The quiz object.
53004e48 344 */
345function quiz_delete_all_attempts($quiz) {
346 global $CFG, $DB;
347 require_once($CFG->libdir . '/questionlib.php');
348 $attempts = $DB->get_records('quiz_attempts', array('quiz' => $quiz->id));
349 foreach ($attempts as $attempt) {
350 delete_attempt($attempt->uniqueid);
351 }
352 $DB->delete_records('quiz_attempts', array('quiz' => $quiz->id));
353 $DB->delete_records('quiz_grades', array('quiz' => $quiz->id));
730fd187 354}
355
8cc86111 356/**
357 * Return a small object with summary information about what a
358 * user has done with a given particular instance of this module
359 * Used for user activity reports.
360 * $return->time = the time they did it
361 * $return->info = a short text description
362 *
363 * @global object
364 * @param object $course
365 * @param object $user
366 * @param object $mod
367 * @param object $quiz
368 * @return object|null
369 */
730fd187 370function quiz_user_outline($course, $user, $mod, $quiz) {
1a96363a
NC
371 global $DB, $CFG;
372 require_once("$CFG->libdir/gradelib.php");
373 $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id);
374
375 if (empty($grades->items[0]->grades)) {
376 return null;
377 } else {
378 $grade = reset($grades->items[0]->grades);
5ecfab51 379 }
380
381 $result = new stdClass;
1a96363a
NC
382 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
383 $result->time = $grade->dategraded;
5ecfab51 384 return $result;
1a96363a 385}
730fd187 386
739b0711 387/**
3b1d5cc4 388 * Is this a graded quiz? If this method returns true, you can assume that
739b0711 389 * $quiz->grade and $quiz->sumgrades are non-zero (for example, if you want to
390 * divide by them).
391 *
392 * @param object $quiz a row from the quiz table.
393 * @return boolean whether this is a graded quiz.
394 */
395function quiz_has_grades($quiz) {
396 return $quiz->grade != 0 && $quiz->sumgrades != 0;
397}
398
40f6f97f 399/**
400 * Get the best current grade for a particular user in a quiz.
401 *
2b38499d 402 * @param object $quiz the quiz settings.
40f6f97f 403 * @param integer $userid the id of the user.
404 * @return float the user's current grade for this quiz, or NULL if this user does
405 * not have a grade on this quiz.
406 */
407function quiz_get_best_grade($quiz, $userid) {
408 global $DB;
409 $grade = $DB->get_field('quiz_grades', 'grade', array('quiz' => $quiz->id, 'userid' => $userid));
410
411 // Need to detect errors/no result, without catching 0 scores.
2b38499d
TH
412 if ($grade === false) {
413 return null;
40f6f97f 414 }
2b38499d
TH
415
416 return $grade + 0; // Convert to number.
40f6f97f 417}
418
8cc86111 419/**
420 * Print a detailed representation of what a user has done with
421 * a given particular instance of this module, for user activity reports.
422 *
423 * @global object
424 * @param object $course
425 * @param object $user
426 * @param object $mod
427 * @param object $quiz
428 * @return bool
429 */
730fd187 430function quiz_user_complete($course, $user, $mod, $quiz) {
1a14a14b 431 global $DB, $CFG, $OUTPUT;
1a96363a
NC
432 require_once("$CFG->libdir/gradelib.php");
433 $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id);
434 if (!empty($grades->items[0]->grades)) {
435 $grade = reset($grades->items[0]->grades);
436 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
437 if ($grade->str_feedback) {
438 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
439 }
440 }
730fd187 441
5ecfab51 442 if ($attempts = $DB->get_records('quiz_attempts', array('userid' => $user->id, 'quiz' => $quiz->id), 'attempt')) {
ee1fb969 443 foreach ($attempts as $attempt) {
444 echo get_string('attempt', 'quiz').' '.$attempt->attempt.': ';
445 if ($attempt->timefinish == 0) {
446 print_string('unfinished');
447 } else {
84e628a0 448 echo quiz_format_grade($quiz, $attempt->sumgrades) . '/' . quiz_format_grade($quiz, $quiz->sumgrades);
ee1fb969 449 }
450 echo ' - '.userdate($attempt->timemodified).'<br />';
451 }
452 } else {
453 print_string('noattempts', 'quiz');
454 }
455
730fd187 456 return true;
457}
458
8cc86111 459/**
460 * Function to be run periodically according to the moodle cron
461 * This function searches for things that need to be done, such
462 * as sending out mail, toggling flags etc ...
463 *
464 * @global stdClass
465 * @return bool true
466 */
be0ba083 467function quiz_cron() {
730fd187 468 global $CFG;
469
470 return true;
471}
472
b5a16eb7 473/**
8cc86111 474 * @global object
b5a16eb7 475 * @param integer $quizid the quiz id.
476 * @param integer $userid the userid.
477 * @param string $status 'all', 'finished' or 'unfinished' to control
8cc86111 478 * @param bool $includepreviews
b5a16eb7 479 * @return an array of all the user's attempts at this quiz. Returns an empty array if there are none.
480 */
98f38217 481function quiz_get_user_attempts($quizid, $userid=0, $status = 'finished', $includepreviews = false) {
9cf4a18b 482 global $DB;
b5a16eb7 483 $status_condition = array(
484 'all' => '',
485 'finished' => ' AND timefinish > 0',
486 'unfinished' => ' AND timefinish = 0'
487 );
488 $previewclause = '';
489 if (!$includepreviews) {
490 $previewclause = ' AND preview = 0';
491 }
98f38217 492 $params=array($quizid);
493 if ($userid){
494 $userclause = ' AND userid = ?';
495 $params[]=$userid;
496 } else {
497 $userclause = '';
498 }
9cf4a18b 499 if ($attempts = $DB->get_records_select('quiz_attempts',
98f38217 500 "quiz = ?" .$userclause. $previewclause . $status_condition[$status], $params,
b5a16eb7 501 'attempt ASC')) {
502 return $attempts;
503 } else {
504 return array();
505 }
506}
858deff0 507
d6dd2108 508/**
509 * Return grade for given user or all users.
510 *
8cc86111 511 * @global stdClass
512 * @global object
d6dd2108 513 * @param int $quizid id of quiz
514 * @param int $userid optional user id, 0 means all users
f88fb62c 515 * @return array array of grades, false if none. These are raw grades. They should
516 * be processed with quiz_format_grade for display.
d6dd2108 517 */
518function quiz_get_user_grades($quiz, $userid=0) {
9cf4a18b 519 global $CFG, $DB;
d6dd2108 520
9cf4a18b 521 $params = array($quiz->id);
522 $wheresql = '';
523 if ($userid) {
524 $params[] = $userid;
525 $wheresql = "AND u.id = ?";
526 }
d82b018f 527 $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade, g.timemodified AS dategraded, MAX(a.timefinish) AS datesubmitted
9cf4a18b 528 FROM {user} u, {quiz_grades} g, {quiz_attempts} a
529 WHERE u.id = g.userid AND g.quiz = ? AND a.quiz = g.quiz AND u.id = a.userid $wheresql
d82b018f 530 GROUP BY u.id, g.grade, g.timemodified";
531
9cf4a18b 532 return $DB->get_records_sql($sql, $params);
d6dd2108 533}
534
f88fb62c 535/**
536 * Round a grade to to the correct number of decimal places, and format it for display.
537 *
538 * @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
539 * @param float $grade The grade to round.
8cc86111 540 * @return float
f88fb62c 541 */
542function quiz_format_grade($quiz, $grade) {
543 return format_float($grade, $quiz->decimalpoints);
544}
545
84e628a0 546/**
547 * Round a grade to to the correct number of decimal places, and format it for display.
548 *
549 * @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
550 * @param float $grade The grade to round.
8cc86111 551 * @return float
84e628a0 552 */
553function quiz_format_question_grade($quiz, $grade) {
554 if ($quiz->questiondecimalpoints == -1) {
555 return format_float($grade, $quiz->decimalpoints);
556 } else {
557 return format_float($grade, $quiz->questiondecimalpoints);
558 }
559}
560
d6dd2108 561/**
562 * Update grades in central gradebook
563 *
8cc86111 564 * @global stdClass
565 * @global object
775f811a 566 * @param object $quiz
567 * @param int $userid specific user only, 0 means all
d6dd2108 568 */
775f811a 569function quiz_update_grades($quiz, $userid=0, $nullifnone=true) {
9cf4a18b 570 global $CFG, $DB;
775f811a 571 require_once($CFG->libdir.'/gradelib.php');
ed1daaa9 572
775f811a 573 if ($quiz->grade == 0) {
574 quiz_grade_item_update($quiz);
d6dd2108 575
775f811a 576 } else if ($grades = quiz_get_user_grades($quiz, $userid)) {
577 quiz_grade_item_update($quiz, $grades);
eafb9d9e 578
775f811a 579 } else if ($userid and $nullifnone) {
580 $grade = new object();
581 $grade->userid = $userid;
582 $grade->rawgrade = NULL;
583 quiz_grade_item_update($quiz, $grade);
d6dd2108 584
585 } else {
775f811a 586 quiz_grade_item_update($quiz);
587 }
588}
3b1d5cc4 589
775f811a 590/**
591 * Update all grades in gradebook.
8cc86111 592 *
593 * @global object
775f811a 594 */
595function quiz_upgrade_grades() {
596 global $DB;
597
598 $sql = "SELECT COUNT('x')
599 FROM {quiz} a, {course_modules} cm, {modules} m
600 WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id";
601 $count = $DB->count_records_sql($sql);
602
603 $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
604 FROM {quiz} a, {course_modules} cm, {modules} m
605 WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id";
606 if ($rs = $DB->get_recordset_sql($sql)) {
775f811a 607 $pbar = new progress_bar('quizupgradegrades', 500, true);
608 $i=0;
609 foreach ($rs as $quiz) {
610 $i++;
611 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
612 quiz_update_grades($quiz, 0, false);
613 $pbar->update($i, $count, "Updating Quiz grades ($i/$count).");
d6dd2108 614 }
775f811a 615 $rs->close();
d6dd2108 616 }
d0ac6bc2 617}
618
d6dd2108 619/**
620 * Create grade item for given quiz
621 *
8cc86111 622 * @global stdClass
623 * @uses GRADE_TYPE_VALUE
624 * @uses GRADE_TYPE_NONE
625 * @uses QUIZ_REVIEW_SCORES
626 * @uses QUIZ_REVIEW_CLOSED
627 * @uses QUIZ_REVIEW_OPEN
628 * @uses PARAM_INT
629 * @uses GRADE_UPDATE_ITEM_LOCKED
d6dd2108 630 * @param object $quiz object with extra cmidnumber
8cc86111 631 * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
d6dd2108 632 * @return int 0 if ok, error code otherwise
633 */
ced5ee59 634function quiz_grade_item_update($quiz, $grades=NULL) {
3b1d5cc4 635 global $CFG, $OUTPUT;
d6dd2108 636 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
637 require_once($CFG->libdir.'/gradelib.php');
638 }
639
640 if (array_key_exists('cmidnumber', $quiz)) { //it may not be always present
641 $params = array('itemname'=>$quiz->name, 'idnumber'=>$quiz->cmidnumber);
642 } else {
643 $params = array('itemname'=>$quiz->name);
644 }
645
646 if ($quiz->grade > 0) {
647 $params['gradetype'] = GRADE_TYPE_VALUE;
648 $params['grademax'] = $quiz->grade;
649 $params['grademin'] = 0;
650
651 } else {
652 $params['gradetype'] = GRADE_TYPE_NONE;
653 }
654
1223d24a 655/* description by TJ:
6561/ If the quiz is set to not show scores while the quiz is still open, and is set to show scores after
657 the quiz is closed, then create the grade_item with a show-after date that is the quiz close date.
6582/ If the quiz is set to not show scores at either of those times, create the grade_item as hidden.
6593/ If the quiz is set to show scores, create the grade_item visible.
660*/
661 if (!($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED)
662 and !($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN)) {
663 $params['hidden'] = 1;
664
665 } else if ( ($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED)
666 and !($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN)) {
667 if ($quiz->timeclose) {
668 $params['hidden'] = $quiz->timeclose;
669 } else {
670 $params['hidden'] = 1;
671 }
672
673 } else {
674 // a) both open and closed enabled
675 // b) open enabled, closed disabled - we can not "hide after", grades are kept visible even after closing
676 $params['hidden'] = 0;
677 }
678
0b5a80a1 679 if ($grades === 'reset') {
680 $params['reset'] = true;
681 $grades = NULL;
682 }
9cf4a18b 683
49460d84 684 $gradebook_grades = grade_get_grades($quiz->course, 'mod', 'quiz', $quiz->id);
d45459b7 685 if (!empty($gradebook_grades->items)) {
686 $grade_item = $gradebook_grades->items[0];
687 if ($grade_item->locked) {
688 $confirm_regrade = optional_param('confirm_regrade', 0, PARAM_INT);
689 if (!$confirm_regrade) {
690 $message = get_string('gradeitemislocked', 'grades');
691 $back_link = $CFG->wwwroot . '/mod/quiz/report.php?q=' . $quiz->id . '&amp;mode=overview';
692 $regrade_link = qualified_me() . '&amp;confirm_regrade=1';
3b1d5cc4 693 echo $OUTPUT->box_start('generalbox', 'notice');
d45459b7 694 echo '<p>'. $message .'</p>';
39e37019 695 echo $OUTPUT->container_start('buttons');
5c2ed7e2
PS
696 echo $OUTPUT->single_button($regrade_link, get_string('regradeanyway', 'grades'));
697 echo $OUTPUT->single_button($back_link, get_string('cancel'));
39e37019 698 echo $OUTPUT->container_end();
3b1d5cc4 699 echo $OUTPUT->box_end();
9cf4a18b 700
d45459b7 701 return GRADE_UPDATE_ITEM_LOCKED;
702 }
49460d84 703 }
704 }
0b5a80a1 705
ced5ee59 706 return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, $grades, $params);
d6dd2108 707}
708
709/**
710 * Delete grade item for given quiz
711 *
8cc86111 712 * @global stdClass
d6dd2108 713 * @param object $quiz object
714 * @return object quiz
715 */
716function quiz_grade_item_delete($quiz) {
717 global $CFG;
53004e48 718 require_once($CFG->libdir . '/gradelib.php');
d6dd2108 719
53004e48 720 return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, NULL, array('deleted' => 1));
d6dd2108 721}
722
e2249afe 723/**
724 * @return the options for calculating the quiz grade from the individual attempt grades.
725 */
726function quiz_get_grading_options() {
727 return array (
728 QUIZ_GRADEHIGHEST => get_string('gradehighest', 'quiz'),
729 QUIZ_GRADEAVERAGE => get_string('gradeaverage', 'quiz'),
730 QUIZ_ATTEMPTFIRST => get_string('attemptfirst', 'quiz'),
731 QUIZ_ATTEMPTLAST => get_string('attemptlast', 'quiz'));
732}
d6dd2108 733
8cc86111 734/**
735 * Returns an array of users who have data in a given quiz
736 *
737 * @global stdClass
738 * @global object
739 * @param int $quizid
740 * @return array
741 */
d061d883 742function quiz_get_participants($quizid) {
9cf4a18b 743 global $CFG, $DB;
d061d883 744
e4acc4ce 745 //Get users from attempts
9cf4a18b 746 $us_attempts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
747 FROM {user} u,
748 {quiz_attempts} a
749 WHERE a.quiz = ? and
750 u.id = a.userid", array($quizid));
e4acc4ce 751
e4acc4ce 752 //Return us_attempts array (it contains an array of unique users)
6b224376 753 return $us_attempts;
e4acc4ce 754
d061d883 755}
730fd187 756
8cc86111 757/**
758 * This standard function will check all instances of this module
759 * and make sure there are up-to-date events created for each of them.
760 * If courseid = 0, then every quiz event in the site is checked, else
761 * only quiz events belonging to the course specified are checked.
762 * This function is used, in its new format, by restore_refresh_events()
763 *
764 * @global object
765 * @uses QUIZ_MAX_EVENT_LENGTH
766 * @param int $courseid
767 * @return bool
768 */
d2f308c0 769function quiz_refresh_events($courseid = 0) {
9cf4a18b 770 global $DB;
d2f308c0 771
772 if ($courseid == 0) {
9cf4a18b 773 if (! $quizzes = $DB->get_records('quiz')) {
d2f308c0 774 return true;
775 }
776 } else {
9cf4a18b 777 if (! $quizzes = $DB->get_records('quiz', array('course' => $courseid))) {
d2f308c0 778 return true;
779 }
780 }
f41e824f 781
d2f308c0 782 foreach ($quizzes as $quiz) {
990650f9 783 quiz_update_events($quiz);
d2f308c0 784 }
990650f9 785
d2f308c0 786 return true;
787}
788
dd97c328 789/**
790 * Returns all quiz graded users since a given time for specified quiz
791 */
8d297188
TH
792function quiz_get_recent_mod_activity(&$activities, &$index, $timestart,
793 $courseid, $cmid, $userid = 0, $groupid = 0) {
9cf4a18b 794 global $CFG, $COURSE, $USER, $DB;
8d297188 795 require_once('locallib.php');
6710ec87 796
dd97c328 797 if ($COURSE->id == $courseid) {
798 $course = $COURSE;
6710ec87 799 } else {
9cf4a18b 800 $course = $DB->get_record('course', array('id' => $courseid));
6710ec87 801 }
6710ec87 802
dd97c328 803 $modinfo =& get_fast_modinfo($course);
6710ec87 804
dd97c328 805 $cm = $modinfo->cms[$cmid];
8d297188 806 $quiz = $DB->get_record('quiz', array('id' => $cm->instance));
9cf4a18b 807
dd97c328 808 if ($userid) {
8d297188
TH
809 $userselect = "AND u.id = :userid";
810 $params['userid'] = $userid;
dd97c328 811 } else {
8d297188 812 $userselect = '';
dd97c328 813 }
ac21ad39 814
dd97c328 815 if ($groupid) {
8d297188
TH
816 $groupselect = 'AND gm.groupid = :groupid';
817 $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id';
818 $params['groupid'] = $groupid;
dd97c328 819 } else {
8d297188
TH
820 $groupselect = '';
821 $groupjoin = '';
822 }
823
122fc5d9
DM
824 $params['timestart'] = $timestart;
825 $params['quizid'] = $quiz->id;
826
8d297188
TH
827 if (!$attempts = $DB->get_records_sql("
828 SELECT qa.*,
829 u.firstname, u.lastname, u.email, u.picture, u.imagealt
830 FROM {quiz_attempts} qa
831 JOIN {user} u ON u.id = qa.userid
832 $groupjoin
833 WHERE qa.timefinish > :timestart
834 AND qa.quiz = :quizid
835 AND qa.preview = 0
836 $userselect
837 $groupselect
838 ORDER BY qa.timefinish ASC", $params)) {
839 return;
840 }
841
842 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
8d297188
TH
843 $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
844 $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
cb323d02 845 $grader = has_capability('mod/quiz:viewreports', $context);
dd97c328 846 $groupmode = groups_get_activity_groupmode($cm, $course);
6710ec87 847
dd97c328 848 if (is_null($modinfo->groups)) {
849 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
850 }
6710ec87 851
8d297188 852 $usersgroups = null;
dd97c328 853 $aname = format_string($cm->name,true);
854 foreach ($attempts as $attempt) {
855 if ($attempt->userid != $USER->id) {
856 if (!$grader) {
8d297188 857 // Grade permission required
dd97c328 858 continue;
859 }
6710ec87 860
9cf4a18b 861 if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
8d297188
TH
862 if (is_null($usersgroups)) {
863 $usersgroups = groups_get_all_groups($course->id,
864 $attempt->userid, $cm->groupingid);
865 if (is_array($usersgroups)) {
866 $usersgroups = array_keys($usersgroups);
867 } else {
868 $usersgroups = array();
869 }
dd97c328 870 }
8d297188 871 if (!array_intersect($usersgroups, $modinfo->groups[$cm->id])) {
dd97c328 872 continue;
873 }
874 }
8d297188
TH
875 }
876
877 $options = quiz_get_reviewoptions($quiz, $attempt, $context);
dd97c328 878
8d297188 879 $tmpactivity = new stdClass;
dd97c328 880
8d297188
TH
881 $tmpactivity->type = 'quiz';
882 $tmpactivity->cmid = $cm->id;
883 $tmpactivity->name = $aname;
884 $tmpactivity->sectionnum = $cm->sectionnum;
885 $tmpactivity->timestamp = $attempt->timefinish;
9cf4a18b 886
dd97c328 887 $tmpactivity->content->attemptid = $attempt->id;
dd97c328 888 $tmpactivity->content->attempt = $attempt->attempt;
8d297188
TH
889 if (quiz_has_grades($quiz) && $options->scores) {
890 $tmpactivity->content->sumgrades = quiz_format_grade($quiz, $attempt->sumgrades);
891 $tmpactivity->content->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades);
892 } else {
893 $tmpactivity->content->sumgrades = null;
894 $tmpactivity->content->maxgrade = null;
895 }
9cf4a18b 896
8d297188
TH
897 $tmpactivity->user->id = $attempt->id;
898 $tmpactivity->user->firstname = $attempt->firstname;
899 $tmpactivity->user->lastname = $attempt->lastname;
dd97c328 900 $tmpactivity->user->fullname = fullname($attempt, $viewfullnames);
901 $tmpactivity->user->picture = $attempt->picture;
8d297188 902 $tmpactivity->user->imagealt = $attempt->imagealt;
3a11c09f 903 $tmpactivity->user->email = $attempt->email;
9cf4a18b 904
dd97c328 905 $activities[$index++] = $tmpactivity;
6710ec87 906 }
907
908 return;
909}
910
dd97c328 911function quiz_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
e63f88c9 912 global $CFG, $OUTPUT;
6710ec87 913
dd97c328 914 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
6710ec87 915
8d297188
TH
916 echo '<tr><td class="userpicture" valign="top">';
917 echo $OUTPUT->user_picture($activity->user, array('courseid' => $courseid));
918 echo '</td><td>';
6710ec87 919
920 if ($detail) {
dd97c328 921 $modname = $modnames[$activity->type];
922 echo '<div class="title">';
8d297188
TH
923 echo '<img src="' . $OUTPUT->pix_url('icon', $activity->type) . '" ' .
924 'class="icon" alt="' . $modname . '" />';
925 echo '<a href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' .
926 $activity->cmid . '">' . $activity->name . '</a>';
dd97c328 927 echo '</div>';
6710ec87 928 }
929
dd97c328 930 echo '<div class="grade">';
8d297188
TH
931 echo get_string('attempt', 'quiz', $activity->content->attempt);
932 if (isset($activity->content->maxgrade)) {
933 $grades = $activity->content->sumgrades . ' / ' . $activity->content->maxgrade;
934 echo ': (<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?attempt=' .
935 $activity->content->attemptid . '">' . $grades . '</a>)';
936 }
dd97c328 937 echo '</div>';
6710ec87 938
dd97c328 939 echo '<div class="user">';
8d297188
TH
940 echo '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $activity->user->id .
941 '&amp;course=' . $courseid . '">' . $activity->user->fullname .
942 '</a> - ' . userdate($activity->timestamp);
dd97c328 943 echo '</div>';
6710ec87 944
8d297188 945 echo '</td></tr></table>';
6710ec87 946
947 return;
948}
949
ee1fb969 950/**
920b93d1 951 * Pre-process the quiz options form data, making any necessary adjustments.
ad4cd837 952 * Called by add/update instance in this file.
b159da78 953 *
8cc86111 954 * @uses QUIZ_REVIEW_OVERALLFEEDBACK
955 * @uses QUIZ_REVIEW_CLOSED
956 * @uses QUIZ_REVIEW_OPEN
957 * @uses QUIZ_REVIEW_IMMEDIATELY
958 * @uses QUIZ_REVIEW_GENERALFEEDBACK
959 * @uses QUIZ_REVIEW_SOLUTIONS
960 * @uses QUIZ_REVIEW_ANSWERS
961 * @uses QUIZ_REVIEW_FEEDBACK
962 * @uses QUIZ_REVIEW_SCORES
963 * @uses QUIZ_REVIEW_RESPONSES
964 * @uses QUESTION_ADAPTIVE
920b93d1 965 * @param object $quiz The variables set on the form.
8cc86111 966 * @return string
920b93d1 967 */
968function quiz_process_options(&$quiz) {
969 $quiz->timemodified = time();
ee1fb969 970
dc5c6851 971 // Quiz name.
972 if (!empty($quiz->name)) {
973 $quiz->name = trim($quiz->name);
974 }
a23f0aaf 975
ab0a8ff2 976 // Password field - different in form to stop browsers that remember passwords
977 // getting confused.
978 $quiz->password = $quiz->quizpassword;
979 unset($quiz->quizpassword);
980
212b7b8c 981 // Quiz feedback
a0807a00 982 if (isset($quiz->feedbacktext)) {
983 // Clean up the boundary text.
984 for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) {
985 if (empty($quiz->feedbacktext[$i])) {
986 $quiz->feedbacktext[$i] = '';
987 } else {
988 $quiz->feedbacktext[$i] = trim($quiz->feedbacktext[$i]);
989 }
212b7b8c 990 }
b159da78 991
a0807a00 992 // Check the boundary value is a number or a percentage, and in range.
993 $i = 0;
994 while (!empty($quiz->feedbackboundaries[$i])) {
995 $boundary = trim($quiz->feedbackboundaries[$i]);
996 if (!is_numeric($boundary)) {
997 if (strlen($boundary) > 0 && $boundary[strlen($boundary) - 1] == '%') {
998 $boundary = trim(substr($boundary, 0, -1));
999 if (is_numeric($boundary)) {
1000 $boundary = $boundary * $quiz->grade / 100.0;
1001 } else {
1002 return get_string('feedbackerrorboundaryformat', 'quiz', $i + 1);
1003 }
212b7b8c 1004 }
1005 }
a0807a00 1006 if ($boundary <= 0 || $boundary >= $quiz->grade) {
1007 return get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1);
1008 }
1009 if ($i > 0 && $boundary >= $quiz->feedbackboundaries[$i - 1]) {
1010 return get_string('feedbackerrororder', 'quiz', $i + 1);
1011 }
1012 $quiz->feedbackboundaries[$i] = $boundary;
1013 $i += 1;
212b7b8c 1014 }
a0807a00 1015 $numboundaries = $i;
b159da78 1016
a0807a00 1017 // Check there is nothing in the remaining unused fields.
e0b7cfcb 1018 if (!empty($quiz->feedbackboundaries)) {
1019 for ($i = $numboundaries; $i < count($quiz->feedbackboundaries); $i += 1) {
1020 if (!empty($quiz->feedbackboundaries[$i]) && trim($quiz->feedbackboundaries[$i]) != '') {
1021 return get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1);
1022 }
a0807a00 1023 }
212b7b8c 1024 }
a0807a00 1025 for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) {
1026 if (!empty($quiz->feedbacktext[$i]) && trim($quiz->feedbacktext[$i]) != '') {
1027 return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1);
1028 }
212b7b8c 1029 }
a0807a00 1030 $quiz->feedbackboundaries[-1] = $quiz->grade + 1; // Needs to be bigger than $quiz->grade because of '<' test in quiz_feedback_for_grade().
1031 $quiz->feedbackboundaries[$numboundaries] = 0;
1032 $quiz->feedbackboundarycount = $numboundaries;
212b7b8c 1033 }
a23f0aaf 1034
920b93d1 1035 // Settings that get combined to go into the optionflags column.
1036 $quiz->optionflags = 0;
1037 if (!empty($quiz->adaptive)) {
1038 $quiz->optionflags |= QUESTION_ADAPTIVE;
1039 }
1040
1041 // Settings that get combined to go into the review column.
1042 $review = 0;
1043 if (isset($quiz->responsesimmediately)) {
ee1fb969 1044 $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_IMMEDIATELY);
920b93d1 1045 unset($quiz->responsesimmediately);
ee1fb969 1046 }
920b93d1 1047 if (isset($quiz->responsesopen)) {
ee1fb969 1048 $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_OPEN);
920b93d1 1049 unset($quiz->responsesopen);
ee1fb969 1050 }
920b93d1 1051 if (isset($quiz->responsesclosed)) {
ee1fb969 1052 $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_CLOSED);
920b93d1 1053 unset($quiz->responsesclosed);
ee1fb969 1054 }
1055
920b93d1 1056 if (isset($quiz->scoreimmediately)) {
ee1fb969 1057 $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_IMMEDIATELY);
920b93d1 1058 unset($quiz->scoreimmediately);
ee1fb969 1059 }
920b93d1 1060 if (isset($quiz->scoreopen)) {
ee1fb969 1061 $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN);
920b93d1 1062 unset($quiz->scoreopen);
ee1fb969 1063 }
920b93d1 1064 if (isset($quiz->scoreclosed)) {
ee1fb969 1065 $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED);
920b93d1 1066 unset($quiz->scoreclosed);
ee1fb969 1067 }
1068
920b93d1 1069 if (isset($quiz->feedbackimmediately)) {
ee1fb969 1070 $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY);
920b93d1 1071 unset($quiz->feedbackimmediately);
ee1fb969 1072 }
920b93d1 1073 if (isset($quiz->feedbackopen)) {
ee1fb969 1074 $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_OPEN);
920b93d1 1075 unset($quiz->feedbackopen);
ee1fb969 1076 }
920b93d1 1077 if (isset($quiz->feedbackclosed)) {
ee1fb969 1078 $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_CLOSED);
920b93d1 1079 unset($quiz->feedbackclosed);
ee1fb969 1080 }
1081
920b93d1 1082 if (isset($quiz->answersimmediately)) {
ee1fb969 1083 $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY);
920b93d1 1084 unset($quiz->answersimmediately);
ee1fb969 1085 }
920b93d1 1086 if (isset($quiz->answersopen)) {
ee1fb969 1087 $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_OPEN);
920b93d1 1088 unset($quiz->answersopen);
ee1fb969 1089 }
920b93d1 1090 if (isset($quiz->answersclosed)) {
ee1fb969 1091 $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_CLOSED);
920b93d1 1092 unset($quiz->answersclosed);
ee1fb969 1093 }
1094
920b93d1 1095 if (isset($quiz->solutionsimmediately)) {
ee1fb969 1096 $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_IMMEDIATELY);
920b93d1 1097 unset($quiz->solutionsimmediately);
ee1fb969 1098 }
920b93d1 1099 if (isset($quiz->solutionsopen)) {
ee1fb969 1100 $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_OPEN);
920b93d1 1101 unset($quiz->solutionsopen);
ee1fb969 1102 }
920b93d1 1103 if (isset($quiz->solutionsclosed)) {
ee1fb969 1104 $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_CLOSED);
920b93d1 1105 unset($quiz->solutionsclosed);
ee1fb969 1106 }
1107
a4514d91 1108 if (isset($quiz->generalfeedbackimmediately)) {
1109 $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_IMMEDIATELY);
00719c02 1110 unset($quiz->generalfeedbackimmediately);
1b8a7434 1111 }
a4514d91 1112 if (isset($quiz->generalfeedbackopen)) {
1113 $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_OPEN);
00719c02 1114 unset($quiz->generalfeedbackopen);
1b8a7434 1115 }
a4514d91 1116 if (isset($quiz->generalfeedbackclosed)) {
1117 $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_CLOSED);
00719c02 1118 unset($quiz->generalfeedbackclosed);
1119 }
1120
1121 if (isset($quiz->overallfeedbackimmediately)) {
1122 $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_IMMEDIATELY);
1123 unset($quiz->overallfeedbackimmediately);
1124 }
1125 if (isset($quiz->overallfeedbackopen)) {
1126 $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_OPEN);
1127 unset($quiz->overallfeedbackopen);
1128 }
1129 if (isset($quiz->overallfeedbackclosed)) {
1130 $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_CLOSED);
1131 unset($quiz->overallfeedbackclosed);
1b8a7434 1132 }
1133
920b93d1 1134 $quiz->review = $review;
1135}
1136
1137/**
1138 * This function is called at the end of quiz_add_instance
1139 * and quiz_update_instance, to do the common processing.
a23f0aaf 1140 *
8cc86111 1141 * @global object
1142 * @uses QUIZ_MAX_EVENT_LENGTH
920b93d1 1143 * @param object $quiz the quiz object.
8cc86111 1144 * @return void|string Void or error message
920b93d1 1145 */
1146function quiz_after_add_or_update($quiz) {
c18269c7 1147 global $DB;
920b93d1 1148
212b7b8c 1149 // Save the feedback
53004e48 1150 $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
a23f0aaf 1151
212b7b8c 1152 for ($i = 0; $i <= $quiz->feedbackboundarycount; $i += 1) {
1153 $feedback = new stdClass;
1154 $feedback->quizid = $quiz->id;
1155 $feedback->feedbacktext = $quiz->feedbacktext[$i];
1156 $feedback->mingrade = $quiz->feedbackboundaries[$i];
1157 $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1];
fc29e51b 1158 $DB->insert_record('quiz_feedback', $feedback, false);
212b7b8c 1159 }
1160
920b93d1 1161 // Update the events relating to this quiz.
990650f9
TH
1162 quiz_update_events($quiz);
1163
1164 //update related grade item
1165 quiz_grade_item_update($quiz);
1166
1167}
1168
1169/**
1170 * This function updates the events associated to the quiz.
1171 * If $override is non-zero, then it updates only the events
1172 * associated with the specified override.
1173 *
1174 * @uses QUIZ_MAX_EVENT_LENGTH
1175 * @param object $quiz the quiz object.
1176 * @param object optional $override limit to a specific override
1177 */
1178function quiz_update_events($quiz, $override = null) {
1179 global $DB;
1180
1181 // Load the old events relating to this quiz.
1182 $conds = array('modulename'=>'quiz',
1183 'instance'=>$quiz->id);
1184 if (!empty($override)) {
1185 // only load events for this override
1186 $conds['groupid'] = isset($override->groupid)? $override->groupid : 0;
1187 $conds['userid'] = isset($override->userid)? $override->userid : 0;
1188 }
1189 $oldevents = $DB->get_records('event', $conds);
1190
1191 // Now make a todo list of all that needs to be updated
1192 if (empty($override)) {
1193 // We are updating the primary settings for the quiz, so we
1194 // need to add all the overrides
1195 $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id));
1196 // as well as the original quiz (empty override)
1197 $overrides[] = new stdClass;
1198 }
1199 else {
1200 // Just do the one override
1201 $overrides = array($override);
920b93d1 1202 }
1203
990650f9
TH
1204 foreach ($overrides as $current) {
1205 $groupid = isset($current->groupid)? $current->groupid : 0;
1206 $userid = isset($current->userid)? $current->userid : 0;
1207 $timeopen = isset($current->timeopen)? $current->timeopen : $quiz->timeopen;
1208 $timeclose = isset($current->timeclose)? $current->timeclose : $quiz->timeclose;
1209
1210 // only add open/close events for an override if they differ from the quiz default
1211 $addopen = empty($current->id) || !empty($current->timeopen);
1212 $addclose = empty($current->id) || !empty($current->timeclose);
1213
1214 $event = new stdClass;
1215 $event->description = $quiz->intro;
1216 $event->courseid = ($userid) ? 0 : $quiz->course; // Events module won't show user events when the courseid is nonzero
1217 $event->groupid = $groupid;
1218 $event->userid = $userid;
1219 $event->modulename = 'quiz';
1220 $event->instance = $quiz->id;
1221 $event->timestart = $timeopen;
1222 $event->timeduration = max($timeclose - $timeopen, 0);
1223 $event->visible = instance_is_visible('quiz', $quiz);
1224 $event->eventtype = 'open';
1225
1226 // Determine the event name
1227 if ($groupid) {
1228 $params = new stdClass;
1229 $params->quiz = $quiz->name;
1230 $params->group = groups_get_group_name($groupid);
1231 if ($params->group === false) {
1232 // group doesn't exist, just skip it
1233 continue;
1234 }
1235 $eventname = get_string('overridegroupeventname', 'quiz', $params);
920b93d1 1236 }
990650f9
TH
1237 else if ($userid) {
1238 $params = new stdClass;
1239 $params->quiz = $quiz->name;
1240 $eventname = get_string('overrideusereventname', 'quiz', $params);
1241 } else {
1242 $eventname = $quiz->name;
1243 }
1244 if ($addopen or $addclose) {
1245 if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) {
1246 // Single event for the whole quiz.
1247 if ($oldevent = array_shift($oldevents)) {
1248 $event->id = $oldevent->id;
1249 }
1250 else {
1251 unset($event->id);
1252 }
1253 $event->name = $eventname;
1254 // calendar_event::create will reuse a db record if the id field is set
1255 calendar_event::create($event);
1256 } else {
1257 // Separate start and end events.
1258 $event->timeduration = 0;
1259 if ($timeopen && $addopen) {
1260 if ($oldevent = array_shift($oldevents)) {
1261 $event->id = $oldevent->id;
1262 }
1263 else {
1264 unset($event->id);
1265 }
1266 $event->name = $eventname.' ('.get_string('quizopens', 'quiz').')';
1267 // calendar_event::create will reuse a db record if the id field is set
1268 calendar_event::create($event);
1269 }
1270 if ($timeclose && $addclose) {
1271 if ($oldevent = array_shift($oldevents)) {
1272 $event->id = $oldevent->id;
1273 }
1274 else {
1275 unset($event->id);
1276 }
1277 $event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')';
1278 $event->timestart = $timeclose;
1279 $event->eventtype = 'close';
1280 calendar_event::create($event);
1281 }
1282 }
920b93d1 1283 }
1284 }
d6dd2108 1285
990650f9
TH
1286 // Delete any leftover events
1287 foreach ($oldevents as $badevent) {
1288 $badevent = calendar_event::load($badevent);
1289 $badevent->delete();
1290 }
ee1fb969 1291}
1292
8cc86111 1293/**
1294 * @return array
1295 */
f3221af9 1296function quiz_get_view_actions() {
acf149ad 1297 return array('view', 'view all', 'report', 'review');
f3221af9 1298}
ee1fb969 1299
8cc86111 1300/**
1301 * @return array
1302 */
f3221af9 1303function quiz_get_post_actions() {
acf149ad 1304 return array('attempt', 'close attempt', 'preview', 'editquestions', 'delete attempt', 'manualgrade');
f3221af9 1305}
ee1fb969 1306
f67172b6 1307/**
1308 * Returns an array of names of quizzes that use this question
1309 *
64d79492 1310 * @param integer $questionid
f67172b6 1311 * @return array of strings
1312 */
1313function quiz_question_list_instances($questionid) {
c6307ef2 1314 global $CFG, $DB;
e8666d9a 1315
64d79492 1316 // TODO MDL-5780: we should also consider other questions that are used by
e8666d9a 1317 // random questions in this quiz, but that is very hard.
1318
1319 $sql = "SELECT q.id, q.name
c6307ef2 1320 FROM {quiz} q
1321 JOIN {quiz_question_instances} qqi ON q.id = qqi.quiz
1322 WHERE qqi.question = ?";
e8666d9a 1323
c6307ef2 1324 if ($instances = $DB->get_records_sql_menu($sql, array($questionid))) {
e8666d9a 1325 return $instances;
1326 }
f67172b6 1327 return array();
1328}
1329
7a6f4066 1330/**
1331 * Implementation of the function for printing the form elements that control
1332 * whether the course reset functionality affects the quiz.
3b1d5cc4 1333 *
0b5a80a1 1334 * @param $mform form passed by reference
1335 */
1336function quiz_reset_course_form_definition(&$mform) {
c159da4c 1337 $mform->addElement('header', 'quizheader', get_string('modulenameplural', 'quiz'));
0b5a80a1 1338 $mform->addElement('advcheckbox', 'reset_quiz_attempts', get_string('removeallquizattempts','quiz'));
1339}
1340
1341/**
1342 * Course reset form defaults.
8cc86111 1343 * @return array
0b5a80a1 1344 */
1345function quiz_reset_course_form_defaults($course) {
1346 return array('reset_quiz_attempts'=>1);
1347}
1348
1349/**
1350 * Removes all grades from gradebook
8cc86111 1351 *
1352 * @global stdClass
1353 * @global object
0b5a80a1 1354 * @param int $courseid
1355 * @param string optional type
7a6f4066 1356 */
0b5a80a1 1357function quiz_reset_gradebook($courseid, $type='') {
9cf4a18b 1358 global $CFG, $DB;
0b5a80a1 1359
1360 $sql = "SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid
9cf4a18b 1361 FROM {quiz} q, {course_modules} cm, {modules} m
1362 WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=q.id AND q.course=?";
0b5a80a1 1363
9cf4a18b 1364 if ($quizs = $DB->get_records_sql($sql, array($courseid))) {
0b5a80a1 1365 foreach ($quizs as $quiz) {
1366 quiz_grade_item_update($quiz, 'reset');
1367 }
1368 }
7a6f4066 1369}
1370
1371/**
1372 * Actual implementation of the rest coures functionality, delete all the
1373 * quiz attempts for course $data->courseid, if $data->reset_quiz_attempts is
1374 * set and true.
6ef56c99 1375 *
1376 * Also, move the quiz open and close dates, if the course start date is changing.
8cc86111 1377 *
1378 * @global stdClass
1379 * @global object
1380 * @param object $data the data submitted from the reset course.
0b5a80a1 1381 * @return array status array
7a6f4066 1382 */
0b5a80a1 1383function quiz_reset_userdata($data) {
53004e48 1384 global $CFG, $DB;
1385 require_once($CFG->libdir.'/questionlib.php');
be0ba083 1386
0b5a80a1 1387 $componentstr = get_string('modulenameplural', 'quiz');
1388 $status = array();
b159da78 1389
6ef56c99 1390 /// Delete attempts.
1391 if (!empty($data->reset_quiz_attempts)) {
53004e48 1392 $quizzes = $DB->get_records('quiz', array('course' => $data->courseid));
1393 foreach ($quizzes as $quiz) {
1394 quiz_delete_all_attempts($quiz);
7a6f4066 1395 }
0b5a80a1 1396
0b5a80a1 1397 // remove all grades from gradebook
1398 if (empty($data->reset_gradebook_grades)) {
1399 quiz_reset_gradebook($data->courseid);
7a6f4066 1400 }
53004e48 1401 $status[] = array('component' => $componentstr, 'item' => get_string('attemptsdeleted', 'quiz'), 'error' => false);
7a6f4066 1402 }
6ef56c99 1403
0b5a80a1 1404 /// updating dates - shift may be negative too
1405 if ($data->timeshift) {
1406 shift_course_mod_dates('quiz', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid);
53004e48 1407 $status[] = array('component' => $componentstr, 'item' => get_string('openclosedatesupdated', 'quiz'), 'error' => false);
7a6f4066 1408 }
0b5a80a1 1409
1410 return $status;
7a6f4066 1411}
14e6dc79 1412
1413/**
1414 * Checks whether the current user is allowed to view a file uploaded in a quiz.
1415 * Teachers can view any from their courses, students can only view their own.
b159da78 1416 *
8cc86111 1417 * @global object
1418 * @global object
1419 * @uses CONTEXT_COURSE
95de57b8 1420 * @param int $attemptuniqueid int attempt id
14e6dc79 1421 * @param int $questionid int question id
b159da78 1422 * @return boolean to indicate access granted or denied
14e6dc79 1423 */
95de57b8 1424function quiz_check_file_access($attemptuniqueid, $questionid) {
9cf4a18b 1425 global $USER, $DB;
b159da78 1426
6102a59d 1427 $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid));
9cf4a18b 1428 $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz));
14e6dc79 1429 $context = get_context_instance(CONTEXT_COURSE, $quiz->course);
b159da78 1430
14e6dc79 1431 // access granted if the current user submitted this file
1432 if ($attempt->userid == $USER->id) {
1433 return true;
1434 // access granted if the current user has permission to grade quizzes in this course
1435 } else if (has_capability('mod/quiz:viewreports', $context) || has_capability('mod/quiz:grade', $context)) {
1436 return true;
1437 }
b159da78 1438
1439 // otherwise, this user does not have permission
14e6dc79 1440 return false;
1441}
b5a16eb7 1442
1443/**
1444 * Prints quiz summaries on MyMoodle Page
8cc86111 1445 *
1446 * @global object
1447 * @global object
1448 * @param arry $courses
1449 * @param array $htmlarray
b5a16eb7 1450 */
1451function quiz_print_overview($courses, &$htmlarray) {
1452 global $USER, $CFG;
1453/// These next 6 Lines are constant in all modules (just change module name)
1454 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1455 return array();
1456 }
1457
2a13e454 1458 if (!$quizzes = get_all_instances_in_courses('quiz', $courses)) {
b5a16eb7 1459 return;
1460 }
1461
1462/// Fetch some language strings outside the main loop.
1463 $strquiz = get_string('modulename', 'quiz');
1464 $strnoattempts = get_string('noattempts', 'quiz');
1465
1466/// We want to list quizzes that are currently available, and which have a close date.
1467/// This is the same as what the lesson does, and the dabate is in MDL-10568.
6c58e198 1468 $now = time();
2a13e454 1469 foreach ($quizzes as $quiz) {
b5a16eb7 1470 if ($quiz->timeclose >= $now && $quiz->timeopen < $now) {
1471 /// Give a link to the quiz, and the deadline.
1472 $str = '<div class="quiz overview">' .
1473 '<div class="name">' . $strquiz . ': <a ' . ($quiz->visible ? '' : ' class="dimmed"') .
1474 ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->coursemodule . '">' .
1475 $quiz->name . '</a></div>';
1476 $str .= '<div class="info">' . get_string('quizcloseson', 'quiz', userdate($quiz->timeclose)) . '</div>';
1477
1478 /// Now provide more information depending on the uers's role.
1479 $context = get_context_instance(CONTEXT_MODULE, $quiz->coursemodule);
1480 if (has_capability('mod/quiz:viewreports', $context)) {
1481 /// For teacher-like people, show a summary of the number of student attempts.
9cf4a18b 1482 // The $quiz objects returned by get_all_instances_in_course have the necessary $cm
2a13e454 1483 // fields set to make the following call work.
7956944f 1484 $str .= '<div class="info">' . quiz_num_attempt_summary($quiz, $quiz, true) . '</div>';
96c7d771 1485 } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $context)) { // Student
b5a16eb7 1486 /// For student-like people, tell them how many attempts they have made.
1487 if (isset($USER->id) && ($attempts = quiz_get_user_attempts($quiz->id, $USER->id))) {
1488 $numattempts = count($attempts);
9cf4a18b 1489 $str .= '<div class="info">' . get_string('numattemptsmade', 'quiz', $numattempts) . '</div>';
b5a16eb7 1490 } else {
1491 $str .= '<div class="info">' . $strnoattempts . '</div>';
1492 }
1493 } else {
1494 /// For ayone else, there is no point listing this quiz, so stop processing.
1495 continue;
1496 }
1497
1498 /// Add the output for this quiz to the rest.
1499 $str .= '</div>';
1500 if (empty($htmlarray[$quiz->course]['quiz'])) {
1501 $htmlarray[$quiz->course]['quiz'] = $str;
1502 } else {
1503 $htmlarray[$quiz->course]['quiz'] .= $str;
1504 }
1505 }
1506 }
1507}
6c58e198 1508
1509/**
1510 * Return a textual summary of the number of attemtps that have been made at a particular quiz,
1511 * returns '' if no attemtps have been made yet, unless $returnzero is passed as true.
8cc86111 1512 *
6c58e198 1513 * @param object $quiz the quiz object. Only $quiz->id is used at the moment.
2a13e454 1514 * @param object $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid fields are used at the moment.
1515 * @param boolean $returnzero if false (default), when no attempts have been made '' is returned instead of 'Attempts: 0'.
1516 * @param int $currentgroup if there is a concept of current group where this method is being called
1517 * (e.g. a report) pass it in here. Default 0 which means no current group.
1518 * @return string a string like "Attempts: 123", "Attemtps 123 (45 from your groups)" or
1519 * "Attemtps 123 (45 from this group)".
6c58e198 1520 */
2a13e454 1521function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup = 0) {
a49cb927 1522 global $DB, $USER;
9cf4a18b 1523 $numattempts = $DB->count_records('quiz_attempts', array('quiz'=> $quiz->id, 'preview'=>0));
6c58e198 1524 if ($numattempts || $returnzero) {
2a13e454 1525 if (groups_get_activity_groupmode($cm)) {
1526 $a->total = $numattempts;
1527 if ($currentgroup) {
9cf4a18b 1528 $a->group = $DB->count_records_sql('SELECT count(1) FROM ' .
1529 '{quiz_attempts} qa JOIN ' .
1530 '{groups_members} gm ON qa.userid = gm.userid ' .
1531 'WHERE quiz = ? AND preview = 0 AND groupid = ?', array($quiz->id, $currentgroup));
2a13e454 1532 return get_string('attemptsnumthisgroup', 'quiz', $a);
9cf4a18b 1533 } else if ($groups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid)) {
1534 list($usql, $params) = $DB->get_in_or_equal(array_keys($groups));
1535 $a->group = $DB->count_records_sql('SELECT count(1) FROM ' .
1536 '{quiz_attempts} qa JOIN ' .
1537 '{groups_members} gm ON qa.userid = gm.userid ' .
1538 'WHERE quiz = ? AND preview = 0 AND ' .
1539 "groupid $usql", array_merge(array($quiz->id), $params));
2a13e454 1540 return get_string('attemptsnumyourgroups', 'quiz', $a);
1541 }
1542 }
6c58e198 1543 return get_string('attemptsnum', 'quiz', $numattempts);
1544 }
1545 return '';
1546}
f432bebf 1547
4e781c7b 1548/**
a49cb927
TH
1549 * Returns the same as {@link quiz_num_attempt_summary()} but wrapped in a link
1550 * to the quiz reports.
1551 *
1552 * @param object $quiz the quiz object. Only $quiz->id is used at the moment.
1553 * @param object $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid fields are used at the moment.
1554 * @param object $context the quiz context.
1555 * @param boolean $returnzero if false (default), when no attempts have been made '' is returned instead of 'Attempts: 0'.
1556 * @param int $currentgroup if there is a concept of current group where this method is being called
1557 * (e.g. a report) pass it in here. Default 0 which means no current group.
1558 * @return string HTML fragment for the link.
1559 */
1560function quiz_attempt_summary_link_to_reports($quiz, $cm, $context, $returnzero = false, $currentgroup = 0) {
1561 global $CFG;
1562 $summary = quiz_num_attempt_summary($quiz, $cm, $returnzero, $currentgroup);
1563 if (!$summary) {
1564 return '';
1565 }
1566
1567 require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
1568 $url = new moodle_url('/mod/quiz/report.php', array(
1569 'id' => $cm->id, 'mode' => quiz_report_default_report($context)));
1570 return html_writer::link($url, $summary);
1571}
1572
1573/**
4e781c7b 1574 * @param string $feature FEATURE_xx constant for requested feature
1575 * @return bool True if quiz supports feature
1576 */
1577function quiz_supports($feature) {
1578 switch($feature) {
42f103be 1579 case FEATURE_GROUPS: return true;
1580 case FEATURE_GROUPINGS: return true;
1581 case FEATURE_GROUPMEMBERSONLY: return true;
dc5c2bd9 1582 case FEATURE_MOD_INTRO: return true;
4e781c7b 1583 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
42f103be 1584 case FEATURE_GRADE_HAS_GRADE: return true;
1585 case FEATURE_GRADE_OUTCOMES: return true;
1586
49f6e5f4 1587 default: return null;
4e781c7b 1588 }
1589}
1590
f432bebf 1591/**
8cc86111 1592 * @global object
1593 * @global stdClass
cca6e300 1594 * @return array all other caps used in module
f432bebf 1595 */
1596function quiz_get_extra_capabilities() {
be0ba083 1597 global $DB, $CFG;
1598 require_once($CFG->libdir.'/questionlib.php');
cca6e300 1599 $caps = question_get_all_capabilities();
bbf4f440 1600 $reportcaps = $DB->get_records_select_menu('capabilities', 'name LIKE ?', array('quizreport/%'), 'id,name');
1601 $caps = array_merge($caps, $reportcaps);
cca6e300 1602 $caps[] = 'moodle/site:accessallgroups';
1603 return $caps;
f432bebf 1604}
55f599f0 1605
1606/**
792881f0 1607 * This fucntion extends the global navigation for the site.
55f599f0 1608 * It is important to note that you should not rely on PAGE objects within this
1609 * body of code as there is no guarantee that during an AJAX request they are
1610 * available
1611 *
56ed242b 1612 * @param navigation_node $quiznode The quiz node within the global navigation
55f599f0 1613 * @param stdClass $course The course object returned from the DB
1614 * @param stdClass $module The module object returned from the DB
792881f0 1615 * @param stdClass $cm The course module instance returned from the DB
55f599f0 1616 */
56ed242b
SH
1617function quiz_extend_navigation($quiznode, $course, $module, $cm) {
1618 global $CFG;
1619
1620 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1621
1622 if (has_capability('mod/quiz:view', $context)) {
1623 $url = new moodle_url('/mod/quiz/view.php', array('id'=>$cm->id));
a49cb927
TH
1624 $quiznode->add(get_string('info', 'quiz'), $url, navigation_node::TYPE_SETTING,
1625 null, null, new pix_icon('i/info', ''));
56ed242b
SH
1626 }
1627
1628 if (has_capability('mod/quiz:viewreports', $context)) {
56ed242b
SH
1629 require_once($CFG->dirroot.'/mod/quiz/report/reportlib.php');
1630 $reportlist = quiz_report_list($context);
a49cb927
TH
1631
1632 $url = new moodle_url('/mod/quiz/report.php', array('id' => $cm->id, 'mode' => reset($reportlist)));
1633 $reportnode = $quiznode->add(get_string('results', 'quiz'), $url, navigation_node::TYPE_SETTING,
1634 null, null, new pix_icon('i/report', ''));
1635
56ed242b 1636 foreach ($reportlist as $report) {
a49cb927
TH
1637 $url = new moodle_url('/mod/quiz/report.php', array('id' => $cm->id, 'mode' => $report));
1638 $reportnode->add(get_string($report, 'quiz_'.$report), $url, navigation_node::TYPE_SETTING,
2a8a78c3 1639 null, 'quiz_report_' . $report, new pix_icon('i/item', ''));
56ed242b
SH
1640 }
1641 }
55f599f0 1642}
1643
1644/**
1645 * This function extends the settings navigation block for the site.
1646 *
1647 * It is safe to rely on PAGE here as we will only ever be within the module
1648 * context when this is called
1649 *
0b29477b
SH
1650 * @param settings_navigation $settings
1651 * @param navigation_node $quiznode
55f599f0 1652 */
0b29477b
SH
1653function quiz_extend_settings_navigation($settings, $quiznode) {
1654 global $PAGE, $CFG;
55f599f0 1655
56ed242b
SH
1656 /**
1657 * Require {@link questionlib.php}
1658 * Included here as we only ever want to include this file if we really need to.
1659 */
1660 require_once($CFG->libdir . '/questionlib.php');
55f599f0 1661
56ed242b 1662 if (has_capability('mod/quiz:manageoverrides', $PAGE->cm->context)) {
56ed242b 1663 $url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$PAGE->cm->id));
eb02301a
TH
1664 $quiznode->add(get_string('groupoverrides', 'quiz'), new moodle_url($url, array('mode'=>'group')),
1665 navigation_node::TYPE_SETTING, null, 'groupoverrides');
1666 $quiznode->add(get_string('useroverrides', 'quiz'), new moodle_url($url, array('mode'=>'user')),
1667 navigation_node::TYPE_SETTING, null, 'useroverrides');
55f599f0 1668 }
56ed242b 1669
55f599f0 1670 if (has_capability('mod/quiz:manage', $PAGE->cm->context)) {
a6855934 1671 $url = new moodle_url('/mod/quiz/edit.php', array('cmid'=>$PAGE->cm->id));
1724eb71 1672 $text = get_string('editquiz', 'quiz');
eb02301a
TH
1673 $quiznode->add($text, $url, navigation_node::TYPE_SETTING, null,
1674 'mod_quiz_edit', new pix_icon('t/edit', ''));
55f599f0 1675 }
56ed242b
SH
1676
1677 if (has_capability('mod/quiz:preview', $PAGE->cm->context)) {
1678 $url = new moodle_url('/mod/quiz/startattempt.php', array('cmid'=>$PAGE->cm->id, 'sesskey'=>sesskey()));
eb02301a
TH
1679 $quiznode->add(get_string('preview', 'quiz'), $url, navigation_node::TYPE_SETTING,
1680 null, 'mod_quiz_preview', new pix_icon('t/preview', ''));
55f599f0 1681 }
56ed242b 1682
2a8a78c3 1683 question_extend_settings_navigation($quiznode, $PAGE->cm->context)->trim_if_empty();
56ed242b 1684}