Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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
namespace mod_quiz\task;
18
 
19
use core\task\scheduled_task;
20
use mod_quiz\quiz_settings;
21
use question_engine;
22
 
23
/**
24
 * Pre-create attempts for quizzes that have passed their threshold.
25
 *
26
 * @package   mod_quiz
27
 * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
28
 * @author    Mark Johnson <mark.johnson@catalyst-eu.net>
29
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class precreate_attempts extends scheduled_task {
32
    /**
33
     * Create new instance of the task.
34
     *
35
     * @param int $maxruntime The number of seconds to allow the task to start processing new quizzes.
36
     */
37
    public function __construct(
38
        /** @var int $maxruntime The number of seconds to allow the task to start processing new quizzes. */
39
        protected int $maxruntime = 600,
40
    ) {
41
    }
42
 
43
    #[\Override]
44
    public function get_name(): string {
45
        return get_string('precreatetask', 'mod_quiz');
46
    }
47
 
48
    /**
49
     * Pre-create quiz attempts for configured quizzes.
50
     *
51
     * Find all quizzes with timeopen where the current time is later
52
     * than timeopen-precreateperiod, the quiz has questions, but no attempts.
53
     *
54
     * If the precreateperiod setting is unlocked, also filter by quizzes with precreateattempts enabled.
55
     *
56
     * Find all the users enrolled on the course who can attempt the quiz and create an attempt
57
     * in the NOT_STARTED state.
58
     *
59
     * This will run for $this->maxruntime seconds, then stop to avoid hogging the cron process. Remaining quizzes will be
60
     * processed on subsequent runs.
61
     *
62
     * @return void
63
     */
64
    public function execute(): void {
65
        global $DB;
66
        $starttime = time();
67
        $precreateperiod = (int)get_config('quiz', 'precreateperiod');
68
        $precreatedefault  = (int)get_config('quiz', 'precreateattempts');
69
        if ($precreateperiod === 0) {
70
            mtrace('Pre-creation of quiz attempts is disabled. Nothing to do.');
71
            return;
72
        }
73
        $sql = "
74
            SELECT DISTINCT q.id, q.name, q.course, q.timeopen
75
              FROM {quiz} q
76
              JOIN {quiz_slots} qs ON q.id = qs.quizid
77
         LEFT JOIN {quiz_attempts} qa ON q.id = qa.quiz
78
             WHERE qa.id IS NULL
79
                   AND q.timeopen > :now
80
                   AND q.timeopen < :threshold
81
                   AND (
82
                       q.precreateattempts = :precreateattempts
83
                       OR (1 = :precreatedefault AND q.precreateattempts IS NULL)
84
                   )
85
          ORDER BY q.timeopen ASC";
86
        $params = [
87
            'now' => $starttime,
88
            'threshold' => $starttime + $precreateperiod,
89
            'precreateattempts' => 1,
90
            'precreatedefault' => $precreatedefault,
91
        ];
92
 
93
        $quizzes = $DB->get_records_sql($sql, $params);
94
        mtrace('Found ' . count($quizzes) . ' quizzes to create attempts for.');
95
        $quizcount = 0;
96
        foreach ($quizzes as $quiz) {
97
            $transaction = $DB->start_delegated_transaction();
98
            try {
99
                $quizstart = microtime(true);
100
                mtrace('Creating attempts for ' . $quiz->name);
101
                $attemptcount = self::precreate_attempts_for_quiz($quiz->id, $quiz->course);
102
                $quizend = microtime(true);
103
                $quizduration = round($quizend - $quizstart, 2);
104
                mtrace('Created ' . $attemptcount . ' attempts for ' . $quiz->name . ' in ' . $quizduration . ' seconds');
105
                $quizcount++;
106
                $transaction->allow_commit();
107
            } catch (\Throwable $e) {
108
                mtrace('Failed to create attempts for ' . $quiz->name);
109
                $transaction->rollback($e);
110
            }
111
 
112
            if (microtime(true) - $starttime > $this->maxruntime) {
113
                // Stop to let other tasks run, then do some more next run.
114
                mtrace('Time limit reached.');
115
                break;
116
            }
117
        }
118
        mtrace('Created attempts for ' . $quizcount . ' quizzes.');
119
    }
120
 
121
    /**
122
     * Pre-create attempts for a quiz.
123
     *
124
     * @param int $quizid
125
     * @param int $courseid
126
     * @return int The number of attempts created.
127
     */
128
    public static function precreate_attempts_for_quiz(int $quizid, int $courseid): int {
129
        global $DB;
130
        $coursecontext = \context_course::instance($courseid);
131
        $users = get_enrolled_users($coursecontext, 'mod/quiz:attempt');
132
        $attemptcount = 0;
133
        $timenow = time();
134
        foreach ($users as $user) {
135
            if ($DB->record_exists('quiz_attempts', ['userid' => $user->id, 'quiz' => $quizid])) {
136
                // Last-minute safety check in case the quiz opened and the user started an attempt since the task started.
137
                continue;
138
            }
139
            $quizobj = quiz_settings::create($quizid, $user->id);
140
            $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
141
            $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
142
            $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user->id);
143
            quiz_start_new_attempt(
144
                $quizobj,
145
                $quba,
146
                $attempt,
147
                1,
148
                $timenow,
149
            );
150
            quiz_attempt_save_not_started($quba, $attempt);
151
            $attemptcount++;
152
        }
153
        return $attemptcount;
154
    }
155
}