first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,152 @@
<?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 mod_quiz\task;
defined('MOODLE_INTERNAL') || die();
use context_module;
use core_user;
use mod_quiz\quiz_attempt;
use moodle_recordset;
use question_display_options;
use mod_quiz\question\display_options;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Cron Quiz Notify Attempts Graded Task.
*
* @package mod_quiz
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
*/
class quiz_notify_attempt_manual_grading_completed extends \core\task\scheduled_task {
/**
* @var int|null For using in unit testing only. Override the time we consider as now.
*/
protected $forcedtime = null;
#[\Override]
public function get_name(): string {
return get_string('notifyattemptsgradedtask', 'mod_quiz');
}
/**
* To let this class be unit tested, we wrap all accesses to the current time in this method.
*
* @return int The current time.
*/
protected function get_time(): int {
if (PHPUNIT_TEST && $this->forcedtime !== null) {
return $this->forcedtime;
}
return time();
}
/**
* For testing only, pretend the current time is different.
*
* @param int $time The time to set as the current time.
*/
public function set_time_for_testing(int $time): void {
if (!PHPUNIT_TEST) {
throw new \coding_exception('set_time_for_testing should only be used in unit tests.');
}
$this->forcedtime = $time;
}
#[\Override]
public function execute(): void {
global $DB;
mtrace('Looking for quiz attempts which may need a graded notification sent...');
$attempts = $this->get_list_of_attempts();
$course = null;
$quiz = null;
$cm = null;
foreach ($attempts as $attempt) {
mtrace('Checking attempt ' . $attempt->id . ' at quiz ' . $attempt->quiz . '.');
if (!$quiz || $attempt->quiz != $quiz->id) {
$quiz = $DB->get_record('quiz', ['id' => $attempt->quiz], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
$quizcontext = context_module::instance($cm->id);
}
if (!$course || $course->id != $quiz->course) {
$course = get_course($quiz->course);
}
$quiz = quiz_update_effective_access($quiz, $attempt->userid);
$attemptobj = new quiz_attempt($attempt, $quiz, $cm, $course, false);
$options = display_options::make_from_quiz($quiz, quiz_attempt_state($quiz, $attempt));
if ($options->manualcomment == question_display_options::HIDDEN) {
// User cannot currently see the feedback, so don't message them.
// However, this may change in future, so leave them on the list.
mtrace('Not sending an email because manualcomment review option is not set.');
continue;
}
if (!has_capability('mod/quiz:emailnotifyattemptgraded', $quizcontext, $attempt->userid, false)) {
// User not eligible to get a notification. Mark them done while doing nothing.
mtrace('Not sending an email because user does not have mod/quiz:emailnotifyattemptgraded capability.');
$DB->set_field('quiz_attempts', 'gradednotificationsenttime', $attempt->timefinish, ['id' => $attempt->id]);
continue;
}
// OK, send notification.
mtrace('Sending email to user ' . $attempt->userid . '...');
$ok = quiz_send_notify_manual_graded_message($attemptobj, core_user::get_user($attempt->userid));
if ($ok) {
mtrace('Send email successfully!');
$attempt->gradednotificationsenttime = $this->get_time();
$DB->set_field('quiz_attempts', 'gradednotificationsenttime', $attempt->gradednotificationsenttime,
['id' => $attempt->id]);
$attemptobj->fire_attempt_manual_grading_completed_event();
}
}
$attempts->close();
}
/**
* Get a number of records as an array of quiz_attempts using a SQL statement.
*
* @return moodle_recordset Of quiz_attempts that need to be processed.
*/
public function get_list_of_attempts(): moodle_recordset {
global $DB;
$delaytime = $this->get_time() - get_config('quiz', 'notifyattemptgradeddelay');
$sql = "SELECT qa.*
FROM {quiz_attempts} qa
JOIN {quiz} quiz ON quiz.id = qa.quiz
WHERE qa.state = 'finished'
AND qa.gradednotificationsenttime IS NULL
AND qa.sumgrades IS NOT NULL
AND qa.timemodified < :delaytime
ORDER BY quiz.course, qa.quiz";
return $DB->get_recordset_sql($sql, ['delaytime' => $delaytime]);
}
}
@@ -0,0 +1,155 @@
<?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/>.
/**
* Update Overdue Attempts Task
*
* @package mod_quiz
* @copyright 2017 Michael Hughes
* @author Michael Hughes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\task;
use mod_quiz\quiz_attempt;
use moodle_exception;
use moodle_recordset;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Update Overdue Attempts Task
*
* @package mod_quiz
* @copyright 2017 Michael Hughes
* @author Michael Hughes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
*/
class update_overdue_attempts extends \core\task\scheduled_task {
public function get_name(): string {
return get_string('updateoverdueattemptstask', 'mod_quiz');
}
/**
* Close off any overdue attempts.
*/
public function execute() {
$timenow = time();
$processto = $timenow - get_config('quiz', 'graceperiodmin');
mtrace(' Looking for quiz overdue quiz attempts...');
list($count, $quizcount) = $this->update_all_overdue_attempts($timenow, $processto);
mtrace(' Considered ' . $count . ' attempts in ' . $quizcount . ' quizzes.');
}
/**
* Do the processing required.
*
* @param int $timenow the time to consider as 'now' during the processing.
* @param int $processto only process attempt with timecheckstate longer ago than this.
* @return array with two elements, the number of attempt considered, and how many different quizzes that was.
*/
public function update_all_overdue_attempts(int $timenow, int $processto): array {
global $DB;
$attemptstoprocess = $this->get_list_of_overdue_attempts($processto);
$course = null;
$quiz = null;
$cm = null;
$count = 0;
$quizcount = 0;
foreach ($attemptstoprocess as $attempt) {
try {
// If we have moved on to a different quiz, fetch the new data.
if (!$quiz || $attempt->quiz != $quiz->id) {
$quiz = $DB->get_record('quiz', ['id' => $attempt->quiz], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
$quizcount += 1;
}
// If we have moved on to a different course, fetch the new data.
if (!$course || $course->id != $quiz->course) {
$course = get_course($quiz->course);
}
// Make a specialised version of the quiz settings, with the relevant overrides.
$quizforuser = clone($quiz);
$quizforuser->timeclose = $attempt->usertimeclose;
$quizforuser->timelimit = $attempt->usertimelimit;
// Trigger any transitions that are required.
$attemptobj = new quiz_attempt($attempt, $quizforuser, $cm, $course);
$attemptobj->handle_if_time_expired($timenow, false);
$count += 1;
} 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();
return [$count, $quizcount];
}
/**
* Get a recordset of all the attempts that need to be processed now.
*
* (Only public to allow unit testing. Do not use!)
*
* @param int $processto timestamp to process up to.
* @return moodle_recordset of quiz_attempts that need to be processed because time has
* passed, sorted by courseid then quizid.
*/
public function get_list_of_overdue_attempts(int $processto): moodle_recordset {
global $DB;
// SQL to compute timeclose and timelimit for each attempt.
$quizausersql = quiz_get_attempt_usertime_sql(
"iquiza.state IN ('inprogress', 'overdue') AND iquiza.timecheckstate <= :iprocessto");
// This query should have all the quiz_attempts columns.
return $DB->get_recordset_sql("
SELECT quiza.*,
quizauser.usertimeclose,
quizauser.usertimelimit
FROM {quiz_attempts} quiza
JOIN {quiz} quiz ON quiz.id = quiza.quiz
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
WHERE quiza.state IN ('inprogress', 'overdue')
AND quiza.timecheckstate <= :processto
ORDER BY quiz.course, quiza.quiz",
['processto' => $processto, 'iprocessto' => $processto]);
}
}