Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace quiz_statistics\task;

use core\dml\sql_join;
use quiz_attempt;
< use quiz;
use quiz_statistics_report; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php'); require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php'); require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php'); /** * Re-calculate question statistics. * * @package quiz_statistics * @copyright 2022 Catalyst IT Australia Pty Ltd * @author Nathan Nguyen <nathannguyen@catalyst-au.net> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */
< class recalculate extends \core\task\scheduled_task { < /** @var int the maximum length of time one instance of this task will run. */ < const TIME_LIMIT = 3600;
> class recalculate extends \core\task\adhoc_task { > /** > * The time to delay queued runs by, to prevent repeated recalculations. > */ > const DELAY = HOURSECS; > > /** > * Create a new instance of the task. > * > * This sets the properties so that only one task will be queued at a time for a given quiz. > * > * @param int $quizid > * @return recalculate > */ > public static function instance(int $quizid): recalculate { > $task = new self(); > $task->set_component('quiz_statistics'); > $task->set_custom_data((object)[ > 'quizid' => $quizid, > ]); > return $task; > } >
public function get_name(): string { return get_string('recalculatetask', 'quiz_statistics'); } public function execute(): void { global $DB;
< $stoptime = time() + self::TIME_LIMIT;
$dateformat = get_string('strftimedatetimeshortaccurate', 'core_langconfig');
< < // TODO: MDL-75197, add quizid in quiz_statistics so that it is simpler to find quizzes for stats calculation. < // Only calculate stats for quizzes which have recently finished attempt. < $latestattempts = $DB->get_records_sql(" < SELECT q.id AS quizid, < q.name AS quizname, < q.grademethod AS quizgrademethod, < c.id AS courseid, < c.shortname AS courseshortname, < MAX(quiza.timefinish) AS mostrecentattempttime, < COUNT(1) AS numberofattempts < < FROM {quiz_attempts} quiza < JOIN {quiz} q ON q.id = quiza.quiz < JOIN {course} c ON c.id = q.course < < WHERE quiza.preview = 0 < AND quiza.state = :quizstatefinished < < GROUP BY q.id, q.name, q.grademethod, c.id, c.shortname < ORDER BY MAX(quiza.timefinish) DESC < ", ["quizstatefinished" => quiz_attempt::FINISHED]); < < $anyexception = null; < foreach ($latestattempts as $latestattempt) { < if (time() >= $stoptime) { < mtrace("This task has been running for more than " . < format_time(self::TIME_LIMIT) . " so stopping this execution."); < break; < } < < // Check if there is any existing question stats, and it has been calculated after latest quiz attempt. < $qubaids = quiz_statistics_qubaids_condition($latestattempt->quizid, < new sql_join(), $latestattempt->quizgrademethod); < $lateststatstime = $DB->get_field('quiz_statistics', 'COALESCE(MAX(timemodified), 0)', < ['hashcode' => $qubaids->get_hash_code()]); < < $quizinfo = "'$latestattempt->quizname' ($latestattempt->quizid) in course " . < "$latestattempt->courseshortname ($latestattempt->courseid) has most recent attempt finished at " . < userdate($latestattempt->mostrecentattempttime, $dateformat); < if ($lateststatstime) { < $quizinfo .= " and statistics from " . userdate($lateststatstime, $dateformat); < } < < if ($lateststatstime >= $latestattempt->mostrecentattempttime) { < mtrace(" " . $quizinfo . " so nothing to do."); < continue;
> $data = $this->get_custom_data(); > $quiz = $DB->get_record('quiz', ['id' => $data->quizid]); > if (!$quiz) { > mtrace('Could not find quiz with ID ' . $data->quizid . '.'); > return; > } > $course = $DB->get_record('course', ['id' => $quiz->course]); > if (!$course) { > mtrace('Could not find course with ID ' . $quiz->course . '.'); > return; > } > $attemptcount = $DB->count_records('quiz_attempts', ['quiz' => $data->quizid, 'state' => quiz_attempt::FINISHED]); > if ($attemptcount === 0) { > mtrace('Could not find any finished attempts for course with ID ' . $data->quizid . '.'); > return;
}
< // OK, so we need to calculate for this quiz. < mtrace(" " . $quizinfo . " so re-calculating statistics for $latestattempt->numberofattempts attempts, start time " .
> mtrace("Re-calculating statistics for quiz {$quiz->name} ({$quiz->id}) " . > "from course {$course->shortname} ({$course->id}) with {$attemptcount} attempts, start time " .
userdate(time(), $dateformat) . " ...");
< try { < $quizobj = quiz::create($latestattempt->quizid);
> $qubaids = quiz_statistics_qubaids_condition( > $quiz->id, > new sql_join(), > $quiz->grademethod > ); >
$report = new quiz_statistics_report(); $report->clear_cached_data($qubaids);
< $report->calculate_questions_stats_for_question_bank($quizobj->get_quizid()); < mtrace(" Calculations completed at " . userdate(time(), $dateformat) . "."); < < } catch (\Throwable $e) { < // We don't want an exception from one quiz to stop processing of other quizzes. < mtrace_exception($e); < $anyexception = $e; < }
> $report->calculate_questions_stats_for_question_bank($quiz->id); > mtrace(' Calculations completed at ' . userdate(time(), $dateformat) . '.');
}
< if ($anyexception) { < // If there was any error, ensure the task fails. < throw $anyexception; < }
> /** > * Queue an instance of this task to happen after a delay. > * > * Multiple events may happen over a short period that require a recalculation. Rather than > * run the recalculation each time, this will queue a single run of the task for a given quiz, > * within the delay period. > * > * @param int $quizid The quiz to run the recalculation for. > * @return bool true of the task was queued. > */ > public static function queue_future_run(int $quizid): bool { > $task = self::instance($quizid); > $task->set_next_run_time(time() + self::DELAY); > return \core\task\manager::queue_adhoc_task($task, true);
} }