Commit | Line | Data |
---|---|---|
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 | 30 | require_once($CFG->libdir . '/eventslib.php'); |
f81a8247 SH |
31 | /** Require {@link calendar/lib.php} */ |
32 | require_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 | 40 | define('QUIZ_GRADEHIGHEST', 1); |
41 | define('QUIZ_GRADEAVERAGE', 2); | |
42 | define('QUIZ_ATTEMPTFIRST', 3); | |
43 | define('QUIZ_ATTEMPTLAST', 4); | |
e2249afe | 44 | /**#@-*/ |
45 | ||
84e628a0 | 46 | define('QUIZ_MAX_ATTEMPT_OPTION', 10); |
47 | define('QUIZ_MAX_QPP_OPTION', 50); | |
48 | define('QUIZ_MAX_DECIMAL_OPTION', 5); | |
49 | define('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 | 63 | define('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 | 67 | define('QUIZ_REVIEW_OPEN', 0x3c00fc0); |
75cd257b | 68 | /** |
00719c02 | 69 | * the final 6 + 4 bits refer to the time after the quiz closes |
75cd257b | 70 | */ |
00719c02 | 71 | define('QUIZ_REVIEW_CLOSED', 0x3c03f000); |
75cd257b | 72 | |
73 | // within each group of 6 bits we determine what should be shown | |
00719c02 | 74 | define('QUIZ_REVIEW_RESPONSES', 1*0x1041); // Show responses |
75 | define('QUIZ_REVIEW_SCORES', 2*0x1041); // Show scores | |
76 | define('QUIZ_REVIEW_FEEDBACK', 4*0x1041); // Show question feedback | |
77 | define('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 | 80 | define('QUIZ_REVIEW_SOLUTIONS', 16*0x1041); // Show solutions |
81 | define('QUIZ_REVIEW_GENERALFEEDBACK',32*0x1041); // Show question general feedback | |
82 | define('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 | 90 | define("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 | 105 | function 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 | 135 | function 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 | 174 | function 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); |
730fd187 | 182 | |
53004e48 | 183 | $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id)); |
184 | $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); | |
730fd187 | 185 | |
53004e48 | 186 | $events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id)); |
187 | foreach($events as $event) { | |
f81a8247 SH |
188 | $event = calendar_event::load($event); |
189 | $event->delete(); | |
b2a3cd2d | 190 | } |
191 | ||
d6dd2108 | 192 | quiz_grade_item_delete($quiz); |
53004e48 | 193 | $DB->delete_records('quiz', array('id' => $quiz->id)); |
d6dd2108 | 194 | |
53004e48 | 195 | return true; |
196 | } | |
197 | ||
198 | /** | |
199 | * Delete all the attempts belonging to a quiz. | |
8cc86111 | 200 | * |
201 | * @global stdClass | |
202 | * @global object | |
203 | * @param object $quiz The quiz object. | |
53004e48 | 204 | */ |
205 | function quiz_delete_all_attempts($quiz) { | |
206 | global $CFG, $DB; | |
207 | require_once($CFG->libdir . '/questionlib.php'); | |
208 | $attempts = $DB->get_records('quiz_attempts', array('quiz' => $quiz->id)); | |
209 | foreach ($attempts as $attempt) { | |
210 | delete_attempt($attempt->uniqueid); | |
211 | } | |
212 | $DB->delete_records('quiz_attempts', array('quiz' => $quiz->id)); | |
213 | $DB->delete_records('quiz_grades', array('quiz' => $quiz->id)); | |
730fd187 | 214 | } |
215 | ||
8cc86111 | 216 | /** |
217 | * Return a small object with summary information about what a | |
218 | * user has done with a given particular instance of this module | |
219 | * Used for user activity reports. | |
220 | * $return->time = the time they did it | |
221 | * $return->info = a short text description | |
222 | * | |
223 | * @global object | |
224 | * @param object $course | |
225 | * @param object $user | |
226 | * @param object $mod | |
227 | * @param object $quiz | |
228 | * @return object|null | |
229 | */ | |
730fd187 | 230 | function quiz_user_outline($course, $user, $mod, $quiz) { |
1a96363a NC |
231 | global $DB, $CFG; |
232 | require_once("$CFG->libdir/gradelib.php"); | |
233 | $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); | |
234 | ||
235 | if (empty($grades->items[0]->grades)) { | |
236 | return null; | |
237 | } else { | |
238 | $grade = reset($grades->items[0]->grades); | |
5ecfab51 | 239 | } |
240 | ||
241 | $result = new stdClass; | |
1a96363a NC |
242 | $result->info = get_string('grade') . ': ' . $grade->str_long_grade; |
243 | $result->time = $grade->dategraded; | |
5ecfab51 | 244 | return $result; |
1a96363a | 245 | } |
730fd187 | 246 | |
739b0711 | 247 | /** |
3b1d5cc4 | 248 | * Is this a graded quiz? If this method returns true, you can assume that |
739b0711 | 249 | * $quiz->grade and $quiz->sumgrades are non-zero (for example, if you want to |
250 | * divide by them). | |
251 | * | |
252 | * @param object $quiz a row from the quiz table. | |
253 | * @return boolean whether this is a graded quiz. | |
254 | */ | |
255 | function quiz_has_grades($quiz) { | |
256 | return $quiz->grade != 0 && $quiz->sumgrades != 0; | |
257 | } | |
258 | ||
40f6f97f | 259 | /** |
260 | * Get the best current grade for a particular user in a quiz. | |
261 | * | |
8cc86111 | 262 | * @global object |
40f6f97f | 263 | * @param object $quiz the quiz object. |
264 | * @param integer $userid the id of the user. | |
265 | * @return float the user's current grade for this quiz, or NULL if this user does | |
266 | * not have a grade on this quiz. | |
267 | */ | |
268 | function quiz_get_best_grade($quiz, $userid) { | |
269 | global $DB; | |
270 | $grade = $DB->get_field('quiz_grades', 'grade', array('quiz' => $quiz->id, 'userid' => $userid)); | |
271 | ||
272 | // Need to detect errors/no result, without catching 0 scores. | |
273 | if (is_numeric($grade)) { | |
274 | return quiz_format_grade($quiz, $grade); | |
275 | } else { | |
276 | return NULL; | |
277 | } | |
278 | } | |
279 | ||
8cc86111 | 280 | /** |
281 | * Print a detailed representation of what a user has done with | |
282 | * a given particular instance of this module, for user activity reports. | |
283 | * | |
284 | * @global object | |
285 | * @param object $course | |
286 | * @param object $user | |
287 | * @param object $mod | |
288 | * @param object $quiz | |
289 | * @return bool | |
290 | */ | |
730fd187 | 291 | function quiz_user_complete($course, $user, $mod, $quiz) { |
1a14a14b | 292 | global $DB, $CFG, $OUTPUT; |
1a96363a NC |
293 | require_once("$CFG->libdir/gradelib.php"); |
294 | $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); | |
295 | if (!empty($grades->items[0]->grades)) { | |
296 | $grade = reset($grades->items[0]->grades); | |
297 | echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade); | |
298 | if ($grade->str_feedback) { | |
299 | echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback); | |
300 | } | |
301 | } | |
730fd187 | 302 | |
5ecfab51 | 303 | if ($attempts = $DB->get_records('quiz_attempts', array('userid' => $user->id, 'quiz' => $quiz->id), 'attempt')) { |
ee1fb969 | 304 | foreach ($attempts as $attempt) { |
305 | echo get_string('attempt', 'quiz').' '.$attempt->attempt.': '; | |
306 | if ($attempt->timefinish == 0) { | |
307 | print_string('unfinished'); | |
308 | } else { | |
84e628a0 | 309 | echo quiz_format_grade($quiz, $attempt->sumgrades) . '/' . quiz_format_grade($quiz, $quiz->sumgrades); |
ee1fb969 | 310 | } |
311 | echo ' - '.userdate($attempt->timemodified).'<br />'; | |
312 | } | |
313 | } else { | |
314 | print_string('noattempts', 'quiz'); | |
315 | } | |
316 | ||
730fd187 | 317 | return true; |
318 | } | |
319 | ||
8cc86111 | 320 | /** |
321 | * Function to be run periodically according to the moodle cron | |
322 | * This function searches for things that need to be done, such | |
323 | * as sending out mail, toggling flags etc ... | |
324 | * | |
325 | * @global stdClass | |
326 | * @return bool true | |
327 | */ | |
be0ba083 | 328 | function quiz_cron() { |
730fd187 | 329 | global $CFG; |
330 | ||
331 | return true; | |
332 | } | |
333 | ||
b5a16eb7 | 334 | /** |
8cc86111 | 335 | * @global object |
b5a16eb7 | 336 | * @param integer $quizid the quiz id. |
337 | * @param integer $userid the userid. | |
338 | * @param string $status 'all', 'finished' or 'unfinished' to control | |
8cc86111 | 339 | * @param bool $includepreviews |
b5a16eb7 | 340 | * @return an array of all the user's attempts at this quiz. Returns an empty array if there are none. |
341 | */ | |
98f38217 | 342 | function quiz_get_user_attempts($quizid, $userid=0, $status = 'finished', $includepreviews = false) { |
9cf4a18b | 343 | global $DB; |
b5a16eb7 | 344 | $status_condition = array( |
345 | 'all' => '', | |
346 | 'finished' => ' AND timefinish > 0', | |
347 | 'unfinished' => ' AND timefinish = 0' | |
348 | ); | |
349 | $previewclause = ''; | |
350 | if (!$includepreviews) { | |
351 | $previewclause = ' AND preview = 0'; | |
352 | } | |
98f38217 | 353 | $params=array($quizid); |
354 | if ($userid){ | |
355 | $userclause = ' AND userid = ?'; | |
356 | $params[]=$userid; | |
357 | } else { | |
358 | $userclause = ''; | |
359 | } | |
9cf4a18b | 360 | if ($attempts = $DB->get_records_select('quiz_attempts', |
98f38217 | 361 | "quiz = ?" .$userclause. $previewclause . $status_condition[$status], $params, |
b5a16eb7 | 362 | 'attempt ASC')) { |
363 | return $attempts; | |
364 | } else { | |
365 | return array(); | |
366 | } | |
367 | } | |
858deff0 | 368 | |
d6dd2108 | 369 | /** |
370 | * Return grade for given user or all users. | |
371 | * | |
8cc86111 | 372 | * @global stdClass |
373 | * @global object | |
d6dd2108 | 374 | * @param int $quizid id of quiz |
375 | * @param int $userid optional user id, 0 means all users | |
f88fb62c | 376 | * @return array array of grades, false if none. These are raw grades. They should |
377 | * be processed with quiz_format_grade for display. | |
d6dd2108 | 378 | */ |
379 | function quiz_get_user_grades($quiz, $userid=0) { | |
9cf4a18b | 380 | global $CFG, $DB; |
d6dd2108 | 381 | |
9cf4a18b | 382 | $params = array($quiz->id); |
383 | $wheresql = ''; | |
384 | if ($userid) { | |
385 | $params[] = $userid; | |
386 | $wheresql = "AND u.id = ?"; | |
387 | } | |
d82b018f | 388 | $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade, g.timemodified AS dategraded, MAX(a.timefinish) AS datesubmitted |
9cf4a18b | 389 | FROM {user} u, {quiz_grades} g, {quiz_attempts} a |
390 | WHERE u.id = g.userid AND g.quiz = ? AND a.quiz = g.quiz AND u.id = a.userid $wheresql | |
d82b018f | 391 | GROUP BY u.id, g.grade, g.timemodified"; |
392 | ||
9cf4a18b | 393 | return $DB->get_records_sql($sql, $params); |
d6dd2108 | 394 | } |
395 | ||
f88fb62c | 396 | /** |
397 | * Round a grade to to the correct number of decimal places, and format it for display. | |
398 | * | |
399 | * @param object $quiz The quiz table row, only $quiz->decimalpoints is used. | |
400 | * @param float $grade The grade to round. | |
8cc86111 | 401 | * @return float |
f88fb62c | 402 | */ |
403 | function quiz_format_grade($quiz, $grade) { | |
404 | return format_float($grade, $quiz->decimalpoints); | |
405 | } | |
406 | ||
84e628a0 | 407 | /** |
408 | * Round a grade to to the correct number of decimal places, and format it for display. | |
409 | * | |
410 | * @param object $quiz The quiz table row, only $quiz->decimalpoints is used. | |
411 | * @param float $grade The grade to round. | |
8cc86111 | 412 | * @return float |
84e628a0 | 413 | */ |
414 | function quiz_format_question_grade($quiz, $grade) { | |
415 | if ($quiz->questiondecimalpoints == -1) { | |
416 | return format_float($grade, $quiz->decimalpoints); | |
417 | } else { | |
418 | return format_float($grade, $quiz->questiondecimalpoints); | |
419 | } | |
420 | } | |
421 | ||
d6dd2108 | 422 | /** |
423 | * Update grades in central gradebook | |
424 | * | |
8cc86111 | 425 | * @global stdClass |
426 | * @global object | |
775f811a | 427 | * @param object $quiz |
428 | * @param int $userid specific user only, 0 means all | |
d6dd2108 | 429 | */ |
775f811a | 430 | function quiz_update_grades($quiz, $userid=0, $nullifnone=true) { |
9cf4a18b | 431 | global $CFG, $DB; |
775f811a | 432 | require_once($CFG->libdir.'/gradelib.php'); |
ed1daaa9 | 433 | |
775f811a | 434 | if ($quiz->grade == 0) { |
435 | quiz_grade_item_update($quiz); | |
d6dd2108 | 436 | |
775f811a | 437 | } else if ($grades = quiz_get_user_grades($quiz, $userid)) { |
438 | quiz_grade_item_update($quiz, $grades); | |
eafb9d9e | 439 | |
775f811a | 440 | } else if ($userid and $nullifnone) { |
441 | $grade = new object(); | |
442 | $grade->userid = $userid; | |
443 | $grade->rawgrade = NULL; | |
444 | quiz_grade_item_update($quiz, $grade); | |
d6dd2108 | 445 | |
446 | } else { | |
775f811a | 447 | quiz_grade_item_update($quiz); |
448 | } | |
449 | } | |
3b1d5cc4 | 450 | |
775f811a | 451 | /** |
452 | * Update all grades in gradebook. | |
8cc86111 | 453 | * |
454 | * @global object | |
775f811a | 455 | */ |
456 | function quiz_upgrade_grades() { | |
457 | global $DB; | |
458 | ||
459 | $sql = "SELECT COUNT('x') | |
460 | FROM {quiz} a, {course_modules} cm, {modules} m | |
461 | WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id"; | |
462 | $count = $DB->count_records_sql($sql); | |
463 | ||
464 | $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid | |
465 | FROM {quiz} a, {course_modules} cm, {modules} m | |
466 | WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id"; | |
467 | if ($rs = $DB->get_recordset_sql($sql)) { | |
775f811a | 468 | $pbar = new progress_bar('quizupgradegrades', 500, true); |
469 | $i=0; | |
470 | foreach ($rs as $quiz) { | |
471 | $i++; | |
472 | upgrade_set_timeout(60*5); // set up timeout, may also abort execution | |
473 | quiz_update_grades($quiz, 0, false); | |
474 | $pbar->update($i, $count, "Updating Quiz grades ($i/$count)."); | |
d6dd2108 | 475 | } |
775f811a | 476 | $rs->close(); |
d6dd2108 | 477 | } |
d0ac6bc2 | 478 | } |
479 | ||
d6dd2108 | 480 | /** |
481 | * Create grade item for given quiz | |
482 | * | |
8cc86111 | 483 | * @global stdClass |
484 | * @uses GRADE_TYPE_VALUE | |
485 | * @uses GRADE_TYPE_NONE | |
486 | * @uses QUIZ_REVIEW_SCORES | |
487 | * @uses QUIZ_REVIEW_CLOSED | |
488 | * @uses QUIZ_REVIEW_OPEN | |
489 | * @uses PARAM_INT | |
490 | * @uses GRADE_UPDATE_ITEM_LOCKED | |
d6dd2108 | 491 | * @param object $quiz object with extra cmidnumber |
8cc86111 | 492 | * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook |
d6dd2108 | 493 | * @return int 0 if ok, error code otherwise |
494 | */ | |
ced5ee59 | 495 | function quiz_grade_item_update($quiz, $grades=NULL) { |
3b1d5cc4 | 496 | global $CFG, $OUTPUT; |
d6dd2108 | 497 | if (!function_exists('grade_update')) { //workaround for buggy PHP versions |
498 | require_once($CFG->libdir.'/gradelib.php'); | |
499 | } | |
500 | ||
501 | if (array_key_exists('cmidnumber', $quiz)) { //it may not be always present | |
502 | $params = array('itemname'=>$quiz->name, 'idnumber'=>$quiz->cmidnumber); | |
503 | } else { | |
504 | $params = array('itemname'=>$quiz->name); | |
505 | } | |
506 | ||
507 | if ($quiz->grade > 0) { | |
508 | $params['gradetype'] = GRADE_TYPE_VALUE; | |
509 | $params['grademax'] = $quiz->grade; | |
510 | $params['grademin'] = 0; | |
511 | ||
512 | } else { | |
513 | $params['gradetype'] = GRADE_TYPE_NONE; | |
514 | } | |
515 | ||
1223d24a | 516 | /* description by TJ: |
517 | 1/ If the quiz is set to not show scores while the quiz is still open, and is set to show scores after | |
518 | the quiz is closed, then create the grade_item with a show-after date that is the quiz close date. | |
519 | 2/ If the quiz is set to not show scores at either of those times, create the grade_item as hidden. | |
520 | 3/ If the quiz is set to show scores, create the grade_item visible. | |
521 | */ | |
522 | if (!($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED) | |
523 | and !($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN)) { | |
524 | $params['hidden'] = 1; | |
525 | ||
526 | } else if ( ($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED) | |
527 | and !($quiz->review & QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN)) { | |
528 | if ($quiz->timeclose) { | |
529 | $params['hidden'] = $quiz->timeclose; | |
530 | } else { | |
531 | $params['hidden'] = 1; | |
532 | } | |
533 | ||
534 | } else { | |
535 | // a) both open and closed enabled | |
536 | // b) open enabled, closed disabled - we can not "hide after", grades are kept visible even after closing | |
537 | $params['hidden'] = 0; | |
538 | } | |
539 | ||
0b5a80a1 | 540 | if ($grades === 'reset') { |
541 | $params['reset'] = true; | |
542 | $grades = NULL; | |
543 | } | |
9cf4a18b | 544 | |
49460d84 | 545 | $gradebook_grades = grade_get_grades($quiz->course, 'mod', 'quiz', $quiz->id); |
d45459b7 | 546 | if (!empty($gradebook_grades->items)) { |
547 | $grade_item = $gradebook_grades->items[0]; | |
548 | if ($grade_item->locked) { | |
549 | $confirm_regrade = optional_param('confirm_regrade', 0, PARAM_INT); | |
550 | if (!$confirm_regrade) { | |
551 | $message = get_string('gradeitemislocked', 'grades'); | |
552 | $back_link = $CFG->wwwroot . '/mod/quiz/report.php?q=' . $quiz->id . '&mode=overview'; | |
553 | $regrade_link = qualified_me() . '&confirm_regrade=1'; | |
3b1d5cc4 | 554 | echo $OUTPUT->box_start('generalbox', 'notice'); |
d45459b7 | 555 | echo '<p>'. $message .'</p>'; |
39e37019 | 556 | echo $OUTPUT->container_start('buttons'); |
5c2ed7e2 PS |
557 | echo $OUTPUT->single_button($regrade_link, get_string('regradeanyway', 'grades')); |
558 | echo $OUTPUT->single_button($back_link, get_string('cancel')); | |
39e37019 | 559 | echo $OUTPUT->container_end(); |
3b1d5cc4 | 560 | echo $OUTPUT->box_end(); |
9cf4a18b | 561 | |
d45459b7 | 562 | return GRADE_UPDATE_ITEM_LOCKED; |
563 | } | |
49460d84 | 564 | } |
565 | } | |
0b5a80a1 | 566 | |
ced5ee59 | 567 | return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, $grades, $params); |
d6dd2108 | 568 | } |
569 | ||
570 | /** | |
571 | * Delete grade item for given quiz | |
572 | * | |
8cc86111 | 573 | * @global stdClass |
d6dd2108 | 574 | * @param object $quiz object |
575 | * @return object quiz | |
576 | */ | |
577 | function quiz_grade_item_delete($quiz) { | |
578 | global $CFG; | |
53004e48 | 579 | require_once($CFG->libdir . '/gradelib.php'); |
d6dd2108 | 580 | |
53004e48 | 581 | return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, NULL, array('deleted' => 1)); |
d6dd2108 | 582 | } |
583 | ||
e2249afe | 584 | /** |
585 | * @return the options for calculating the quiz grade from the individual attempt grades. | |
586 | */ | |
587 | function quiz_get_grading_options() { | |
588 | return array ( | |
589 | QUIZ_GRADEHIGHEST => get_string('gradehighest', 'quiz'), | |
590 | QUIZ_GRADEAVERAGE => get_string('gradeaverage', 'quiz'), | |
591 | QUIZ_ATTEMPTFIRST => get_string('attemptfirst', 'quiz'), | |
592 | QUIZ_ATTEMPTLAST => get_string('attemptlast', 'quiz')); | |
593 | } | |
d6dd2108 | 594 | |
8cc86111 | 595 | /** |
596 | * Returns an array of users who have data in a given quiz | |
597 | * | |
598 | * @global stdClass | |
599 | * @global object | |
600 | * @param int $quizid | |
601 | * @return array | |
602 | */ | |
d061d883 | 603 | function quiz_get_participants($quizid) { |
9cf4a18b | 604 | global $CFG, $DB; |
d061d883 | 605 | |
e4acc4ce | 606 | //Get users from attempts |
9cf4a18b | 607 | $us_attempts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id |
608 | FROM {user} u, | |
609 | {quiz_attempts} a | |
610 | WHERE a.quiz = ? and | |
611 | u.id = a.userid", array($quizid)); | |
e4acc4ce | 612 | |
e4acc4ce | 613 | //Return us_attempts array (it contains an array of unique users) |
6b224376 | 614 | return $us_attempts; |
e4acc4ce | 615 | |
d061d883 | 616 | } |
730fd187 | 617 | |
8cc86111 | 618 | /** |
619 | * This standard function will check all instances of this module | |
620 | * and make sure there are up-to-date events created for each of them. | |
621 | * If courseid = 0, then every quiz event in the site is checked, else | |
622 | * only quiz events belonging to the course specified are checked. | |
623 | * This function is used, in its new format, by restore_refresh_events() | |
624 | * | |
625 | * @global object | |
626 | * @uses QUIZ_MAX_EVENT_LENGTH | |
627 | * @param int $courseid | |
628 | * @return bool | |
629 | */ | |
d2f308c0 | 630 | function quiz_refresh_events($courseid = 0) { |
9cf4a18b | 631 | global $DB; |
d2f308c0 | 632 | |
633 | if ($courseid == 0) { | |
9cf4a18b | 634 | if (! $quizzes = $DB->get_records('quiz')) { |
d2f308c0 | 635 | return true; |
636 | } | |
637 | } else { | |
9cf4a18b | 638 | if (! $quizzes = $DB->get_records('quiz', array('course' => $courseid))) { |
d2f308c0 | 639 | return true; |
640 | } | |
641 | } | |
9cf4a18b | 642 | $moduleid = $DB->get_field('modules', 'id', array('name' => 'quiz')); |
f41e824f | 643 | |
d2f308c0 | 644 | foreach ($quizzes as $quiz) { |
0cd1affc | 645 | $cm = get_coursemodule_from_id('quiz', $quiz->id); |
d2f308c0 | 646 | $event = NULL; |
b2a3cd2d | 647 | $event2 = NULL; |
648 | $event2old = NULL; | |
649 | ||
53004e48 | 650 | if ($events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id), 'timestart')) { |
b2a3cd2d | 651 | $event = array_shift($events); |
652 | if (!empty($events)) { | |
653 | $event2old = array_shift($events); | |
654 | if (!empty($events)) { | |
655 | foreach ($events as $badevent) { | |
f81a8247 SH |
656 | $badevent = calendar_event::load($badevent); |
657 | $badevent->delete(); | |
b2a3cd2d | 658 | } |
659 | } | |
660 | } | |
661 | } | |
662 | ||
ae040d4b | 663 | $event->name = $quiz->name; |
0cd1affc | 664 | $event->description = format_module_intro('quiz', $quiz, $cm->id); |
b2a3cd2d | 665 | $event->courseid = $quiz->course; |
666 | $event->groupid = 0; | |
667 | $event->userid = 0; | |
668 | $event->modulename = 'quiz'; | |
669 | $event->instance = $quiz->id; | |
ba288539 | 670 | $event->visible = instance_is_visible('quiz', $quiz); |
d2f308c0 | 671 | $event->timestart = $quiz->timeopen; |
b2a3cd2d | 672 | $event->eventtype = 'open'; |
d2f308c0 | 673 | $event->timeduration = ($quiz->timeclose - $quiz->timeopen); |
d2f308c0 | 674 | |
b2a3cd2d | 675 | if ($event->timeduration > QUIZ_MAX_EVENT_LENGTH) { /// Set up two events |
d2f308c0 | 676 | |
b2a3cd2d | 677 | $event2 = $event; |
d2f308c0 | 678 | |
ae040d4b | 679 | $event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; |
b2a3cd2d | 680 | $event->timeduration = 0; |
681 | ||
ae040d4b | 682 | $event2->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; |
b2a3cd2d | 683 | $event2->timestart = $quiz->timeclose; |
684 | $event2->eventtype = 'close'; | |
685 | $event2->timeduration = 0; | |
686 | ||
687 | if (empty($event2old->id)) { | |
688 | unset($event2->id); | |
f81a8247 | 689 | calendar_event::create($event2); |
b2a3cd2d | 690 | } else { |
691 | $event2->id = $event2old->id; | |
f81a8247 SH |
692 | $event2 = calendar_event::load($event2); |
693 | $event2->update($event2); | |
b2a3cd2d | 694 | } |
acda04bf | 695 | } else if (!empty($event2old->id)) { |
f81a8247 SH |
696 | $event2old = calendar_event::load($event2old); |
697 | $event2old->delete(); | |
b2a3cd2d | 698 | } |
699 | ||
700 | if (empty($event->id)) { | |
acda04bf | 701 | if (!empty($event->timestart)) { |
f81a8247 | 702 | calendar_event::create($event); |
acda04bf | 703 | } |
b2a3cd2d | 704 | } else { |
f81a8247 SH |
705 | $event = calendar_event::load($event); |
706 | $event->update($event); | |
d2f308c0 | 707 | } |
b2a3cd2d | 708 | |
d2f308c0 | 709 | } |
710 | return true; | |
711 | } | |
712 | ||
dd97c328 | 713 | /** |
714 | * Returns all quiz graded users since a given time for specified quiz | |
8cc86111 | 715 | * |
716 | * @global stdClass | |
717 | * @global object | |
718 | * @global object | |
719 | * @global object | |
720 | * @uses CONTEXT_MODULE | |
721 | * @param array $activities By reference | |
722 | * @param int $index By reference | |
723 | * @param int $timestart | |
724 | * @param int $courseid | |
725 | * @param int $cmid | |
726 | * @param int $userid | |
727 | * @param int $groupid | |
728 | * @return void | |
dd97c328 | 729 | */ |
730 | function quiz_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) { | |
9cf4a18b | 731 | global $CFG, $COURSE, $USER, $DB; |
6710ec87 | 732 | |
dd97c328 | 733 | if ($COURSE->id == $courseid) { |
734 | $course = $COURSE; | |
6710ec87 | 735 | } else { |
9cf4a18b | 736 | $course = $DB->get_record('course', array('id' => $courseid)); |
6710ec87 | 737 | } |
6710ec87 | 738 | |
dd97c328 | 739 | $modinfo =& get_fast_modinfo($course); |
6710ec87 | 740 | |
dd97c328 | 741 | $cm = $modinfo->cms[$cmid]; |
6710ec87 | 742 | |
9cf4a18b | 743 | $params = array($timestart, $cm->instance); |
744 | ||
dd97c328 | 745 | if ($userid) { |
9cf4a18b | 746 | $userselect = "AND u.id = ?"; |
747 | $params[] = $userid; | |
dd97c328 | 748 | } else { |
749 | $userselect = ""; | |
750 | } | |
ac21ad39 | 751 | |
dd97c328 | 752 | if ($groupid) { |
9cf4a18b | 753 | $groupselect = "AND gm.groupid = ?"; |
754 | $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id"; | |
755 | $params[] = $groupid; | |
dd97c328 | 756 | } else { |
757 | $groupselect = ""; | |
758 | $groupjoin = ""; | |
759 | } | |
6710ec87 | 760 | |
9cf4a18b | 761 | if (!$attempts = $DB->get_records_sql("SELECT qa.*, q.sumgrades AS maxgrade, |
762 | u.firstname, u.lastname, u.email, u.picture | |
763 | FROM {quiz_attempts} qa | |
764 | JOIN {quiz} q ON q.id = qa.quiz | |
765 | JOIN {user} u ON u.id = qa.userid | |
dd97c328 | 766 | $groupjoin |
767 | WHERE qa.timefinish > $timestart AND q.id = $cm->instance | |
768 | $userselect $groupselect | |
9cf4a18b | 769 | ORDER BY qa.timefinish ASC", $params)) { |
dd97c328 | 770 | return; |
771 | } | |
6710ec87 | 772 | |
dd97c328 | 773 | $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id); |
774 | $grader = has_capability('moodle/grade:viewall', $cm_context); | |
775 | $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context); | |
776 | $viewfullnames = has_capability('moodle/site:viewfullnames', $cm_context); | |
777 | $grader = has_capability('mod/quiz:grade', $cm_context); | |
778 | $groupmode = groups_get_activity_groupmode($cm, $course); | |
6710ec87 | 779 | |
dd97c328 | 780 | if (is_null($modinfo->groups)) { |
781 | $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo | |
782 | } | |
6710ec87 | 783 | |
dd97c328 | 784 | $aname = format_string($cm->name,true); |
785 | foreach ($attempts as $attempt) { | |
786 | if ($attempt->userid != $USER->id) { | |
787 | if (!$grader) { | |
788 | // grade permission required | |
789 | continue; | |
790 | } | |
6710ec87 | 791 | |
9cf4a18b | 792 | if ($groupmode == SEPARATEGROUPS and !$accessallgroups) { |
dd97c328 | 793 | $usersgroups = groups_get_all_groups($course->id, $attempt->userid, $cm->groupingid); |
794 | if (!is_array($usersgroups)) { | |
795 | continue; | |
796 | } | |
797 | $usersgroups = array_keys($usersgroups); | |
798 | $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]); | |
799 | if (empty($intersect)) { | |
800 | continue; | |
801 | } | |
802 | } | |
803 | } | |
804 | ||
805 | $tmpactivity = new object(); | |
806 | ||
807 | $tmpactivity->type = 'quiz'; | |
808 | $tmpactivity->cmid = $cm->id; | |
809 | $tmpactivity->name = $aname; | |
76cbde41 | 810 | $tmpactivity->sectionnum= $cm->sectionnum; |
dd97c328 | 811 | $tmpactivity->timestamp = $attempt->timefinish; |
9cf4a18b | 812 | |
dd97c328 | 813 | $tmpactivity->content->attemptid = $attempt->id; |
814 | $tmpactivity->content->sumgrades = $attempt->sumgrades; | |
815 | $tmpactivity->content->maxgrade = $attempt->maxgrade; | |
816 | $tmpactivity->content->attempt = $attempt->attempt; | |
9cf4a18b | 817 | |
dd97c328 | 818 | $tmpactivity->user->userid = $attempt->userid; |
819 | $tmpactivity->user->fullname = fullname($attempt, $viewfullnames); | |
820 | $tmpactivity->user->picture = $attempt->picture; | |
9cf4a18b | 821 | |
dd97c328 | 822 | $activities[$index++] = $tmpactivity; |
6710ec87 | 823 | } |
824 | ||
825 | return; | |
826 | } | |
827 | ||
8cc86111 | 828 | /** |
829 | * @global stdClass | |
830 | * @param object $activity | |
831 | * @param int $courseid | |
832 | * @param bool $detail | |
833 | * @param array $modnames | |
834 | * @return void output is echo'd | |
835 | */ | |
dd97c328 | 836 | function quiz_print_recent_mod_activity($activity, $courseid, $detail, $modnames) { |
e63f88c9 | 837 | global $CFG, $OUTPUT; |
6710ec87 | 838 | |
dd97c328 | 839 | echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">'; |
6710ec87 | 840 | |
dd97c328 | 841 | echo "<tr><td class=\"userpicture\" valign=\"top\">"; |
812dbaf7 | 842 | echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)); |
dd97c328 | 843 | echo "</td><td>"; |
6710ec87 | 844 | |
845 | if ($detail) { | |
dd97c328 | 846 | $modname = $modnames[$activity->type]; |
847 | echo '<div class="title">'; | |
b5d0cafc | 848 | echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ". |
dd97c328 | 849 | "class=\"icon\" alt=\"$modname\" />"; |
850 | echo "<a href=\"$CFG->wwwroot/mod/quiz/view.php?id={$activity->cmid}\">{$activity->name}</a>"; | |
851 | echo '</div>'; | |
6710ec87 | 852 | } |
853 | ||
dd97c328 | 854 | echo '<div class="grade">'; |
855 | echo get_string("attempt", "quiz")." {$activity->content->attempt}: "; | |
856 | $grades = "({$activity->content->sumgrades} / {$activity->content->maxgrade})"; | |
857 | echo "<a href=\"$CFG->wwwroot/mod/quiz/review.php?attempt={$activity->content->attemptid}\">$grades</a>"; | |
858 | echo '</div>'; | |
6710ec87 | 859 | |
dd97c328 | 860 | echo '<div class="user">'; |
861 | echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->userid}&course=$courseid\">" | |
862 | ."{$activity->user->fullname}</a> - ".userdate($activity->timestamp); | |
863 | echo '</div>'; | |
6710ec87 | 864 | |
dd97c328 | 865 | echo "</td></tr></table>"; |
6710ec87 | 866 | |
867 | return; | |
868 | } | |
869 | ||
ee1fb969 | 870 | /** |
920b93d1 | 871 | * Pre-process the quiz options form data, making any necessary adjustments. |
ad4cd837 | 872 | * Called by add/update instance in this file. |
b159da78 | 873 | * |
8cc86111 | 874 | * @uses QUIZ_REVIEW_OVERALLFEEDBACK |
875 | * @uses QUIZ_REVIEW_CLOSED | |
876 | * @uses QUIZ_REVIEW_OPEN | |
877 | * @uses QUIZ_REVIEW_IMMEDIATELY | |
878 | * @uses QUIZ_REVIEW_GENERALFEEDBACK | |
879 | * @uses QUIZ_REVIEW_SOLUTIONS | |
880 | * @uses QUIZ_REVIEW_ANSWERS | |
881 | * @uses QUIZ_REVIEW_FEEDBACK | |
882 | * @uses QUIZ_REVIEW_SCORES | |
883 | * @uses QUIZ_REVIEW_RESPONSES | |
884 | * @uses QUESTION_ADAPTIVE | |
920b93d1 | 885 | * @param object $quiz The variables set on the form. |
8cc86111 | 886 | * @return string |
920b93d1 | 887 | */ |
888 | function quiz_process_options(&$quiz) { | |
889 | $quiz->timemodified = time(); | |
ee1fb969 | 890 | |
dc5c6851 | 891 | // Quiz name. |
892 | if (!empty($quiz->name)) { | |
893 | $quiz->name = trim($quiz->name); | |
894 | } | |
a23f0aaf | 895 | |
ab0a8ff2 | 896 | // Password field - different in form to stop browsers that remember passwords |
897 | // getting confused. | |
898 | $quiz->password = $quiz->quizpassword; | |
899 | unset($quiz->quizpassword); | |
900 | ||
212b7b8c | 901 | // Quiz feedback |
a0807a00 | 902 | if (isset($quiz->feedbacktext)) { |
903 | // Clean up the boundary text. | |
904 | for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) { | |
905 | if (empty($quiz->feedbacktext[$i])) { | |
906 | $quiz->feedbacktext[$i] = ''; | |
907 | } else { | |
908 | $quiz->feedbacktext[$i] = trim($quiz->feedbacktext[$i]); | |
909 | } | |
212b7b8c | 910 | } |
b159da78 | 911 | |
a0807a00 | 912 | // Check the boundary value is a number or a percentage, and in range. |
913 | $i = 0; | |
914 | while (!empty($quiz->feedbackboundaries[$i])) { | |
915 | $boundary = trim($quiz->feedbackboundaries[$i]); | |
916 | if (!is_numeric($boundary)) { | |
917 | if (strlen($boundary) > 0 && $boundary[strlen($boundary) - 1] == '%') { | |
918 | $boundary = trim(substr($boundary, 0, -1)); | |
919 | if (is_numeric($boundary)) { | |
920 | $boundary = $boundary * $quiz->grade / 100.0; | |
921 | } else { | |
922 | return get_string('feedbackerrorboundaryformat', 'quiz', $i + 1); | |
923 | } | |
212b7b8c | 924 | } |
925 | } | |
a0807a00 | 926 | if ($boundary <= 0 || $boundary >= $quiz->grade) { |
927 | return get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1); | |
928 | } | |
929 | if ($i > 0 && $boundary >= $quiz->feedbackboundaries[$i - 1]) { | |
930 | return get_string('feedbackerrororder', 'quiz', $i + 1); | |
931 | } | |
932 | $quiz->feedbackboundaries[$i] = $boundary; | |
933 | $i += 1; | |
212b7b8c | 934 | } |
a0807a00 | 935 | $numboundaries = $i; |
b159da78 | 936 | |
a0807a00 | 937 | // Check there is nothing in the remaining unused fields. |
e0b7cfcb | 938 | if (!empty($quiz->feedbackboundaries)) { |
939 | for ($i = $numboundaries; $i < count($quiz->feedbackboundaries); $i += 1) { | |
940 | if (!empty($quiz->feedbackboundaries[$i]) && trim($quiz->feedbackboundaries[$i]) != '') { | |
941 | return get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1); | |
942 | } | |
a0807a00 | 943 | } |
212b7b8c | 944 | } |
a0807a00 | 945 | for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) { |
946 | if (!empty($quiz->feedbacktext[$i]) && trim($quiz->feedbacktext[$i]) != '') { | |
947 | return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); | |
948 | } | |
212b7b8c | 949 | } |
a0807a00 | 950 | $quiz->feedbackboundaries[-1] = $quiz->grade + 1; // Needs to be bigger than $quiz->grade because of '<' test in quiz_feedback_for_grade(). |
951 | $quiz->feedbackboundaries[$numboundaries] = 0; | |
952 | $quiz->feedbackboundarycount = $numboundaries; | |
212b7b8c | 953 | } |
a23f0aaf | 954 | |
920b93d1 | 955 | // Settings that get combined to go into the optionflags column. |
956 | $quiz->optionflags = 0; | |
957 | if (!empty($quiz->adaptive)) { | |
958 | $quiz->optionflags |= QUESTION_ADAPTIVE; | |
959 | } | |
960 | ||
961 | // Settings that get combined to go into the review column. | |
962 | $review = 0; | |
963 | if (isset($quiz->responsesimmediately)) { | |
ee1fb969 | 964 | $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_IMMEDIATELY); |
920b93d1 | 965 | unset($quiz->responsesimmediately); |
ee1fb969 | 966 | } |
920b93d1 | 967 | if (isset($quiz->responsesopen)) { |
ee1fb969 | 968 | $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_OPEN); |
920b93d1 | 969 | unset($quiz->responsesopen); |
ee1fb969 | 970 | } |
920b93d1 | 971 | if (isset($quiz->responsesclosed)) { |
ee1fb969 | 972 | $review += (QUIZ_REVIEW_RESPONSES & QUIZ_REVIEW_CLOSED); |
920b93d1 | 973 | unset($quiz->responsesclosed); |
ee1fb969 | 974 | } |
975 | ||
920b93d1 | 976 | if (isset($quiz->scoreimmediately)) { |
ee1fb969 | 977 | $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_IMMEDIATELY); |
920b93d1 | 978 | unset($quiz->scoreimmediately); |
ee1fb969 | 979 | } |
920b93d1 | 980 | if (isset($quiz->scoreopen)) { |
ee1fb969 | 981 | $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_OPEN); |
920b93d1 | 982 | unset($quiz->scoreopen); |
ee1fb969 | 983 | } |
920b93d1 | 984 | if (isset($quiz->scoreclosed)) { |
ee1fb969 | 985 | $review += (QUIZ_REVIEW_SCORES & QUIZ_REVIEW_CLOSED); |
920b93d1 | 986 | unset($quiz->scoreclosed); |
ee1fb969 | 987 | } |
988 | ||
920b93d1 | 989 | if (isset($quiz->feedbackimmediately)) { |
ee1fb969 | 990 | $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY); |
920b93d1 | 991 | unset($quiz->feedbackimmediately); |
ee1fb969 | 992 | } |
920b93d1 | 993 | if (isset($quiz->feedbackopen)) { |
ee1fb969 | 994 | $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_OPEN); |
920b93d1 | 995 | unset($quiz->feedbackopen); |
ee1fb969 | 996 | } |
920b93d1 | 997 | if (isset($quiz->feedbackclosed)) { |
ee1fb969 | 998 | $review += (QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_CLOSED); |
920b93d1 | 999 | unset($quiz->feedbackclosed); |
ee1fb969 | 1000 | } |
1001 | ||
920b93d1 | 1002 | if (isset($quiz->answersimmediately)) { |
ee1fb969 | 1003 | $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY); |
920b93d1 | 1004 | unset($quiz->answersimmediately); |
ee1fb969 | 1005 | } |
920b93d1 | 1006 | if (isset($quiz->answersopen)) { |
ee1fb969 | 1007 | $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_OPEN); |
920b93d1 | 1008 | unset($quiz->answersopen); |
ee1fb969 | 1009 | } |
920b93d1 | 1010 | if (isset($quiz->answersclosed)) { |
ee1fb969 | 1011 | $review += (QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_CLOSED); |
920b93d1 | 1012 | unset($quiz->answersclosed); |
ee1fb969 | 1013 | } |
1014 | ||
920b93d1 | 1015 | if (isset($quiz->solutionsimmediately)) { |
ee1fb969 | 1016 | $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_IMMEDIATELY); |
920b93d1 | 1017 | unset($quiz->solutionsimmediately); |
ee1fb969 | 1018 | } |
920b93d1 | 1019 | if (isset($quiz->solutionsopen)) { |
ee1fb969 | 1020 | $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_OPEN); |
920b93d1 | 1021 | unset($quiz->solutionsopen); |
ee1fb969 | 1022 | } |
920b93d1 | 1023 | if (isset($quiz->solutionsclosed)) { |
ee1fb969 | 1024 | $review += (QUIZ_REVIEW_SOLUTIONS & QUIZ_REVIEW_CLOSED); |
920b93d1 | 1025 | unset($quiz->solutionsclosed); |
ee1fb969 | 1026 | } |
1027 | ||
a4514d91 | 1028 | if (isset($quiz->generalfeedbackimmediately)) { |
1029 | $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_IMMEDIATELY); | |
00719c02 | 1030 | unset($quiz->generalfeedbackimmediately); |
1b8a7434 | 1031 | } |
a4514d91 | 1032 | if (isset($quiz->generalfeedbackopen)) { |
1033 | $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_OPEN); | |
00719c02 | 1034 | unset($quiz->generalfeedbackopen); |
1b8a7434 | 1035 | } |
a4514d91 | 1036 | if (isset($quiz->generalfeedbackclosed)) { |
1037 | $review += (QUIZ_REVIEW_GENERALFEEDBACK & QUIZ_REVIEW_CLOSED); | |
00719c02 | 1038 | unset($quiz->generalfeedbackclosed); |
1039 | } | |
1040 | ||
1041 | if (isset($quiz->overallfeedbackimmediately)) { | |
1042 | $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_IMMEDIATELY); | |
1043 | unset($quiz->overallfeedbackimmediately); | |
1044 | } | |
1045 | if (isset($quiz->overallfeedbackopen)) { | |
1046 | $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_OPEN); | |
1047 | unset($quiz->overallfeedbackopen); | |
1048 | } | |
1049 | if (isset($quiz->overallfeedbackclosed)) { | |
1050 | $review += (QUIZ_REVIEW_OVERALLFEEDBACK & QUIZ_REVIEW_CLOSED); | |
1051 | unset($quiz->overallfeedbackclosed); | |
1b8a7434 | 1052 | } |
1053 | ||
920b93d1 | 1054 | $quiz->review = $review; |
1055 | } | |
1056 | ||
1057 | /** | |
1058 | * This function is called at the end of quiz_add_instance | |
1059 | * and quiz_update_instance, to do the common processing. | |
a23f0aaf | 1060 | * |
8cc86111 | 1061 | * @global object |
1062 | * @uses QUIZ_MAX_EVENT_LENGTH | |
920b93d1 | 1063 | * @param object $quiz the quiz object. |
8cc86111 | 1064 | * @return void|string Void or error message |
920b93d1 | 1065 | */ |
1066 | function quiz_after_add_or_update($quiz) { | |
c18269c7 | 1067 | global $DB; |
920b93d1 | 1068 | |
212b7b8c | 1069 | // Save the feedback |
53004e48 | 1070 | $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); |
a23f0aaf | 1071 | |
212b7b8c | 1072 | for ($i = 0; $i <= $quiz->feedbackboundarycount; $i += 1) { |
1073 | $feedback = new stdClass; | |
1074 | $feedback->quizid = $quiz->id; | |
1075 | $feedback->feedbacktext = $quiz->feedbacktext[$i]; | |
1076 | $feedback->mingrade = $quiz->feedbackboundaries[$i]; | |
1077 | $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1]; | |
fc29e51b | 1078 | $DB->insert_record('quiz_feedback', $feedback, false); |
212b7b8c | 1079 | } |
1080 | ||
920b93d1 | 1081 | // Update the events relating to this quiz. |
1082 | // This is slightly inefficient, deleting the old events and creating new ones. However, | |
1083 | // there are at most two events, and this keeps the code simpler. | |
c18269c7 | 1084 | if ($events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id))) { |
920b93d1 | 1085 | foreach($events as $event) { |
f81a8247 SH |
1086 | $event2old = calendar_event::load($event); |
1087 | $event2old->delete(); | |
920b93d1 | 1088 | } |
1089 | } | |
1090 | ||
1091 | $event = new stdClass; | |
1092 | $event->description = $quiz->intro; | |
1093 | $event->courseid = $quiz->course; | |
1094 | $event->groupid = 0; | |
1095 | $event->userid = 0; | |
1096 | $event->modulename = 'quiz'; | |
1097 | $event->instance = $quiz->id; | |
1098 | $event->timestart = $quiz->timeopen; | |
1099 | $event->timeduration = $quiz->timeclose - $quiz->timeopen; | |
1100 | $event->visible = instance_is_visible('quiz', $quiz); | |
1101 | $event->eventtype = 'open'; | |
1102 | ||
1103 | if ($quiz->timeclose and $quiz->timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { | |
1104 | // Single event for the whole quiz. | |
1105 | $event->name = $quiz->name; | |
f81a8247 | 1106 | calendar_event::create($event); |
920b93d1 | 1107 | } else { |
1108 | // Separate start and end events. | |
1109 | $event->timeduration = 0; | |
1110 | if ($quiz->timeopen) { | |
1111 | $event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; | |
f81a8247 | 1112 | calendar_event::create($event); |
920b93d1 | 1113 | unset($event->id); // So we can use the same object for the close event. |
1114 | } | |
1115 | if ($quiz->timeclose) { | |
1116 | $event->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; | |
1117 | $event->timestart = $quiz->timeclose; | |
1118 | $event->eventtype = 'close'; | |
f81a8247 | 1119 | calendar_event::create($event); |
920b93d1 | 1120 | } |
1121 | } | |
d6dd2108 | 1122 | |
1123 | //update related grade item | |
c18269c7 | 1124 | quiz_grade_item_update($quiz); |
ee1fb969 | 1125 | } |
1126 | ||
8cc86111 | 1127 | /** |
1128 | * @return array | |
1129 | */ | |
f3221af9 | 1130 | function quiz_get_view_actions() { |
acf149ad | 1131 | return array('view', 'view all', 'report', 'review'); |
f3221af9 | 1132 | } |
ee1fb969 | 1133 | |
8cc86111 | 1134 | /** |
1135 | * @return array | |
1136 | */ | |
f3221af9 | 1137 | function quiz_get_post_actions() { |
acf149ad | 1138 | return array('attempt', 'close attempt', 'preview', 'editquestions', 'delete attempt', 'manualgrade'); |
f3221af9 | 1139 | } |
ee1fb969 | 1140 | |
f67172b6 | 1141 | /** |
1142 | * Returns an array of names of quizzes that use this question | |
1143 | * | |
64d79492 | 1144 | * @param integer $questionid |
f67172b6 | 1145 | * @return array of strings |
1146 | */ | |
1147 | function quiz_question_list_instances($questionid) { | |
c6307ef2 | 1148 | global $CFG, $DB; |
e8666d9a | 1149 | |
64d79492 | 1150 | // TODO MDL-5780: we should also consider other questions that are used by |
e8666d9a | 1151 | // random questions in this quiz, but that is very hard. |
1152 | ||
1153 | $sql = "SELECT q.id, q.name | |
c6307ef2 | 1154 | FROM {quiz} q |
1155 | JOIN {quiz_question_instances} qqi ON q.id = qqi.quiz | |
1156 | WHERE qqi.question = ?"; | |
e8666d9a | 1157 | |
c6307ef2 | 1158 | if ($instances = $DB->get_records_sql_menu($sql, array($questionid))) { |
e8666d9a | 1159 | return $instances; |
1160 | } | |
f67172b6 | 1161 | return array(); |
1162 | } | |
1163 | ||
7a6f4066 | 1164 | /** |
1165 | * Implementation of the function for printing the form elements that control | |
1166 | * whether the course reset functionality affects the quiz. | |
3b1d5cc4 | 1167 | * |
0b5a80a1 | 1168 | * @param $mform form passed by reference |
1169 | */ | |
1170 | function quiz_reset_course_form_definition(&$mform) { | |
c159da4c | 1171 | $mform->addElement('header', 'quizheader', get_string('modulenameplural', 'quiz')); |
0b5a80a1 | 1172 | $mform->addElement('advcheckbox', 'reset_quiz_attempts', get_string('removeallquizattempts','quiz')); |
1173 | } | |
1174 | ||
1175 | /** | |
1176 | * Course reset form defaults. | |
8cc86111 | 1177 | * @return array |
0b5a80a1 | 1178 | */ |
1179 | function quiz_reset_course_form_defaults($course) { | |
1180 | return array('reset_quiz_attempts'=>1); | |
1181 | } | |
1182 | ||
1183 | /** | |
1184 | * Removes all grades from gradebook | |
8cc86111 | 1185 | * |
1186 | * @global stdClass | |
1187 | * @global object | |
0b5a80a1 | 1188 | * @param int $courseid |
1189 | * @param string optional type | |
7a6f4066 | 1190 | */ |
0b5a80a1 | 1191 | function quiz_reset_gradebook($courseid, $type='') { |
9cf4a18b | 1192 | global $CFG, $DB; |
0b5a80a1 | 1193 | |
1194 | $sql = "SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid | |
9cf4a18b | 1195 | FROM {quiz} q, {course_modules} cm, {modules} m |
1196 | WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=q.id AND q.course=?"; | |
0b5a80a1 | 1197 | |
9cf4a18b | 1198 | if ($quizs = $DB->get_records_sql($sql, array($courseid))) { |
0b5a80a1 | 1199 | foreach ($quizs as $quiz) { |
1200 | quiz_grade_item_update($quiz, 'reset'); | |
1201 | } | |
1202 | } | |
7a6f4066 | 1203 | } |
1204 | ||
1205 | /** | |
1206 | * Actual implementation of the rest coures functionality, delete all the | |
1207 | * quiz attempts for course $data->courseid, if $data->reset_quiz_attempts is | |
1208 | * set and true. | |
6ef56c99 | 1209 | * |
1210 | * Also, move the quiz open and close dates, if the course start date is changing. | |
8cc86111 | 1211 | * |
1212 | * @global stdClass | |
1213 | * @global object | |
1214 | * @param object $data the data submitted from the reset course. | |
0b5a80a1 | 1215 | * @return array status array |
7a6f4066 | 1216 | */ |
0b5a80a1 | 1217 | function quiz_reset_userdata($data) { |
53004e48 | 1218 | global $CFG, $DB; |
1219 | require_once($CFG->libdir.'/questionlib.php'); | |
be0ba083 | 1220 | |
0b5a80a1 | 1221 | $componentstr = get_string('modulenameplural', 'quiz'); |
1222 | $status = array(); | |
b159da78 | 1223 | |
6ef56c99 | 1224 | /// Delete attempts. |
1225 | if (!empty($data->reset_quiz_attempts)) { | |
53004e48 | 1226 | $quizzes = $DB->get_records('quiz', array('course' => $data->courseid)); |
1227 | foreach ($quizzes as $quiz) { | |
1228 | quiz_delete_all_attempts($quiz); | |
7a6f4066 | 1229 | } |
0b5a80a1 | 1230 | |
0b5a80a1 | 1231 | // remove all grades from gradebook |
1232 | if (empty($data->reset_gradebook_grades)) { | |
1233 | quiz_reset_gradebook($data->courseid); | |
7a6f4066 | 1234 | } |
53004e48 | 1235 | $status[] = array('component' => $componentstr, 'item' => get_string('attemptsdeleted', 'quiz'), 'error' => false); |
7a6f4066 | 1236 | } |
6ef56c99 | 1237 | |
0b5a80a1 | 1238 | /// updating dates - shift may be negative too |
1239 | if ($data->timeshift) { | |
1240 | shift_course_mod_dates('quiz', array('timeopen', 'timeclose'), $data->timeshift, $data->courseid); | |
53004e48 | 1241 | $status[] = array('component' => $componentstr, 'item' => get_string('openclosedatesupdated', 'quiz'), 'error' => false); |
7a6f4066 | 1242 | } |
0b5a80a1 | 1243 | |
1244 | return $status; | |
7a6f4066 | 1245 | } |
14e6dc79 | 1246 | |
1247 | /** | |
1248 | * Checks whether the current user is allowed to view a file uploaded in a quiz. | |
1249 | * Teachers can view any from their courses, students can only view their own. | |
b159da78 | 1250 | * |
8cc86111 | 1251 | * @global object |
1252 | * @global object | |
1253 | * @uses CONTEXT_COURSE | |
95de57b8 | 1254 | * @param int $attemptuniqueid int attempt id |
14e6dc79 | 1255 | * @param int $questionid int question id |
b159da78 | 1256 | * @return boolean to indicate access granted or denied |
14e6dc79 | 1257 | */ |
95de57b8 | 1258 | function quiz_check_file_access($attemptuniqueid, $questionid) { |
9cf4a18b | 1259 | global $USER, $DB; |
b159da78 | 1260 | |
6102a59d | 1261 | $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid)); |
9cf4a18b | 1262 | $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz)); |
14e6dc79 | 1263 | $context = get_context_instance(CONTEXT_COURSE, $quiz->course); |
b159da78 | 1264 | |
14e6dc79 | 1265 | // access granted if the current user submitted this file |
1266 | if ($attempt->userid == $USER->id) { | |
1267 | return true; | |
1268 | // access granted if the current user has permission to grade quizzes in this course | |
1269 | } else if (has_capability('mod/quiz:viewreports', $context) || has_capability('mod/quiz:grade', $context)) { | |
1270 | return true; | |
1271 | } | |
b159da78 | 1272 | |
1273 | // otherwise, this user does not have permission | |
14e6dc79 | 1274 | return false; |
1275 | } | |
b5a16eb7 | 1276 | |
1277 | /** | |
1278 | * Prints quiz summaries on MyMoodle Page | |
8cc86111 | 1279 | * |
1280 | * @global object | |
1281 | * @global object | |
1282 | * @param arry $courses | |
1283 | * @param array $htmlarray | |
b5a16eb7 | 1284 | */ |
1285 | function quiz_print_overview($courses, &$htmlarray) { | |
1286 | global $USER, $CFG; | |
1287 | /// These next 6 Lines are constant in all modules (just change module name) | |
1288 | if (empty($courses) || !is_array($courses) || count($courses) == 0) { | |
1289 | return array(); | |
1290 | } | |
1291 | ||
2a13e454 | 1292 | if (!$quizzes = get_all_instances_in_courses('quiz', $courses)) { |
b5a16eb7 | 1293 | return; |
1294 | } | |
1295 | ||
1296 | /// Fetch some language strings outside the main loop. | |
1297 | $strquiz = get_string('modulename', 'quiz'); | |
1298 | $strnoattempts = get_string('noattempts', 'quiz'); | |
1299 | ||
1300 | /// We want to list quizzes that are currently available, and which have a close date. | |
1301 | /// This is the same as what the lesson does, and the dabate is in MDL-10568. | |
6c58e198 | 1302 | $now = time(); |
2a13e454 | 1303 | foreach ($quizzes as $quiz) { |
b5a16eb7 | 1304 | if ($quiz->timeclose >= $now && $quiz->timeopen < $now) { |
1305 | /// Give a link to the quiz, and the deadline. | |
1306 | $str = '<div class="quiz overview">' . | |
1307 | '<div class="name">' . $strquiz . ': <a ' . ($quiz->visible ? '' : ' class="dimmed"') . | |
1308 | ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->coursemodule . '">' . | |
1309 | $quiz->name . '</a></div>'; | |
1310 | $str .= '<div class="info">' . get_string('quizcloseson', 'quiz', userdate($quiz->timeclose)) . '</div>'; | |
1311 | ||
1312 | /// Now provide more information depending on the uers's role. | |
1313 | $context = get_context_instance(CONTEXT_MODULE, $quiz->coursemodule); | |
1314 | if (has_capability('mod/quiz:viewreports', $context)) { | |
1315 | /// For teacher-like people, show a summary of the number of student attempts. | |
9cf4a18b | 1316 | // The $quiz objects returned by get_all_instances_in_course have the necessary $cm |
2a13e454 | 1317 | // fields set to make the following call work. |
7956944f | 1318 | $str .= '<div class="info">' . quiz_num_attempt_summary($quiz, $quiz, true) . '</div>'; |
96c7d771 | 1319 | } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $context)) { // Student |
b5a16eb7 | 1320 | /// For student-like people, tell them how many attempts they have made. |
1321 | if (isset($USER->id) && ($attempts = quiz_get_user_attempts($quiz->id, $USER->id))) { | |
1322 | $numattempts = count($attempts); | |
9cf4a18b | 1323 | $str .= '<div class="info">' . get_string('numattemptsmade', 'quiz', $numattempts) . '</div>'; |
b5a16eb7 | 1324 | } else { |
1325 | $str .= '<div class="info">' . $strnoattempts . '</div>'; | |
1326 | } | |
1327 | } else { | |
1328 | /// For ayone else, there is no point listing this quiz, so stop processing. | |
1329 | continue; | |
1330 | } | |
1331 | ||
1332 | /// Add the output for this quiz to the rest. | |
1333 | $str .= '</div>'; | |
1334 | if (empty($htmlarray[$quiz->course]['quiz'])) { | |
1335 | $htmlarray[$quiz->course]['quiz'] = $str; | |
1336 | } else { | |
1337 | $htmlarray[$quiz->course]['quiz'] .= $str; | |
1338 | } | |
1339 | } | |
1340 | } | |
1341 | } | |
6c58e198 | 1342 | |
1343 | /** | |
1344 | * Return a textual summary of the number of attemtps that have been made at a particular quiz, | |
1345 | * returns '' if no attemtps have been made yet, unless $returnzero is passed as true. | |
8cc86111 | 1346 | * |
1347 | * @global stdClass | |
1348 | * @global object | |
1349 | * @global object | |
6c58e198 | 1350 | * @param object $quiz the quiz object. Only $quiz->id is used at the moment. |
2a13e454 | 1351 | * @param object $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid fields are used at the moment. |
1352 | * @param boolean $returnzero if false (default), when no attempts have been made '' is returned instead of 'Attempts: 0'. | |
1353 | * @param int $currentgroup if there is a concept of current group where this method is being called | |
1354 | * (e.g. a report) pass it in here. Default 0 which means no current group. | |
1355 | * @return string a string like "Attempts: 123", "Attemtps 123 (45 from your groups)" or | |
1356 | * "Attemtps 123 (45 from this group)". | |
6c58e198 | 1357 | */ |
2a13e454 | 1358 | function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup = 0) { |
9cf4a18b | 1359 | global $CFG, $USER, $DB; |
1360 | $numattempts = $DB->count_records('quiz_attempts', array('quiz'=> $quiz->id, 'preview'=>0)); | |
6c58e198 | 1361 | if ($numattempts || $returnzero) { |
2a13e454 | 1362 | if (groups_get_activity_groupmode($cm)) { |
1363 | $a->total = $numattempts; | |
1364 | if ($currentgroup) { | |
9cf4a18b | 1365 | $a->group = $DB->count_records_sql('SELECT count(1) FROM ' . |
1366 | '{quiz_attempts} qa JOIN ' . | |
1367 | '{groups_members} gm ON qa.userid = gm.userid ' . | |
1368 | 'WHERE quiz = ? AND preview = 0 AND groupid = ?', array($quiz->id, $currentgroup)); | |
2a13e454 | 1369 | return get_string('attemptsnumthisgroup', 'quiz', $a); |
9cf4a18b | 1370 | } else if ($groups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid)) { |
1371 | list($usql, $params) = $DB->get_in_or_equal(array_keys($groups)); | |
1372 | $a->group = $DB->count_records_sql('SELECT count(1) FROM ' . | |
1373 | '{quiz_attempts} qa JOIN ' . | |
1374 | '{groups_members} gm ON qa.userid = gm.userid ' . | |
1375 | 'WHERE quiz = ? AND preview = 0 AND ' . | |
1376 | "groupid $usql", array_merge(array($quiz->id), $params)); | |
2a13e454 | 1377 | return get_string('attemptsnumyourgroups', 'quiz', $a); |
1378 | } | |
1379 | } | |
6c58e198 | 1380 | return get_string('attemptsnum', 'quiz', $numattempts); |
1381 | } | |
1382 | return ''; | |
1383 | } | |
f432bebf | 1384 | |
4e781c7b | 1385 | /** |
8cc86111 | 1386 | * @uses FEATURE_GROUPS |
1387 | * @uses FEATURE_GROUPINGS | |
1388 | * @uses FEATURE_GROUPMEMBERSONLY | |
1389 | * @uses FEATURE_MOD_INTRO | |
1390 | * @uses FEATURE_COMPLETION_TRACKS_VIEWS | |
1391 | * @uses FEATURE_GRADE_HAS_GRADE | |
1392 | * @uses FEATURE_GRADE_OUTCOMES | |
4e781c7b | 1393 | * @param string $feature FEATURE_xx constant for requested feature |
1394 | * @return bool True if quiz supports feature | |
1395 | */ | |
1396 | function quiz_supports($feature) { | |
1397 | switch($feature) { | |
42f103be | 1398 | case FEATURE_GROUPS: return true; |
1399 | case FEATURE_GROUPINGS: return true; | |
1400 | case FEATURE_GROUPMEMBERSONLY: return true; | |
dc5c2bd9 | 1401 | case FEATURE_MOD_INTRO: return true; |
4e781c7b | 1402 | case FEATURE_COMPLETION_TRACKS_VIEWS: return true; |
42f103be | 1403 | case FEATURE_GRADE_HAS_GRADE: return true; |
1404 | case FEATURE_GRADE_OUTCOMES: return true; | |
17da2e6f | 1405 | case FEATURE_MOD_SUBPLUGINS: return array('quiz'=>'mod/quiz/report'); |
42f103be | 1406 | |
49f6e5f4 | 1407 | default: return null; |
4e781c7b | 1408 | } |
1409 | } | |
1410 | ||
f432bebf | 1411 | /** |
8cc86111 | 1412 | * @global object |
1413 | * @global stdClass | |
cca6e300 | 1414 | * @return array all other caps used in module |
f432bebf | 1415 | */ |
1416 | function quiz_get_extra_capabilities() { | |
be0ba083 | 1417 | global $DB, $CFG; |
1418 | require_once($CFG->libdir.'/questionlib.php'); | |
cca6e300 | 1419 | $caps = question_get_all_capabilities(); |
bbf4f440 | 1420 | $reportcaps = $DB->get_records_select_menu('capabilities', 'name LIKE ?', array('quizreport/%'), 'id,name'); |
1421 | $caps = array_merge($caps, $reportcaps); | |
cca6e300 | 1422 | $caps[] = 'moodle/site:accessallgroups'; |
1423 | return $caps; | |
f432bebf | 1424 | } |
55f599f0 | 1425 | |
1426 | /** | |
1427 | * This fucntion extends the global navigaiton for the site. | |
1428 | * It is important to note that you should not rely on PAGE objects within this | |
1429 | * body of code as there is no guarantee that during an AJAX request they are | |
1430 | * available | |
1431 | * | |
1432 | * @param navigation_node $navigation The quiz node within the global navigation | |
1433 | * @param stdClass $course The course object returned from the DB | |
1434 | * @param stdClass $module The module object returned from the DB | |
1435 | * @param stdClass $cm The course module isntance returned from the DB | |
1436 | */ | |
1437 | function quiz_extend_navigation($navigation, $course, $module, $cm) { | |
1438 | /** | |
1439 | * This is currently just a stub so that it can be easily expanded upon. | |
1440 | * When expanding just remove this comment and the line below and then add | |
1441 | * you content. | |
1442 | */ | |
1443 | $navigation->nodetype = navigation_node::NODETYPE_LEAF; | |
1444 | } | |
1445 | ||
1446 | /** | |
1447 | * This function extends the settings navigation block for the site. | |
1448 | * | |
1449 | * It is safe to rely on PAGE here as we will only ever be within the module | |
1450 | * context when this is called | |
1451 | * | |
1452 | * @param navigation_node $settings | |
1453 | * @param stdClass $module | |
1454 | */ | |
1455 | function quiz_extend_settings_navigation($settings, $module) { | |
56115eea | 1456 | global $PAGE, $CFG, $OUTPUT; |
55f599f0 | 1457 | |
55f599f0 | 1458 | $quiznavkey = $settings->add(get_string('quizadministration', 'quiz')); |
1459 | $quiznav = $settings->get($quiznavkey); | |
1460 | $quiznav->forceopen = true; | |
1461 | ||
1462 | if (has_capability('mod/quiz:view', $PAGE->cm->context)) { | |
a6855934 | 1463 | $url = new moodle_url('/mod/quiz/view.php', array('id'=>$PAGE->cm->id)); |
f9fc1461 | 1464 | $quiznav->add(get_string('info', 'quiz'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/info', '')); |
55f599f0 | 1465 | } |
1466 | if (has_capability('mod/quiz:viewreports', $PAGE->cm->context)) { | |
a6855934 | 1467 | $url = new moodle_url('/mod/quiz/report.php', array('q'=>$PAGE->cm->instance)); |
f9fc1461 | 1468 | $reportkey = $quiznav->add(get_string('results', 'quiz'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', '')); |
55f599f0 | 1469 | |
1470 | require_once($CFG->dirroot.'/mod/quiz/report/reportlib.php'); | |
1471 | $reportlist = quiz_report_list($PAGE->cm->context); | |
1472 | foreach ($reportlist as $report) { | |
a6855934 | 1473 | $url = new moodle_url('/mod/quiz/report.php', array('q'=>$PAGE->cm->instance, 'mode'=>$report)); |
f9fc1461 | 1474 | $quiznav->get($reportkey)->add(get_string($report, 'quiz_'.$report), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/item', '')); |
55f599f0 | 1475 | } |
1476 | } | |
1477 | if (has_capability('mod/quiz:preview', $PAGE->cm->context)) { | |
a6855934 | 1478 | $url = new moodle_url('/mod/quiz/startattempt.php', array('cmid'=>$PAGE->cm->id, 'sesskey'=>sesskey())); |
f9fc1461 | 1479 | $quiznav->add(get_string('preview', 'quiz'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('t/preview', '')); |
55f599f0 | 1480 | } |
1481 | if (has_capability('mod/quiz:manage', $PAGE->cm->context)) { | |
a6855934 | 1482 | $url = new moodle_url('/mod/quiz/edit.php', array('cmid'=>$PAGE->cm->id)); |
f9fc1461 | 1483 | $quiznav->add(get_string('edit'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('t/edit', '')); |
55f599f0 | 1484 | } |
1485 | ||
1486 | if (has_capability('moodle/course:manageactivities', $PAGE->cm->context)) { | |
a6855934 | 1487 | $url = new moodle_url('/course/mod.php', array('update' => $PAGE->cm->id, 'return' => true, 'sesskey' => sesskey())); |
55f599f0 | 1488 | $quiznav->add(get_string('updatethis', '', get_string('modulename', 'quiz')), $url); |
1489 | } | |
1490 | ||
1491 | if (count($quiznav->children)<1) { | |
1492 | $settings->remove_child($quiznavkey); | |
1493 | } | |
1a96363a | 1494 | } |