PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/moodle/mod/quiz/lib.php

https://bitbucket.org/sebastianberm/moodle2x
PHP | 1720 lines | 1051 code | 209 blank | 460 comment | 200 complexity | a1f14753ecaa0d8a2d9b581f0b32cc89 MD5 | raw file
Possible License(s): AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Library of functions for the quiz module.
  18. *
  19. * This contains functions that are called also from outside the quiz module
  20. * Functions that are only called by the quiz module itself are in {@link locallib.php}
  21. *
  22. * @package mod
  23. * @subpackage quiz
  24. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26. */
  27. defined('MOODLE_INTERNAL') || die();
  28. require_once($CFG->libdir . '/eventslib.php');
  29. require_once($CFG->dirroot . '/calendar/lib.php');
  30. /**#@+
  31. * Option controlling what options are offered on the quiz settings form.
  32. */
  33. define('QUIZ_MAX_ATTEMPT_OPTION', 10);
  34. define('QUIZ_MAX_QPP_OPTION', 50);
  35. define('QUIZ_MAX_DECIMAL_OPTION', 5);
  36. define('QUIZ_MAX_Q_DECIMAL_OPTION', 7);
  37. /**#@-*/
  38. /**
  39. * If start and end date for the quiz are more than this many seconds apart
  40. * they will be represented by two separate events in the calendar
  41. */
  42. define('QUIZ_MAX_EVENT_LENGTH', 5*24*60*60); // 5 days
  43. /**
  44. * Given an object containing all the necessary data,
  45. * (defined by the form in mod_form.php) this function
  46. * will create a new instance and return the id number
  47. * of the new instance.
  48. *
  49. * @param object $quiz the data that came from the form.
  50. * @return mixed the id of the new instance on success,
  51. * false or a string error message on failure.
  52. */
  53. function quiz_add_instance($quiz) {
  54. global $DB;
  55. $cmid = $quiz->coursemodule;
  56. // Process the options from the form.
  57. $quiz->created = time();
  58. $quiz->questions = '';
  59. $result = quiz_process_options($quiz);
  60. if ($result && is_string($result)) {
  61. return $result;
  62. }
  63. // Try to store it in the database.
  64. $quiz->id = $DB->insert_record('quiz', $quiz);
  65. // Do the processing required after an add or an update.
  66. quiz_after_add_or_update($quiz);
  67. return $quiz->id;
  68. }
  69. /**
  70. * Given an object containing all the necessary data,
  71. * (defined by the form in mod_form.php) this function
  72. * will update an existing instance with new data.
  73. *
  74. * @param object $quiz the data that came from the form.
  75. * @return mixed true on success, false or a string error message on failure.
  76. */
  77. function quiz_update_instance($quiz, $mform) {
  78. global $CFG, $DB;
  79. // Process the options from the form.
  80. $result = quiz_process_options($quiz);
  81. if ($result && is_string($result)) {
  82. return $result;
  83. }
  84. $oldquiz = $DB->get_record('quiz', array('id' => $quiz->instance));
  85. // Repaginate, if asked to.
  86. if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) {
  87. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  88. $quiz->questions = quiz_repaginate(quiz_clean_layout($oldquiz->questions, true),
  89. $quiz->questionsperpage);
  90. }
  91. unset($quiz->repaginatenow);
  92. // Update the database.
  93. $quiz->id = $quiz->instance;
  94. $DB->update_record('quiz', $quiz);
  95. // Do the processing required after an add or an update.
  96. quiz_after_add_or_update($quiz);
  97. if ($oldquiz->grademethod != $quiz->grademethod) {
  98. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  99. $quiz->sumgrades = $oldquiz->sumgrades;
  100. $quiz->grade = $oldquiz->grade;
  101. quiz_update_all_final_grades($quiz);
  102. quiz_update_grades($quiz);
  103. }
  104. // Delete any previous preview attempts
  105. quiz_delete_previews($quiz);
  106. return true;
  107. }
  108. /**
  109. * Given an ID of an instance of this module,
  110. * this function will permanently delete the instance
  111. * and any data that depends on it.
  112. *
  113. * @param int $id the id of the quiz to delete.
  114. * @return bool success or failure.
  115. */
  116. function quiz_delete_instance($id) {
  117. global $DB;
  118. $quiz = $DB->get_record('quiz', array('id' => $id), '*', MUST_EXIST);
  119. quiz_delete_all_attempts($quiz);
  120. quiz_delete_all_overrides($quiz);
  121. $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id));
  122. $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
  123. $events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id));
  124. foreach ($events as $event) {
  125. $event = calendar_event::load($event);
  126. $event->delete();
  127. }
  128. quiz_grade_item_delete($quiz);
  129. $DB->delete_records('quiz', array('id' => $quiz->id));
  130. return true;
  131. }
  132. /**
  133. * Deletes a quiz override from the database and clears any corresponding calendar events
  134. *
  135. * @param object $quiz The quiz object.
  136. * @param int $overrideid The id of the override being deleted
  137. * @return bool true on success
  138. */
  139. function quiz_delete_override($quiz, $overrideid) {
  140. global $DB;
  141. $override = $DB->get_record('quiz_overrides', array('id' => $overrideid), '*', MUST_EXIST);
  142. // Delete the events
  143. $events = $DB->get_records('event', array('modulename' => 'quiz',
  144. 'instance' => $quiz->id, 'groupid' => (int)$override->groupid,
  145. 'userid' => (int)$override->userid));
  146. foreach ($events as $event) {
  147. $eventold = calendar_event::load($event);
  148. $eventold->delete();
  149. }
  150. $DB->delete_records('quiz_overrides', array('id' => $overrideid));
  151. return true;
  152. }
  153. /**
  154. * Deletes all quiz overrides from the database and clears any corresponding calendar events
  155. *
  156. * @param object $quiz The quiz object.
  157. */
  158. function quiz_delete_all_overrides($quiz) {
  159. global $DB;
  160. $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id');
  161. foreach ($overrides as $override) {
  162. quiz_delete_override($quiz, $override->id);
  163. }
  164. }
  165. /**
  166. * Updates a quiz object with override information for a user.
  167. *
  168. * Algorithm: For each quiz setting, if there is a matching user-specific override,
  169. * then use that otherwise, if there are group-specific overrides, return the most
  170. * lenient combination of them. If neither applies, leave the quiz setting unchanged.
  171. *
  172. * Special case: if there is more than one password that applies to the user, then
  173. * quiz->extrapasswords will contain an array of strings giving the remaining
  174. * passwords.
  175. *
  176. * @param object $quiz The quiz object.
  177. * @param int $userid The userid.
  178. * @return object $quiz The updated quiz object.
  179. */
  180. function quiz_update_effective_access($quiz, $userid) {
  181. global $DB;
  182. // check for user override
  183. $override = $DB->get_record('quiz_overrides', array('quiz' => $quiz->id, 'userid' => $userid));
  184. if (!$override) {
  185. $override = new stdClass();
  186. $override->timeopen = null;
  187. $override->timeclose = null;
  188. $override->timelimit = null;
  189. $override->attempts = null;
  190. $override->password = null;
  191. }
  192. // check for group overrides
  193. $groupings = groups_get_user_groups($quiz->course, $userid);
  194. if (!empty($groupings[0])) {
  195. // Select all overrides that apply to the User's groups
  196. list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0]));
  197. $sql = "SELECT * FROM {quiz_overrides}
  198. WHERE groupid $extra AND quiz = ?";
  199. $params[] = $quiz->id;
  200. $records = $DB->get_records_sql($sql, $params);
  201. // Combine the overrides
  202. $opens = array();
  203. $closes = array();
  204. $limits = array();
  205. $attempts = array();
  206. $passwords = array();
  207. foreach ($records as $gpoverride) {
  208. if (isset($gpoverride->timeopen)) {
  209. $opens[] = $gpoverride->timeopen;
  210. }
  211. if (isset($gpoverride->timeclose)) {
  212. $closes[] = $gpoverride->timeclose;
  213. }
  214. if (isset($gpoverride->timelimit)) {
  215. $limits[] = $gpoverride->timelimit;
  216. }
  217. if (isset($gpoverride->attempts)) {
  218. $attempts[] = $gpoverride->attempts;
  219. }
  220. if (isset($gpoverride->password)) {
  221. $passwords[] = $gpoverride->password;
  222. }
  223. }
  224. // If there is a user override for a setting, ignore the group override
  225. if (is_null($override->timeopen) && count($opens)) {
  226. $override->timeopen = min($opens);
  227. }
  228. if (is_null($override->timeclose) && count($closes)) {
  229. $override->timeclose = max($closes);
  230. }
  231. if (is_null($override->timelimit) && count($limits)) {
  232. $override->timelimit = max($limits);
  233. }
  234. if (is_null($override->attempts) && count($attempts)) {
  235. $override->attempts = max($attempts);
  236. }
  237. if (is_null($override->password) && count($passwords)) {
  238. $override->password = array_shift($passwords);
  239. if (count($passwords)) {
  240. $override->extrapasswords = $passwords;
  241. }
  242. }
  243. }
  244. // merge with quiz defaults
  245. $keys = array('timeopen', 'timeclose', 'timelimit', 'attempts', 'password', 'extrapasswords');
  246. foreach ($keys as $key) {
  247. if (isset($override->{$key})) {
  248. $quiz->{$key} = $override->{$key};
  249. }
  250. }
  251. return $quiz;
  252. }
  253. /**
  254. * Delete all the attempts belonging to a quiz.
  255. *
  256. * @param object $quiz The quiz object.
  257. */
  258. function quiz_delete_all_attempts($quiz) {
  259. global $CFG, $DB;
  260. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  261. question_engine::delete_questions_usage_by_activities(new qubaids_for_quiz($quiz->id));
  262. $DB->delete_records('quiz_attempts', array('quiz' => $quiz->id));
  263. $DB->delete_records('quiz_grades', array('quiz' => $quiz->id));
  264. }
  265. /**
  266. * Get the best current grade for a particular user in a quiz.
  267. *
  268. * @param object $quiz the quiz settings.
  269. * @param int $userid the id of the user.
  270. * @return float the user's current grade for this quiz, or null if this user does
  271. * not have a grade on this quiz.
  272. */
  273. function quiz_get_best_grade($quiz, $userid) {
  274. global $DB;
  275. $grade = $DB->get_field('quiz_grades', 'grade',
  276. array('quiz' => $quiz->id, 'userid' => $userid));
  277. // Need to detect errors/no result, without catching 0 grades.
  278. if ($grade === false) {
  279. return null;
  280. }
  281. return $grade + 0; // Convert to number.
  282. }
  283. /**
  284. * Is this a graded quiz? If this method returns true, you can assume that
  285. * $quiz->grade and $quiz->sumgrades are non-zero (for example, if you want to
  286. * divide by them).
  287. *
  288. * @param object $quiz a row from the quiz table.
  289. * @return bool whether this is a graded quiz.
  290. */
  291. function quiz_has_grades($quiz) {
  292. return $quiz->grade >= 0.000005 && $quiz->sumgrades >= 0.000005;
  293. }
  294. /**
  295. * Return a small object with summary information about what a
  296. * user has done with a given particular instance of this module
  297. * Used for user activity reports.
  298. * $return->time = the time they did it
  299. * $return->info = a short text description
  300. *
  301. * @param object $course
  302. * @param object $user
  303. * @param object $mod
  304. * @param object $quiz
  305. * @return object|null
  306. */
  307. function quiz_user_outline($course, $user, $mod, $quiz) {
  308. global $DB, $CFG;
  309. require_once("$CFG->libdir/gradelib.php");
  310. $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id);
  311. if (empty($grades->items[0]->grades)) {
  312. return null;
  313. } else {
  314. $grade = reset($grades->items[0]->grades);
  315. }
  316. $result = new stdClass();
  317. $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
  318. //datesubmitted == time created. dategraded == time modified or time overridden
  319. //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
  320. //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
  321. if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
  322. $result->time = $grade->dategraded;
  323. } else {
  324. $result->time = $grade->datesubmitted;
  325. }
  326. return $result;
  327. }
  328. /**
  329. * Print a detailed representation of what a user has done with
  330. * a given particular instance of this module, for user activity reports.
  331. *
  332. * @global object
  333. * @param object $course
  334. * @param object $user
  335. * @param object $mod
  336. * @param object $quiz
  337. * @return bool
  338. */
  339. function quiz_user_complete($course, $user, $mod, $quiz) {
  340. global $DB, $CFG, $OUTPUT;
  341. require_once("$CFG->libdir/gradelib.php");
  342. $grades = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id);
  343. if (!empty($grades->items[0]->grades)) {
  344. $grade = reset($grades->items[0]->grades);
  345. echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
  346. if ($grade->str_feedback) {
  347. echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  348. }
  349. }
  350. if ($attempts = $DB->get_records('quiz_attempts',
  351. array('userid' => $user->id, 'quiz' => $quiz->id), 'attempt')) {
  352. foreach ($attempts as $attempt) {
  353. echo get_string('attempt', 'quiz').' '.$attempt->attempt.': ';
  354. if ($attempt->timefinish == 0) {
  355. print_string('unfinished');
  356. } else {
  357. echo quiz_format_grade($quiz, $attempt->sumgrades) . '/' .
  358. quiz_format_grade($quiz, $quiz->sumgrades);
  359. }
  360. echo ' - '.userdate($attempt->timemodified).'<br />';
  361. }
  362. } else {
  363. print_string('noattempts', 'quiz');
  364. }
  365. return true;
  366. }
  367. /**
  368. * Function to be run periodically according to the moodle cron
  369. * This function searches for things that need to be done, such
  370. * as sending out mail, toggling flags etc ...
  371. *
  372. * @return bool true
  373. */
  374. function quiz_cron() {
  375. return true;
  376. }
  377. /**
  378. * @param int $quizid the quiz id.
  379. * @param int $userid the userid.
  380. * @param string $status 'all', 'finished' or 'unfinished' to control
  381. * @param bool $includepreviews
  382. * @return an array of all the user's attempts at this quiz. Returns an empty
  383. * array if there are none.
  384. */
  385. function quiz_get_user_attempts($quizid, $userid, $status = 'finished', $includepreviews = false) {
  386. global $DB;
  387. $status_condition = array(
  388. 'all' => '',
  389. 'finished' => ' AND timefinish > 0',
  390. 'unfinished' => ' AND timefinish = 0'
  391. );
  392. $previewclause = '';
  393. if (!$includepreviews) {
  394. $previewclause = ' AND preview = 0';
  395. }
  396. return $DB->get_records_select('quiz_attempts',
  397. 'quiz = ? AND userid = ?' . $previewclause . $status_condition[$status],
  398. array($quizid, $userid), 'attempt ASC');
  399. }
  400. /**
  401. * Return grade for given user or all users.
  402. *
  403. * @param int $quizid id of quiz
  404. * @param int $userid optional user id, 0 means all users
  405. * @return array array of grades, false if none. These are raw grades. They should
  406. * be processed with quiz_format_grade for display.
  407. */
  408. function quiz_get_user_grades($quiz, $userid = 0) {
  409. global $CFG, $DB;
  410. $params = array($quiz->id);
  411. $usertest = '';
  412. if ($userid) {
  413. $params[] = $userid;
  414. $usertest = 'AND u.id = ?';
  415. }
  416. return $DB->get_records_sql("
  417. SELECT
  418. u.id,
  419. u.id AS userid,
  420. qg.grade AS rawgrade,
  421. qg.timemodified AS dategraded,
  422. MAX(qa.timefinish) AS datesubmitted
  423. FROM {user} u
  424. JOIN {quiz_grades} qg ON u.id = qg.userid
  425. JOIN {quiz_attempts} qa ON qa.quiz = qg.quiz AND qa.userid = u.id
  426. WHERE qg.quiz = ?
  427. $usertest
  428. GROUP BY u.id, qg.grade, qg.timemodified", $params);
  429. }
  430. /**
  431. * Round a grade to to the correct number of decimal places, and format it for display.
  432. *
  433. * @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
  434. * @param float $grade The grade to round.
  435. * @return float
  436. */
  437. function quiz_format_grade($quiz, $grade) {
  438. if (is_null($grade)) {
  439. return get_string('notyetgraded', 'quiz');
  440. }
  441. return format_float($grade, $quiz->decimalpoints);
  442. }
  443. /**
  444. * Round a grade to to the correct number of decimal places, and format it for display.
  445. *
  446. * @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
  447. * @param float $grade The grade to round.
  448. * @return float
  449. */
  450. function quiz_format_question_grade($quiz, $grade) {
  451. if (empty($quiz->questiondecimalpoints)) {
  452. $quiz->questiondecimalpoints = -1;
  453. }
  454. if ($quiz->questiondecimalpoints == -1) {
  455. return format_float($grade, $quiz->decimalpoints);
  456. } else {
  457. return format_float($grade, $quiz->questiondecimalpoints);
  458. }
  459. }
  460. /**
  461. * Update grades in central gradebook
  462. *
  463. * @param object $quiz the quiz settings.
  464. * @param int $userid specific user only, 0 means all users.
  465. */
  466. function quiz_update_grades($quiz, $userid = 0, $nullifnone = true) {
  467. global $CFG, $DB;
  468. require_once($CFG->libdir.'/gradelib.php');
  469. if ($quiz->grade == 0) {
  470. quiz_grade_item_update($quiz);
  471. } else if ($grades = quiz_get_user_grades($quiz, $userid)) {
  472. quiz_grade_item_update($quiz, $grades);
  473. } else if ($userid && $nullifnone) {
  474. $grade = new stdClass();
  475. $grade->userid = $userid;
  476. $grade->rawgrade = null;
  477. quiz_grade_item_update($quiz, $grade);
  478. } else {
  479. quiz_grade_item_update($quiz);
  480. }
  481. }
  482. /**
  483. * Update all grades in gradebook.
  484. */
  485. function quiz_upgrade_grades() {
  486. global $DB;
  487. $sql = "SELECT COUNT('x')
  488. FROM {quiz} a, {course_modules} cm, {modules} m
  489. WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id";
  490. $count = $DB->count_records_sql($sql);
  491. $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
  492. FROM {quiz} a, {course_modules} cm, {modules} m
  493. WHERE m.name='quiz' AND m.id=cm.module AND cm.instance=a.id";
  494. $rs = $DB->get_recordset_sql($sql);
  495. if ($rs->valid()) {
  496. $pbar = new progress_bar('quizupgradegrades', 500, true);
  497. $i=0;
  498. foreach ($rs as $quiz) {
  499. $i++;
  500. upgrade_set_timeout(60*5); // set up timeout, may also abort execution
  501. quiz_update_grades($quiz, 0, false);
  502. $pbar->update($i, $count, "Updating Quiz grades ($i/$count).");
  503. }
  504. }
  505. $rs->close();
  506. }
  507. /**
  508. * Create grade item for given quiz
  509. *
  510. * @param object $quiz object with extra cmidnumber
  511. * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
  512. * @return int 0 if ok, error code otherwise
  513. */
  514. function quiz_grade_item_update($quiz, $grades = null) {
  515. global $CFG, $OUTPUT;
  516. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  517. require_once($CFG->libdir.'/gradelib.php');
  518. if (array_key_exists('cmidnumber', $quiz)) { // may not be always present
  519. $params = array('itemname' => $quiz->name, 'idnumber' => $quiz->cmidnumber);
  520. } else {
  521. $params = array('itemname' => $quiz->name);
  522. }
  523. if ($quiz->grade > 0) {
  524. $params['gradetype'] = GRADE_TYPE_VALUE;
  525. $params['grademax'] = $quiz->grade;
  526. $params['grademin'] = 0;
  527. } else {
  528. $params['gradetype'] = GRADE_TYPE_NONE;
  529. }
  530. // description by TJ:
  531. // 1. If the quiz is set to not show grades while the quiz is still open,
  532. // and is set to show grades after the quiz is closed, then create the
  533. // grade_item with a show-after date that is the quiz close date.
  534. // 2. If the quiz is set to not show grades at either of those times,
  535. // create the grade_item as hidden.
  536. // 3. If the quiz is set to show grades, create the grade_item visible.
  537. $openreviewoptions = mod_quiz_display_options::make_from_quiz($quiz,
  538. mod_quiz_display_options::LATER_WHILE_OPEN);
  539. $closedreviewoptions = mod_quiz_display_options::make_from_quiz($quiz,
  540. mod_quiz_display_options::AFTER_CLOSE);
  541. if ($openreviewoptions->marks < question_display_options::MARK_AND_MAX &&
  542. $closedreviewoptions->marks < question_display_options::MARK_AND_MAX) {
  543. $params['hidden'] = 1;
  544. } else if ($openreviewoptions->marks < question_display_options::MARK_AND_MAX &&
  545. $closedreviewoptions->marks >= question_display_options::MARK_AND_MAX) {
  546. if ($quiz->timeclose) {
  547. $params['hidden'] = $quiz->timeclose;
  548. } else {
  549. $params['hidden'] = 1;
  550. }
  551. } else {
  552. // a) both open and closed enabled
  553. // b) open enabled, closed disabled - we can not "hide after",
  554. // grades are kept visible even after closing
  555. $params['hidden'] = 0;
  556. }
  557. if ($grades === 'reset') {
  558. $params['reset'] = true;
  559. $grades = null;
  560. }
  561. $gradebook_grades = grade_get_grades($quiz->course, 'mod', 'quiz', $quiz->id);
  562. if (!empty($gradebook_grades->items)) {
  563. $grade_item = $gradebook_grades->items[0];
  564. if ($grade_item->locked) {
  565. $confirm_regrade = optional_param('confirm_regrade', 0, PARAM_INT);
  566. if (!$confirm_regrade) {
  567. $message = get_string('gradeitemislocked', 'grades');
  568. $back_link = $CFG->wwwroot . '/mod/quiz/report.php?q=' . $quiz->id .
  569. '&amp;mode=overview';
  570. $regrade_link = qualified_me() . '&amp;confirm_regrade=1';
  571. echo $OUTPUT->box_start('generalbox', 'notice');
  572. echo '<p>'. $message .'</p>';
  573. echo $OUTPUT->container_start('buttons');
  574. echo $OUTPUT->single_button($regrade_link, get_string('regradeanyway', 'grades'));
  575. echo $OUTPUT->single_button($back_link, get_string('cancel'));
  576. echo $OUTPUT->container_end();
  577. echo $OUTPUT->box_end();
  578. return GRADE_UPDATE_ITEM_LOCKED;
  579. }
  580. }
  581. }
  582. return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0, $grades, $params);
  583. }
  584. /**
  585. * Delete grade item for given quiz
  586. *
  587. * @param object $quiz object
  588. * @return object quiz
  589. */
  590. function quiz_grade_item_delete($quiz) {
  591. global $CFG;
  592. require_once($CFG->libdir . '/gradelib.php');
  593. return grade_update('mod/quiz', $quiz->course, 'mod', 'quiz', $quiz->id, 0,
  594. null, array('deleted' => 1));
  595. }
  596. /**
  597. * Returns an array of users who have data in a given quiz
  598. *
  599. * @todo: deprecated - to be deleted in 2.2
  600. *
  601. * @param int $quizid the quiz id.
  602. * @return array of userids.
  603. */
  604. function quiz_get_participants($quizid) {
  605. global $CFG, $DB;
  606. return $DB->get_records_sql('
  607. SELECT DISTINCT userid, userid
  608. JOIN {quiz_attempts} qa
  609. WHERE a.quiz = ?', array($quizid));
  610. }
  611. /**
  612. * This standard function will check all instances of this module
  613. * and make sure there are up-to-date events created for each of them.
  614. * If courseid = 0, then every quiz event in the site is checked, else
  615. * only quiz events belonging to the course specified are checked.
  616. * This function is used, in its new format, by restore_refresh_events()
  617. *
  618. * @param int $courseid
  619. * @return bool
  620. */
  621. function quiz_refresh_events($courseid = 0) {
  622. global $DB;
  623. if ($courseid == 0) {
  624. if (!$quizzes = $DB->get_records('quiz')) {
  625. return true;
  626. }
  627. } else {
  628. if (!$quizzes = $DB->get_records('quiz', array('course' => $courseid))) {
  629. return true;
  630. }
  631. }
  632. foreach ($quizzes as $quiz) {
  633. quiz_update_events($quiz);
  634. }
  635. return true;
  636. }
  637. /**
  638. * Returns all quiz graded users since a given time for specified quiz
  639. */
  640. function quiz_get_recent_mod_activity(&$activities, &$index, $timestart,
  641. $courseid, $cmid, $userid = 0, $groupid = 0) {
  642. global $CFG, $COURSE, $USER, $DB;
  643. require_once('locallib.php');
  644. if ($COURSE->id == $courseid) {
  645. $course = $COURSE;
  646. } else {
  647. $course = $DB->get_record('course', array('id' => $courseid));
  648. }
  649. $modinfo =& get_fast_modinfo($course);
  650. $cm = $modinfo->cms[$cmid];
  651. $quiz = $DB->get_record('quiz', array('id' => $cm->instance));
  652. if ($userid) {
  653. $userselect = "AND u.id = :userid";
  654. $params['userid'] = $userid;
  655. } else {
  656. $userselect = '';
  657. }
  658. if ($groupid) {
  659. $groupselect = 'AND gm.groupid = :groupid';
  660. $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id';
  661. $params['groupid'] = $groupid;
  662. } else {
  663. $groupselect = '';
  664. $groupjoin = '';
  665. }
  666. $params['timestart'] = $timestart;
  667. $params['quizid'] = $quiz->id;
  668. if (!$attempts = $DB->get_records_sql("
  669. SELECT qa.*,
  670. u.firstname, u.lastname, u.email, u.picture, u.imagealt
  671. FROM {quiz_attempts} qa
  672. JOIN {user} u ON u.id = qa.userid
  673. $groupjoin
  674. WHERE qa.timefinish > :timestart
  675. AND qa.quiz = :quizid
  676. AND qa.preview = 0
  677. $userselect
  678. $groupselect
  679. ORDER BY qa.timefinish ASC", $params)) {
  680. return;
  681. }
  682. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  683. $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
  684. $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
  685. $grader = has_capability('mod/quiz:viewreports', $context);
  686. $groupmode = groups_get_activity_groupmode($cm, $course);
  687. if (is_null($modinfo->groups)) {
  688. // load all my groups and cache it in modinfo
  689. $modinfo->groups = groups_get_user_groups($course->id);
  690. }
  691. $usersgroups = null;
  692. $aname = format_string($cm->name, true);
  693. foreach ($attempts as $attempt) {
  694. if ($attempt->userid != $USER->id) {
  695. if (!$grader) {
  696. // Grade permission required
  697. continue;
  698. }
  699. if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
  700. if (is_null($usersgroups)) {
  701. $usersgroups = groups_get_all_groups($course->id,
  702. $attempt->userid, $cm->groupingid);
  703. if (is_array($usersgroups)) {
  704. $usersgroups = array_keys($usersgroups);
  705. } else {
  706. $usersgroups = array();
  707. }
  708. }
  709. if (!array_intersect($usersgroups, $modinfo->groups[$cm->id])) {
  710. continue;
  711. }
  712. }
  713. }
  714. $options = quiz_get_review_options($quiz, $attempt, $context);
  715. $tmpactivity = new stdClass();
  716. $tmpactivity->type = 'quiz';
  717. $tmpactivity->cmid = $cm->id;
  718. $tmpactivity->name = $aname;
  719. $tmpactivity->sectionnum = $cm->sectionnum;
  720. $tmpactivity->timestamp = $attempt->timefinish;
  721. $tmpactivity->content->attemptid = $attempt->id;
  722. $tmpactivity->content->attempt = $attempt->attempt;
  723. if (quiz_has_grades($quiz) && $options->marks >= question_display_options::MARK_AND_MAX) {
  724. $tmpactivity->content->sumgrades = quiz_format_grade($quiz, $attempt->sumgrades);
  725. $tmpactivity->content->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades);
  726. } else {
  727. $tmpactivity->content->sumgrades = null;
  728. $tmpactivity->content->maxgrade = null;
  729. }
  730. $tmpactivity->user->id = $attempt->userid;
  731. $tmpactivity->user->firstname = $attempt->firstname;
  732. $tmpactivity->user->lastname = $attempt->lastname;
  733. $tmpactivity->user->fullname = fullname($attempt, $viewfullnames);
  734. $tmpactivity->user->picture = $attempt->picture;
  735. $tmpactivity->user->imagealt = $attempt->imagealt;
  736. $tmpactivity->user->email = $attempt->email;
  737. $activities[$index++] = $tmpactivity;
  738. }
  739. }
  740. function quiz_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
  741. global $CFG, $OUTPUT;
  742. echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
  743. echo '<tr><td class="userpicture" valign="top">';
  744. echo $OUTPUT->user_picture($activity->user, array('courseid' => $courseid));
  745. echo '</td><td>';
  746. if ($detail) {
  747. $modname = $modnames[$activity->type];
  748. echo '<div class="title">';
  749. echo '<img src="' . $OUTPUT->pix_url('icon', $activity->type) . '" ' .
  750. 'class="icon" alt="' . $modname . '" />';
  751. echo '<a href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' .
  752. $activity->cmid . '">' . $activity->name . '</a>';
  753. echo '</div>';
  754. }
  755. echo '<div class="grade">';
  756. echo get_string('attempt', 'quiz', $activity->content->attempt);
  757. if (isset($activity->content->maxgrade)) {
  758. $grades = $activity->content->sumgrades . ' / ' . $activity->content->maxgrade;
  759. echo ': (<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?attempt=' .
  760. $activity->content->attemptid . '">' . $grades . '</a>)';
  761. }
  762. echo '</div>';
  763. echo '<div class="user">';
  764. echo '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $activity->user->id .
  765. '&amp;course=' . $courseid . '">' . $activity->user->fullname .
  766. '</a> - ' . userdate($activity->timestamp);
  767. echo '</div>';
  768. echo '</td></tr></table>';
  769. return;
  770. }
  771. /**
  772. * Pre-process the quiz options form data, making any necessary adjustments.
  773. * Called by add/update instance in this file.
  774. *
  775. * @param object $quiz The variables set on the form.
  776. */
  777. function quiz_process_options($quiz) {
  778. global $CFG;
  779. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  780. require_once($CFG->libdir . '/questionlib.php');
  781. $quiz->timemodified = time();
  782. // Quiz name.
  783. if (!empty($quiz->name)) {
  784. $quiz->name = trim($quiz->name);
  785. }
  786. // Password field - different in form to stop browsers that remember passwords
  787. // getting confused.
  788. $quiz->password = $quiz->quizpassword;
  789. unset($quiz->quizpassword);
  790. // Quiz feedback
  791. if (isset($quiz->feedbacktext)) {
  792. // Clean up the boundary text.
  793. for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) {
  794. if (empty($quiz->feedbacktext[$i]['text'])) {
  795. $quiz->feedbacktext[$i]['text'] = '';
  796. } else {
  797. $quiz->feedbacktext[$i]['text'] = trim($quiz->feedbacktext[$i]['text']);
  798. }
  799. }
  800. // Check the boundary value is a number or a percentage, and in range.
  801. $i = 0;
  802. while (!empty($quiz->feedbackboundaries[$i])) {
  803. $boundary = trim($quiz->feedbackboundaries[$i]);
  804. if (!is_numeric($boundary)) {
  805. if (strlen($boundary) > 0 && $boundary[strlen($boundary) - 1] == '%') {
  806. $boundary = trim(substr($boundary, 0, -1));
  807. if (is_numeric($boundary)) {
  808. $boundary = $boundary * $quiz->grade / 100.0;
  809. } else {
  810. return get_string('feedbackerrorboundaryformat', 'quiz', $i + 1);
  811. }
  812. }
  813. }
  814. if ($boundary <= 0 || $boundary >= $quiz->grade) {
  815. return get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1);
  816. }
  817. if ($i > 0 && $boundary >= $quiz->feedbackboundaries[$i - 1]) {
  818. return get_string('feedbackerrororder', 'quiz', $i + 1);
  819. }
  820. $quiz->feedbackboundaries[$i] = $boundary;
  821. $i += 1;
  822. }
  823. $numboundaries = $i;
  824. // Check there is nothing in the remaining unused fields.
  825. if (!empty($quiz->feedbackboundaries)) {
  826. for ($i = $numboundaries; $i < count($quiz->feedbackboundaries); $i += 1) {
  827. if (!empty($quiz->feedbackboundaries[$i]) &&
  828. trim($quiz->feedbackboundaries[$i]) != '') {
  829. return get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1);
  830. }
  831. }
  832. }
  833. for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) {
  834. if (!empty($quiz->feedbacktext[$i]['text']) &&
  835. trim($quiz->feedbacktext[$i]['text']) != '') {
  836. return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1);
  837. }
  838. }
  839. // Needs to be bigger than $quiz->grade because of '<' test in quiz_feedback_for_grade().
  840. $quiz->feedbackboundaries[-1] = $quiz->grade + 1;
  841. $quiz->feedbackboundaries[$numboundaries] = 0;
  842. $quiz->feedbackboundarycount = $numboundaries;
  843. }
  844. // Combing the individual settings into the review columns.
  845. $quiz->reviewattempt = quiz_review_option_form_to_db($quiz, 'attempt');
  846. $quiz->reviewcorrectness = quiz_review_option_form_to_db($quiz, 'correctness');
  847. $quiz->reviewmarks = quiz_review_option_form_to_db($quiz, 'marks');
  848. $quiz->reviewspecificfeedback = quiz_review_option_form_to_db($quiz, 'specificfeedback');
  849. $quiz->reviewgeneralfeedback = quiz_review_option_form_to_db($quiz, 'generalfeedback');
  850. $quiz->reviewrightanswer = quiz_review_option_form_to_db($quiz, 'rightanswer');
  851. $quiz->reviewoverallfeedback = quiz_review_option_form_to_db($quiz, 'overallfeedback');
  852. $quiz->reviewattempt |= mod_quiz_display_options::DURING;
  853. $quiz->reviewoverallfeedback &= ~mod_quiz_display_options::DURING;
  854. }
  855. /**
  856. * Helper function for {@link quiz_process_options()}.
  857. * @param object $fromform the sumbitted form date.
  858. * @param string $field one of the review option field names.
  859. */
  860. function quiz_review_option_form_to_db($fromform, $field) {
  861. static $times = array(
  862. 'during' => mod_quiz_display_options::DURING,
  863. 'immediately' => mod_quiz_display_options::IMMEDIATELY_AFTER,
  864. 'open' => mod_quiz_display_options::LATER_WHILE_OPEN,
  865. 'closed' => mod_quiz_display_options::AFTER_CLOSE,
  866. );
  867. $review = 0;
  868. foreach ($times as $whenname => $when) {
  869. $fieldname = $field . $whenname;
  870. if (isset($fromform->$fieldname)) {
  871. $review |= $when;
  872. unset($fromform->$fieldname);
  873. }
  874. }
  875. return $review;
  876. }
  877. /**
  878. * This function is called at the end of quiz_add_instance
  879. * and quiz_update_instance, to do the common processing.
  880. *
  881. * @param object $quiz the quiz object.
  882. */
  883. function quiz_after_add_or_update($quiz) {
  884. global $DB;
  885. $cmid = $quiz->coursemodule;
  886. // we need to use context now, so we need to make sure all needed info is already in db
  887. $DB->set_field('course_modules', 'instance', $quiz->id, array('id'=>$cmid));
  888. $context = get_context_instance(CONTEXT_MODULE, $cmid);
  889. // Save the feedback
  890. $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
  891. for ($i = 0; $i <= $quiz->feedbackboundarycount; $i++) {
  892. $feedback = new stdClass();
  893. $feedback->quizid = $quiz->id;
  894. $feedback->feedbacktext = $quiz->feedbacktext[$i]['text'];
  895. $feedback->feedbacktextformat = $quiz->feedbacktext[$i]['format'];
  896. $feedback->mingrade = $quiz->feedbackboundaries[$i];
  897. $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1];
  898. $feedback->id = $DB->insert_record('quiz_feedback', $feedback);
  899. $feedbacktext = file_save_draft_area_files((int)$quiz->feedbacktext[$i]['itemid'],
  900. $context->id, 'mod_quiz', 'feedback', $feedback->id,
  901. array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0),
  902. $quiz->feedbacktext[$i]['text']);
  903. $DB->set_field('quiz_feedback', 'feedbacktext', $feedbacktext,
  904. array('id' => $feedback->id));
  905. }
  906. // Update the events relating to this quiz.
  907. quiz_update_events($quiz);
  908. //update related grade item
  909. quiz_grade_item_update($quiz);
  910. }
  911. /**
  912. * This function updates the events associated to the quiz.
  913. * If $override is non-zero, then it updates only the events
  914. * associated with the specified override.
  915. *
  916. * @uses QUIZ_MAX_EVENT_LENGTH
  917. * @param object $quiz the quiz object.
  918. * @param object optional $override limit to a specific override
  919. */
  920. function quiz_update_events($quiz, $override = null) {
  921. global $DB;
  922. // Load the old events relating to this quiz.
  923. $conds = array('modulename'=>'quiz',
  924. 'instance'=>$quiz->id);
  925. if (!empty($override)) {
  926. // only load events for this override
  927. $conds['groupid'] = isset($override->groupid)? $override->groupid : 0;
  928. $conds['userid'] = isset($override->userid)? $override->userid : 0;
  929. }
  930. $oldevents = $DB->get_records('event', $conds);
  931. // Now make a todo list of all that needs to be updated
  932. if (empty($override)) {
  933. // We are updating the primary settings for the quiz, so we
  934. // need to add all the overrides
  935. $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id));
  936. // as well as the original quiz (empty override)
  937. $overrides[] = new stdClass();
  938. } else {
  939. // Just do the one override
  940. $overrides = array($override);
  941. }
  942. foreach ($overrides as $current) {
  943. $groupid = isset($current->groupid)? $current->groupid : 0;
  944. $userid = isset($current->userid)? $current->userid : 0;
  945. $timeopen = isset($current->timeopen)? $current->timeopen : $quiz->timeopen;
  946. $timeclose = isset($current->timeclose)? $current->timeclose : $quiz->timeclose;
  947. // only add open/close events for an override if they differ from the quiz default
  948. $addopen = empty($current->id) || !empty($current->timeopen);
  949. $addclose = empty($current->id) || !empty($current->timeclose);
  950. $event = new stdClass();
  951. $event->description = $quiz->intro;
  952. // Events module won't show user events when the courseid is nonzero
  953. $event->courseid = ($userid) ? 0 : $quiz->course;
  954. $event->groupid = $groupid;
  955. $event->userid = $userid;
  956. $event->modulename = 'quiz';
  957. $event->instance = $quiz->id;
  958. $event->timestart = $timeopen;
  959. $event->timeduration = max($timeclose - $timeopen, 0);
  960. $event->visible = instance_is_visible('quiz', $quiz);
  961. $event->eventtype = 'open';
  962. // Determine the event name
  963. if ($groupid) {
  964. $params = new stdClass();
  965. $params->quiz = $quiz->name;
  966. $params->group = groups_get_group_name($groupid);
  967. if ($params->group === false) {
  968. // group doesn't exist, just skip it
  969. continue;
  970. }
  971. $eventname = get_string('overridegroupeventname', 'quiz', $params);
  972. } else if ($userid) {
  973. $params = new stdClass();
  974. $params->quiz = $quiz->name;
  975. $eventname = get_string('overrideusereventname', 'quiz', $params);
  976. } else {
  977. $eventname = $quiz->name;
  978. }
  979. if ($addopen or $addclose) {
  980. if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) {
  981. // Single event for the whole quiz.
  982. if ($oldevent = array_shift($oldevents)) {
  983. $event->id = $oldevent->id;
  984. } else {
  985. unset($event->id);
  986. }
  987. $event->name = $eventname;
  988. // calendar_event::create will reuse a db record if the id field is set
  989. calendar_event::create($event);
  990. } else {
  991. // Separate start and end events.
  992. $event->timeduration = 0;
  993. if ($timeopen && $addopen) {
  994. if ($oldevent = array_shift($oldevents)) {
  995. $event->id = $oldevent->id;
  996. } else {
  997. unset($event->id);
  998. }
  999. $event->name = $eventname.' ('.get_string('quizopens', 'quiz').')';
  1000. // calendar_event::create will reuse a db record if the id field is set
  1001. calendar_event::create($event);
  1002. }
  1003. if ($timeclose && $addclose) {
  1004. if ($oldevent = array_shift($oldevents)) {
  1005. $event->id = $oldevent->id;
  1006. } else {
  1007. unset($event->id);
  1008. }
  1009. $event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')';
  1010. $event->timestart = $timeclose;
  1011. $event->eventtype = 'close';
  1012. calendar_event::create($event);
  1013. }
  1014. }
  1015. }
  1016. }
  1017. // Delete any leftover events
  1018. foreach ($oldevents as $badevent) {
  1019. $badevent = calendar_event::load($badevent);
  1020. $badevent->delete();
  1021. }
  1022. }
  1023. /**
  1024. * @return array
  1025. */
  1026. function quiz_get_view_actions() {
  1027. return array('view', 'view all', 'report', 'review');
  1028. }
  1029. /**
  1030. * @return array
  1031. */
  1032. function quiz_get_post_actions() {
  1033. return array('attempt', 'close attempt', 'preview', 'editquestions',
  1034. 'delete attempt', 'manualgrade');
  1035. }
  1036. /**
  1037. * @param array $questionids of question ids.
  1038. * @return bool whether any of these questions are used by any instance of this module.
  1039. */
  1040. function quiz_questions_in_use($questionids) {
  1041. global $DB, $CFG;
  1042. require_once($CFG->libdir . '/questionlib.php');
  1043. list($test, $params) = $DB->get_in_or_equal($questionids);
  1044. return $DB->record_exists_select('quiz_question_instances',
  1045. 'question ' . $test, $params) || question_engine::questions_in_use(
  1046. $questionids, new qubaid_join('{quiz_attempts} quiza',
  1047. 'quiza.uniqueid', 'quiza.preview = 0'));
  1048. }
  1049. /**
  1050. * Implementation of the function for printing the form elements that control
  1051. * whether the course reset functionality affects the quiz.
  1052. *
  1053. * @param $mform the course reset form that is being built.
  1054. */
  1055. function quiz_reset_course_form_definition($mform) {
  1056. $mform->addElement('header', 'quizheader', get_string('modulenameplural', 'quiz'));
  1057. $mform->addElement('advcheckbox', 'reset_quiz_attempts',
  1058. get_string('removeallquizattempts', 'quiz'));
  1059. }
  1060. /**
  1061. * Course reset form defaults.
  1062. * @return array the defaults.
  1063. */
  1064. function quiz_reset_course_form_defaults($course) {
  1065. return array('reset_quiz_attempts' => 1);
  1066. }
  1067. /**
  1068. * Removes all grades from gradebook
  1069. *
  1070. * @param int $courseid
  1071. * @param string optional type
  1072. */
  1073. function quiz_reset_gradebook($courseid, $type='') {
  1074. global $CFG, $DB;
  1075. $quizzes = $DB->get_records_sql("
  1076. SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid
  1077. FROM {modules} m
  1078. JOIN {course_modules} cm ON m.id = cm.module
  1079. JOIN {quiz} q ON cm.instance = q.id
  1080. WHERE m.name = 'quiz' AND cm.course = ?", array($courseid));
  1081. foreach ($quizzes as $quiz) {
  1082. quiz_grade_item_update($quiz, 'reset');
  1083. }
  1084. }
  1085. /**
  1086. * Actual implementation of the reset course functionality, delete all the
  1087. * quiz attempts for course $data->courseid, if $data->reset_quiz_attempts is
  1088. * set and true.
  1089. *
  1090. * Also, move the quiz open and close dates, if the course start date is changing.
  1091. *
  1092. * @param object $data the data submitted from the reset course.
  1093. * @return array status array
  1094. */
  1095. function quiz_reset_userdata($data) {
  1096. global $CFG, $DB;
  1097. require_once($CFG->libdir.'/questionlib.php');
  1098. $componentstr = get_string('modulenameplural', 'quiz');
  1099. $status = array();
  1100. // Delete attempts.
  1101. if (!empty($data->reset_quiz_attempts)) {
  1102. require_once($CFG->libdir . '/questionlib.php');
  1103. question_engine::delete_questions_usage_by_activities(new qubaid_join(
  1104. '{quiz_attempts} quiza JOIN {quiz} quiz ON quiza.quiz = quiz.id',
  1105. 'quiza.uniqueid', 'quiz.course = :quizcourseid',
  1106. array('quizcourseid' => $data->courseid)));
  1107. $DB->delete_records_select('quiz_attempts',
  1108. 'quiz IN (SELECT id FROM {quiz} WHERE course = ?)', array($data->courseid));
  1109. $status[] = array(
  1110. 'component' => $componentstr,
  1111. 'item' => get_string('attemptsdeleted', 'quiz'),
  1112. 'error' => false);
  1113. // Remove all grades from gradebook
  1114. if (empty($data->reset_gradebook_grades)) {
  1115. quiz_reset_gradebook($data->courseid);
  1116. }
  1117. $status[] = array(
  1118. 'component' => $componentstr,
  1119. 'item' => get_string('attemptsdeleted', 'quiz'),
  1120. 'error' => false);
  1121. }
  1122. // Updating dates - shift may be negative too
  1123. if ($data->timeshift) {
  1124. shift_course_mod_dates('quiz', array('timeopen', 'timeclose'),
  1125. $data->timeshift, $data->courseid);
  1126. $status[] = array(
  1127. 'component' => $componentstr,
  1128. 'item' => get_string('openclosedatesupdated', 'quiz'),
  1129. 'error' => false);
  1130. }
  1131. return $status;
  1132. }
  1133. /**
  1134. * Checks whether the current user is allowed to view a file uploaded in a quiz.
  1135. * Teachers can view any from their courses, students can only view their own.
  1136. *
  1137. * @param int $attemptuniqueid int attempt id
  1138. * @param int $questionid int question id
  1139. * @return bool to indicate access granted or denied
  1140. */
  1141. function quiz_check_file_access($attemptuniqueid, $questionid, $context = null) {
  1142. global $USER, $DB, $CFG;
  1143. require_once(dirname(__FILE__).'/attemptlib.php');
  1144. require_once(dirname(__FILE__).'/locallib.php');
  1145. $attempt = $DB->get_record('quiz_attempts', array('uniqueid' => $attemptuniqueid));
  1146. $attemptobj = quiz_attempt::create($attempt->id);
  1147. // does question exist?
  1148. if (!$question = $DB->get_record('question', array('id' => $questionid))) {
  1149. return false;
  1150. }
  1151. if ($context === null) {
  1152. $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz));
  1153. $cm = get_coursemodule_from_id('quiz', $quiz->id);
  1154. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  1155. }
  1156. // Load those questions and the associated states.
  1157. $attemptobj->load_questions(array($questionid));
  1158. $attemptobj->load_question_states(array($questionid));
  1159. // obtain state
  1160. $state = $attemptobj->get_question_state($questionid);
  1161. // obtain questoin
  1162. $question = $attemptobj->get_question($questionid);
  1163. // access granted if the current user submitted this file
  1164. if ($attempt->userid != $USER->id) {
  1165. return false;
  1166. }
  1167. // access granted if the current user has permission to grade quizzes in this course
  1168. if (!(has_capability('mod/quiz:viewreports', $context) ||
  1169. has_capability('mod/quiz:grade', $context))) {
  1170. return false;
  1171. }
  1172. return array($question, $state, array());
  1173. }
  1174. /**
  1175. * Prints quiz summaries on MyMoodle Page
  1176. * @param arry $courses
  1177. * @param array $htmlarray
  1178. */
  1179. function quiz_print_overview($courses, &$htmlarray) {
  1180. global $USER, $CFG;
  1181. // These next 6 Lines are constant in all modules (just change module name)
  1182. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  1183. return array();
  1184. }
  1185. if (!$quizzes = get_all_instances_in_courses('quiz', $courses)) {
  1186. return;
  1187. }
  1188. // Fetch some language strings outside the main loop.
  1189. $strquiz = get_string('modulename', 'quiz');
  1190. $strnoattempts = get_string('noattempts', 'quiz');
  1191. // We want to list quizzes that are currently available, and which have a close date.
  1192. // This is the same as what the lesson does, and the dabate is in MDL-10568.
  1193. $now = time();
  1194. foreach ($quizzes as $quiz) {
  1195. if ($quiz->timeclose >= $now && $quiz->timeopen < $now) {
  1196. // Give a link to the quiz, and the deadline.
  1197. $str = '<div class="quiz overview">' .
  1198. '<div class="name">' . $strquiz . ': <a ' .
  1199. ($quiz->visible ? '' : ' class="dimmed"') .
  1200. ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' .
  1201. $quiz->coursemodule . '">' .
  1202. $quiz->name . '</a></div>';
  1203. $str .= '<div class="info">' . get_string('quizcloseson', 'quiz',
  1204. userdate($quiz->timeclose)) . '</div>';
  1205. // Now provide more information depending on the uers's role.
  1206. $context = get_context_instance(CONTEXT_MODULE, $quiz->coursemodule);
  1207. if (has_capability('mod/quiz:viewreports', $context)) {
  1208. // For teacher-like people, show a summary of the number of student attempts.
  1209. // The $quiz objects returned by get_all_instances_in_course have the necessary $cm
  1210. // fields set to make the following call work.
  1211. $str .= '<div class="info">' .
  1212. quiz_num_attempt_summary($quiz, $quiz, true) . '</div>';
  1213. } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
  1214. $context)) { // Student
  1215. // For student-like people, tell them how many attempts they have made.
  1216. if (isset($USER->id) &&
  1217. ($attempts = quiz_get_user_attempts($quiz->id, $USER->id))) {
  1218. $numattempts = count($attempts);
  1219. $str .= '<div class="info">' .
  1220. get_string('numattemptsmade', 'quiz', $numattempts) . '</div>';
  1221. } else {
  1222. $str .= '<div class="info">' . $strnoattempts

Large files files are truncated, but you can click here to view the full file