PageRenderTime 10ms CodeModel.GetById 107ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/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

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

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