/** * Creates a normal quiz attempt from a (typically paper copy) usage, so the student can view the results of a paper test * as though they had taken it on Moodle, including feedback. This also allows one to theoretically allow subsequent attempts * at the same quiz on Moodle, using options such as "each attempt buiilds on the last", building on the paper copies. * * @param question_usage_by_actvitity $usage The question_usage_by_activity object which composes the paper copy. * @param int $user_id If provided, the attempt will be owned by the user with the given ID, instead of the current user. * @param bool $finished If set, the attempt will finished and committed to the database as soon as it is created; this assumes the QUBA has already been populated with responses. * @param bool $commit If set, the attempt will be committed to the database after creation. If $finished is set, the value of $commit will be ignored, and the row will be committed regardless. * * @return array Returns the newly created attempt's raw data. (In other words, does not return a quiz_attempt object.) */ protected function build_attempt_from_usage($usage, $user_id = null, $finished = false, $commit = false, $attempt_number = null) { global $DB, $USER; //get the current time $time_now = time(); //start a new attempt $attempt = new stdClass(); $attempt->quiz = $this->quiz->id; $attempt->preview = 0; $attempt->timestart = $time_now; $attempt->timefinish = 0; $attempt->timemodified = $time_now; //associate the attempt with the usage $attempt->uniqueid = $usage->get_id(); //and set the attempt's owner, if specified if ($user_id !== null) { $attempt->userid = $user_id; } else { $attempt->userid = $USER->id; } //if no attempt number was specified, automatically detect one if ($attempt_number === null) { //determine the maximum attempt value for that user/quiz combo $max_attempt = $DB->get_records_sql('SELECT max("attempt") AS max FROM {quiz_attempts} WHERE "userid" = ? AND "quiz" = ?', array($user_id, $this->quiz->id)); $max_attempt = reset($max_attempt); //if no attempts exist, let this be the first attempt if (empty($max_attempt->max)) { $attempt_number = 1; } else { $attempt_number = $max_attempt->max + 1; } } //set the attempt number $attempt->attempt = $attempt_number; //build the attempt layout $attempt->layout = implode(',', $usage->get_slots()); //if requested, commit the attempt to the database if ($commit || $finished) { //and use it to save the usage and attempt question_engine::save_questions_usage_by_activity($usage); $attempt->id = $DB->insert_record('quiz_attempts', $attempt); } //if requested, finish the attempt immediately if ($finished) { $raw_course = $DB->get_record('course', array('id' => $this->course->id)); //wrap the attempt data in an quiz_attempt object, and ask it to finish $attempt->currentpage = 0; $attempt_object = new quiz_attempt($attempt, $this->quiz, $this->cm, $this->course, true); // $attempt_object->finish_attempt($time_now); // $attempt_object->process_submitted_actions($time_now, false); $attempt_object->process_finish($time_now, $finished); } //return the attempt object return $attempt; }
/** * Force submit attempts for this quiz, exactly which attempts are submitted is * controlled by the parameters. * @param object $quiz the quiz settings. * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real. * @param array $groupstudents blank for all attempts, otherwise submit attempts * for these users. * @param array $attemptids blank for all attempts, otherwise only submit * attempts whose id is in this list. */ protected function forcesubmit_attempts($quiz, $dryrun = false, $groupstudents = array(), $attemptids = array()) { global $DB; $this->unlock_session(); $where = "quiz = ? AND preview = 0 AND state IN ('inprogress', 'overdue', 'abandoned')"; $params = array($quiz->id); if ($groupstudents) { list($usql, $uparams) = $DB->get_in_or_equal($groupstudents); $where .= " AND userid {$usql}"; $params = array_merge($params, $uparams); } if ($attemptids) { list($asql, $aparams) = $DB->get_in_or_equal($attemptids); $where .= " AND id {$asql}"; $params = array_merge($params, $aparams); } $count = $DB->count_records_select('quiz_attempts', $where, $params); $attemptstoprocess = $DB->get_recordset_select('quiz_attempts', $where, $params); $cm = get_coursemodule_from_instance('quiz', $quiz->id); $course = $DB->get_record('course', array('id' => $cm->course)); $timenow = time(); $progressbar = new progress_bar('quiz_overview_forcesubmit', 500, true); $a = array('count' => $count, 'done' => 0); foreach ($attemptstoprocess as $attempt) { try { // Test that we have proper quiz and coure data. if (!$quiz || $attempt->quiz != $quiz->id) { throw new moodle_exception('Bad quiz or attempt in request.'); $cm = get_coursemodule_from_instance('quiz', $attempt->quiz); } // Trigger any transitions that are required. $attemptobj = new quiz_attempt($attempt, $quiz, $cm, $course); $attemptobj->process_finish($timenow, false); $a['done']++; $progressbar->update($a['done'], $a['count'], get_string('forcesubmittingattemptxofy', 'quiz_overview', $a)); } catch (moodle_exception $e) { // If an error occurs while processing one attempt, don't let that kill cron. mtrace("Error while processing attempt {$attempt->id} at {$attempt->quiz} quiz:"); mtrace($e->getMessage()); mtrace($e->getTraceAsString()); // Close down any currently open transactions, otherwise one error // will stop following DB changes from being committed. $DB->force_transaction_rollback(); } } $attemptstoprocess->close(); if (!$dryrun) { $this->update_overall_grades($quiz); } }