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
+205
View File
@@ -0,0 +1,205 @@
<?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/>.
/**
* Adhoc task abstract class.
*
* All background tasks should extend this class.
*
* @package core
* @category task
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Abstract class defining an adhoc task.
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class adhoc_task extends task_base {
/** @var string $customdata - Custom data required for when this task is executed. */
private $customdata = '';
/** @var integer|null $id - Adhoc tasks each have their own database record id. */
private $id = null;
/** @var integer|null $userid - Adhoc tasks may choose to run as a specific user. */
private $userid = null;
/** @var \core\lock\lock The concurrency task lock for this task. */
private $concurrencylock = null;
/** @var int $attemptsavailable - The remaining attempts of the task. */
private $attemptsavailable = 12;
/**
* Provide default implementation of the task name for backward compatibility. Extending classes are expected to implement
* this method to provide a descriptive name for the task (shown to admins)
*
* @return string
*/
public function get_name() {
$classparts = explode('\\', get_called_class());
$classname = end($classparts);
// Try to make human readable, capitalized and with spaces.
return ucfirst(str_replace('_', ' ', $classname));
}
/**
* Setter for $id.
* @param int|null $id
*/
public function set_id($id) {
$this->id = $id;
}
/**
* Getter for $userid.
* @return int|null $userid
*/
public function get_userid() {
return $this->userid;
}
/**
* Setter for $customdata.
* @param mixed $customdata (anything that can be handled by json_encode)
*/
public function set_custom_data($customdata) {
$this->customdata = json_encode($customdata);
}
/**
* Alternate setter for $customdata. Expects the data as a json_encoded string.
* @param string $customdata json_encoded string
*/
public function set_custom_data_as_string($customdata) {
$this->customdata = $customdata;
}
/**
* Getter for $customdata.
* @return mixed (anything that can be handled by json_decode).
*/
public function get_custom_data() {
return json_decode($this->customdata);
}
/**
* Alternate getter for $customdata.
* @return string this is the raw json encoded version.
*/
public function get_custom_data_as_string() {
return $this->customdata;
}
/**
* Getter for $id.
* @return int|null $id
*/
public function get_id() {
return $this->id;
}
/**
* Setter for $userid.
* @param int|null $userid
*/
public function set_userid($userid) {
$this->userid = $userid;
}
/**
* Returns default concurrency limit for this task.
*
* @return int default concurrency limit
*/
protected function get_default_concurrency_limit(): int {
global $CFG;
if (isset($CFG->task_concurrency_limit_default)) {
return (int) $CFG->task_concurrency_limit_default;
}
return 0;
}
/**
* Returns effective concurrency limit for this task.
*
* @return int effective concurrency limit for this task
*/
final public function get_concurrency_limit(): int {
global $CFG;
$classname = get_class($this);
if (isset($CFG->task_concurrency_limit[$classname])) {
return (int) $CFG->task_concurrency_limit[$classname];
}
return $this->get_default_concurrency_limit();
}
/**
* Sets concurrency task lock.
*
* @param \core\lock\lock $lock concurrency lock to be set
*/
final public function set_concurrency_lock(\core\lock\lock $lock): void {
$this->concurrencylock = $lock;
}
/**
* Release the concurrency lock for this task type.
*/
final public function release_concurrency_lock(): void {
if ($this->concurrencylock) {
$this->concurrencylock->release();
}
}
/**
* Set the remaining attempts of the task.
*
* @param int $attemptsavailable Number of the remaining attempts of the task.
*/
public function set_attempts_available(int $attemptsavailable): void {
$this->attemptsavailable = $attemptsavailable;
}
/**
* Get the remaining attempts of the task.
*
* @return int Number of the remaining attempts of the task.
*/
public function get_attempts_available(): int {
return $this->attemptsavailable;
}
/**
* Used to indicate if the task should be re-run if it fails.
* By default, tasks will be retried until they succeed, other tasks can override this method to change this behaviour.
*
* @return bool true if the task should be retried until it succeeds, false otherwise.
*/
public function retry_until_success(): bool {
return true;
}
}
@@ -0,0 +1,60 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Delete stale records from analytics tables.
*
* @package core
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class analytics_cleanup_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskanalyticscleanup', 'admin');
}
/**
* Executes the clean up task.
*
* @return void
*/
public function execute() {
if (!\core_analytics\manager::is_analytics_enabled()) {
mtrace(get_string('analyticsdisabled', 'analytics'));
return;
}
$models = \core_analytics\manager::cleanup();
}
}
@@ -0,0 +1,62 @@
<?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/>.
/**
* Clean up task for core antivirus
*
* @package core_antivirus
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Clean up task for core antivirus
*
* @package core_antivirus
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class antivirus_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task.
*
* @return string
*/
public function get_name() {
return get_string('taskcleanup', 'antivirus');
}
/**
* Processes workflows.
*/
public function execute() {
$quarantinetime = get_config('antivirus', 'quarantinetime');
if (empty($quarantinetime)) {
$quarantinetime = \core\antivirus\quarantine::DEFAULT_QUARANTINE_TIME;
set_config('quarantinetime', $quarantinetime, 'antivirus');
}
$timetocleanup = time() - $quarantinetime;
\core\antivirus\quarantine::clean_up_quarantine_folder($timetocleanup);
}
}
@@ -0,0 +1,93 @@
<?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/>.
/**
* Adhoc task that performs asynchronous backups.
*
* @package core
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use async_helper;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
/**
* Adhoc task that performs asynchronous backups.
*
* @package core
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_backup_task extends adhoc_task {
/**
* Run the adhoc task and preform the backup.
*/
public function execute() {
global $DB;
$started = time();
$backupid = $this->get_custom_data()->backupid;
$backuprecord = $DB->get_record('backup_controllers', array('backupid' => $backupid), 'id, controller', MUST_EXIST);
mtrace('Processing asynchronous backup for backup: ' . $backupid);
// Get the backup controller by backup id. If controller is invalid, this task can never complete.
if ($backuprecord->controller === '') {
mtrace('Bad backup controller status, invalid controller, ending backup execution.');
return;
}
$bc = \backup_controller::load_controller($backupid);
$bc->set_progress(new \core\progress\db_updater($backuprecord->id, 'backup_controllers', 'progress'));
// Do some preflight checks on the backup.
$status = $bc->get_status();
$execution = $bc->get_execution();
// Check that the backup is in the correct status and
// that is set for asynchronous execution.
if ($status == \backup::STATUS_AWAITING && $execution == \backup::EXECUTION_DELAYED) {
// Execute the backup.
$bc->execute_plan();
// Send message to user if enabled.
$messageenabled = (bool)get_config('backup', 'backup_async_message_users');
if ($messageenabled && $bc->get_status() == \backup::STATUS_FINISHED_OK) {
$asynchelper = new async_helper('backup', $backupid);
$asynchelper->send_message();
}
} else {
// If status isn't 700, it means the process has failed.
// Retrying isn't going to fix it, so marked operation as failed.
$bc->set_status(\backup::STATUS_FINISHED_ERR);
mtrace('Bad backup controller status, is: ' . $status . ' should be 700, marking job as failed.');
}
// Cleanup.
$bc->destroy();
$duration = time() - $started;
mtrace('Backup completed in: ' . $duration . ' seconds');
}
}
+217
View File
@@ -0,0 +1,217 @@
<?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/>.
/**
* Adhoc task that performs asynchronous course copies.
*
* @package core
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use async_helper;
use cache_helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
/**
* Adhoc task that performs asynchronous course copies.
*
* @package core
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_copy_task extends adhoc_task {
/**
* Run the adhoc task and preform the backup.
*/
public function execute() {
global $CFG, $DB;
$started = time();
$backupid = $this->get_custom_data()->backupid;
$restoreid = $this->get_custom_data()->restoreid;
$backuprecord = $DB->get_record('backup_controllers', array('backupid' => $backupid), 'id, itemid', MUST_EXIST);
$restorerecord = $DB->get_record('backup_controllers', array('backupid' => $restoreid), 'id, itemid', MUST_EXIST);
// First backup the course.
mtrace('Course copy: Processing asynchronous course copy for course id: ' . $backuprecord->itemid);
try {
$bc = \backup_controller::load_controller($backupid); // Get the backup controller by backup id.
} catch (\backup_dbops_exception $e) {
mtrace('Course copy: Can not load backup controller for copy, marking job as failed');
delete_course($restorerecord->itemid, false); // Clean up partially created destination course.
return; // Return early as we can't continue.
}
$rc = \restore_controller::load_controller($restoreid); // Get the restore controller by restore id.
$bc->set_progress(new \core\progress\db_updater($backuprecord->id, 'backup_controllers', 'progress'));
$copyinfo = $rc->get_copy();
$backupplan = $bc->get_plan();
$keepuserdata = (bool)$copyinfo->userdata;
$keptroles = $copyinfo->keptroles;
$bc->set_kept_roles($keptroles);
// If we are not keeping user data don't include users or data in the backup.
// In this case we'll add the user enrolments at the end.
// Also if we have no roles to keep don't backup users.
if (empty($keptroles) || !$keepuserdata) {
$backupplan->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
$backupplan->get_setting('users')->set_value('0');
} else {
$backupplan->get_setting('users')->set_value('1');
}
// Do some preflight checks on the backup.
$status = $bc->get_status();
$execution = $bc->get_execution();
// Check that the backup is in the correct status and
// that is set for asynchronous execution.
if ($status == \backup::STATUS_AWAITING && $execution == \backup::EXECUTION_DELAYED) {
// Execute the backup.
mtrace('Course copy: Backing up course, id: ' . $backuprecord->itemid);
$bc->execute_plan();
} else {
// If status isn't 700, it means the process has failed.
// Retrying isn't going to fix it, so marked operation as failed.
mtrace('Course copy: Bad backup controller status, is: ' . $status . ' should be 700, marking job as failed.');
$bc->set_status(\backup::STATUS_FINISHED_ERR);
delete_course($restorerecord->itemid, false); // Clean up partially created destination course.
$bc->destroy();
return; // Return early as we can't continue.
}
$results = $bc->get_results();
$backupbasepath = $backupplan->get_basepath();
$file = $results['backup_destination'];
$file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
// Start the restore process.
$rc->set_progress(new \core\progress\db_updater($restorerecord->id, 'backup_controllers', 'progress'));
$rc->prepare_copy();
// Set the course settings we can do now (the remaining settings will be done after restore completes).
$plan = $rc->get_plan();
$startdate = $plan->get_setting('course_startdate');
$startdate->set_value($copyinfo->startdate);
$fullname = $plan->get_setting('course_fullname');
$fullname->set_value($copyinfo->fullname);
$shortname = $plan->get_setting('course_shortname');
$shortname->set_value($copyinfo->shortname);
// Do some preflight checks on the restore.
$rc->execute_precheck();
$status = $rc->get_status();
$execution = $rc->get_execution();
// Check that the restore is in the correct status and
// that is set for asynchronous execution.
if ($status == \backup::STATUS_AWAITING && $execution == \backup::EXECUTION_DELAYED) {
// Execute the restore.
mtrace('Course copy: Restoring into course, id: ' . $restorerecord->itemid);
$rc->execute_plan();
} else {
// If status isn't 700, it means the process has failed.
// Retrying isn't going to fix it, so marked operation as failed.
mtrace('Course copy: Bad backup controller status, is: ' . $status . ' should be 700, marking job as failed.');
$rc->set_status(\backup::STATUS_FINISHED_ERR);
delete_course($restorerecord->itemid, false); // Clean up partially created destination course.
$file->delete();
if (empty($CFG->keeptempdirectoriesonbackup)) {
fulldelete($backupbasepath);
}
$rc->destroy();
return; // Return early as we can't continue.
}
// Copy user enrolments from source course to destination.
if (!empty($keptroles) && !$keepuserdata) {
mtrace('Course copy: Creating user enrolments in destination course.');
$context = \context_course::instance($backuprecord->itemid);
$enrol = enrol_get_plugin('manual');
$instance = null;
$enrolinstances = enrol_get_instances($restorerecord->itemid, true);
foreach ($enrolinstances as $courseenrolinstance) {
if ($courseenrolinstance->enrol == 'manual') {
$instance = $courseenrolinstance;
break;
}
}
// Abort if there enrolment plugin problems.
if (empty($enrol) || empty($instance)) {
mtrace('Course copy: Could not enrol users in course.');;
delete_course($restorerecord->itemid, false);
return;
}
// Enrol the users from the source course to the destination.
foreach ($keptroles as $roleid) {
$sourceusers = get_role_users($roleid, $context);
foreach ($sourceusers as $sourceuser) {
$enrol->enrol_user($instance, $sourceuser->id, $roleid);
}
}
}
// Set up remaining course settings.
$course = $DB->get_record('course', array('id' => $restorerecord->itemid), '*', MUST_EXIST);
$course->visible = $copyinfo->visible;
$course->idnumber = $copyinfo->idnumber;
$course->enddate = $copyinfo->enddate;
$DB->update_record('course', $course);
// Send message to user if enabled.
$messageenabled = (bool)get_config('backup', 'backup_async_message_users');
if ($messageenabled && $rc->get_status() == \backup::STATUS_FINISHED_OK) {
mtrace('Course copy: Sending user notification.');
$asynchelper = new async_helper('copy', $restoreid);
$messageid = $asynchelper->send_message();
mtrace('Course copy: Sent message: ' . $messageid);
}
// Cleanup.
$bc->destroy();
$rc->destroy();
$file->delete();
if (empty($CFG->keeptempdirectoriesonbackup)) {
fulldelete($backupbasepath);
}
rebuild_course_cache($restorerecord->itemid, true);
cache_helper::purge_by_event('changesincourse');
$duration = time() - $started;
mtrace('Course copy: Copy completed in: ' . $duration . ' seconds');
}
}
@@ -0,0 +1,115 @@
<?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/>.
/**
* Adhoc task that performs asynchronous restores.
*
* @package core
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use async_helper;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Adhoc task that performs asynchronous restores.
*
* @package core
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_restore_task extends adhoc_task {
/**
* Run the adhoc task and preform the restore.
*/
public function execute() {
global $DB;
$started = time();
$restoreid = $this->get_custom_data()->backupid;
$restorerecord = $DB->get_record('backup_controllers', array('backupid' => $restoreid), 'id, controller', IGNORE_MISSING);
// If the record doesn't exist, the backup controller failed to create. Unable to proceed.
if (empty($restorerecord)) {
mtrace('Unable to find restore controller, ending restore execution.');
return;
}
mtrace('Processing asynchronous restore for id: ' . $restoreid);
// Get the backup controller by backup id. If controller is invalid, this task can never complete.
if ($restorerecord->controller === '') {
mtrace('Bad restore controller status, invalid controller, ending restore execution.');
return;
}
$rc = \restore_controller::load_controller($restoreid);
try {
$rc->set_progress(new \core\progress\db_updater($restorerecord->id, 'backup_controllers', 'progress'));
// Do some preflight checks on the restore.
$status = $rc->get_status();
$execution = $rc->get_execution();
// Check that the restore is in the correct status and
// that is set for asynchronous execution.
if ($status == \backup::STATUS_AWAITING && $execution == \backup::EXECUTION_DELAYED) {
// Execute the restore.
$rc->execute_plan();
// Send message to user if enabled.
$messageenabled = (bool) get_config('backup', 'backup_async_message_users');
if ($messageenabled && $rc->get_status() == \backup::STATUS_FINISHED_OK) {
$asynchelper = new async_helper('restore', $restoreid);
$asynchelper->send_message();
}
} else {
// If status isn't 700, it means the process has failed.
// Retrying isn't going to fix it, so marked operation as failed.
$rc->set_status(\backup::STATUS_FINISHED_ERR);
mtrace('Bad backup controller status, is: ' . $status . ' should be 700, marking job as failed.');
}
$duration = time() - $started;
mtrace('Restore completed in: ' . $duration . ' seconds');
} catch (\Exception $e) {
// If an exception is thrown, mark the restore as failed.
$rc->set_status(\backup::STATUS_FINISHED_ERR);
mtrace('Exception thrown during restore execution, marking job as failed.');
mtrace($e->getMessage());
} finally {
// Cleanup.
// Always destroy the controller.
$rc->destroy();
}
}
/**
* Sets attemptsavailable to false.
*
* @return boolean
*/
public function retry_until_success(): bool {
return false;
}
}
@@ -0,0 +1,86 @@
<?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 core\task;
/**
* Report task for core automation backup.
*
* @package core
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class automated_backup_report_task extends scheduled_task {
/**
* Get a descriptive name for the task (shown to admins).
*
* @return string
*/
public function get_name(): string {
return get_string('taskautomatedbackup_report', 'admin');
}
/**
* Do the job.
*/
public function execute(): void {
global $DB, $CFG;
$queuedtasks = [];
if ($value = get_config('backup', 'backup_auto_adhoctasks')) {
$queuedtasks = explode(',', $value);
}
if (!empty($queuedtasks)) {
// Some automated backup tasks are still running.
// Check the status for each task.
foreach ($queuedtasks as $taskid) {
if (!$DB->record_exists('task_adhoc', ['id' => $taskid])) {
// The task has been completed. Remove it from the queue.
if (($key = array_search($taskid, $queuedtasks)) !== false) {
unset($queuedtasks[$key]);
}
}
}
// Update the queue.
set_config(
'backup_auto_adhoctasks',
implode(',', $queuedtasks),
'backup',
);
}
if (empty($queuedtasks) && get_config('backup', 'backup_auto_emailpending')) {
// All the automated backup tasks have been completed. Send the report.
$admin = get_admin();
if (!$admin) {
mtrace("Error: No admin account was found");
return;
}
// Send email to admin if necessary.
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
\backup_cron_automated_helper::send_backup_status_to_admin($admin);
// Remove the configs.
unset_config(
'backup_auto_adhoctasks',
'backup',
);
unset_config(
'backup_auto_emailpending',
'backup',
);
}
}
}
@@ -0,0 +1,52 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the backup cron.
*/
class automated_backup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskautomatedbackup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
// Run automated backups if required - these may take a long time to execute.
require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php');
\backup_cron_automated_helper::run_automated_backup();
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Simple task to delete old backup records.
*/
class backup_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskbackupcleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $DB;
$sql = 'SELECT * FROM {backup_controllers} WHERE purpose = ? AND status <> ?';
$params = [\backup::MODE_COPY, \backup::STATUS_FINISHED_OK];
$copyrecords = $DB->get_records_sql($sql, $params);
\copy_helper::cleanup_orphaned_copy_controllers($copyrecords);
$loglifetime = get_config('backup', 'loglifetime');
if (empty($loglifetime)) {
mtrace('The \'loglifetime\' config is not set. Can\'t proceed and delete old backup records.');
return;
}
// First, get the list of all backupids older than loglifetime.
$timecreated = time() - ($loglifetime * DAYSECS);
$records = $DB->get_records_select('backup_controllers', 'timecreated < ?', array($timecreated), 'id', 'id, backupid');
foreach ($records as $record) {
// Check if there is no incomplete adhoc task relying on the given backupid.
$params = array('%' . $record->backupid . '%');
$select = $DB->sql_like('customdata', '?', false);
$count = $DB->count_records_select('task_adhoc', $select, $params);
if ($count === 0) {
// Looks like there is no adhoc task, so we can delete logs and controllers for this backupid.
$DB->delete_records('backup_logs', array('backupid' => $record->backupid));
$DB->delete_records('backup_controllers', array('backupid' => $record->backupid));
}
}
// Delete files and dirs older than 1 week.
\backup_helper::delete_old_backup_dirs(strtotime('-1 week'));
}
}
+70
View File
@@ -0,0 +1,70 @@
<?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 core\task;
/**
* Class badges_adhoc_task
*
* @package core
* @copyright 2023 Jay Oswald <jayoswald@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badges_adhoc_task extends adhoc_task {
/**
* Sets the name of the badges adhoc task
*
* @return void
*/
public function get_name() {
return get_string('taskbadgesadhoc', 'admin');
}
/**
* Badge adhoc task to assign a single badge
*
* @return void
*/
public function execute() {
$data = $this->get_custom_data();
$badge = new \core_badges\badge($data->badgeid);
$traceprefix = "Badge $data->badgeid: $badge->name: ";
try {
if (!$badge->has_criteria()) {
mtrace("$traceprefix Badge has no criteria to be processed");
return;
}
$issued = $badge->review_all_criteria();
mtrace("$traceprefix badge was issued to $issued users.");
} catch (\moodle_exception $e) {
$badgeeditlink =
new \moodle_url('/badges/edit.php', ['id' => $data->badgeid, 'action' => 'badge']);
switch($e->errorcode){
case 'invalidcoursemoduleid':
$badge->set_status(BADGE_STATUS_INACTIVE);
mtrace("$traceprefix has invalid course modules, it has been made inactive $badgeeditlink");
break;
default:
mtrace("$traceprefix Error: {$e->getMessage()} $badgeeditlink");
throw($e);
}
}
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the badges cron.
*/
class badges_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskbadgescron', 'admin');
}
/**
* Reviews criteria and awards badges
*
* First find all badges that can be earned, then reviews each badge.
* (Not sure how efficient this is timewise).
*/
public function execute() {
global $DB, $CFG;
if (empty($CFG->enablebadges)) {
return;
}
require_once($CFG->libdir . '/badgeslib.php');
$courseparams = [];
if (empty($CFG->badges_allowcoursebadges)) {
$coursesql = '';
} else {
$coursesql = "OR EXISTS (
SELECT c.id
FROM {course} c
WHERE c.visible = :visible
AND c.startdate < :current
AND c.id = b.courseid
) ";
$courseparams = ['visible' => 1, 'current' => time()];
}
$sql = "SELECT b.id
FROM {badge} b
WHERE (b.status = :active OR b.status = :activelocked)
AND (b.type = :site $coursesql )";
$badgeparams = [
'active' => BADGE_STATUS_ACTIVE,
'activelocked' => BADGE_STATUS_ACTIVE_LOCKED,
'site' => BADGE_TYPE_SITE,
];
$params = array_merge($badgeparams, $courseparams);
$badges = $DB->get_fieldset_sql($sql, $params);
foreach ($badges as $bid) {
$task = new badges_adhoc_task();
$task->set_custom_data(['badgeid' => $bid]);
manager::queue_adhoc_task($task, true);
}
mtrace(count($badges) . " adhoc badge tasks were added");
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the badges cron.
*/
class badges_message_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskbadgesmessagecron', 'admin');
}
/**
* Reviews criteria and awards badges
*
* First find all badges that can be earned, then reviews each badge.
*/
public function execute() {
global $CFG, $DB;
if (!empty($CFG->enablebadges)) {
require_once($CFG->libdir . '/badgeslib.php');
mtrace('Sending scheduled badge notifications.');
$scheduled = $DB->get_records_select('badge', 'notification > ? AND (status != ?) AND nextcron < ?',
array(BADGE_MESSAGE_ALWAYS, BADGE_STATUS_ARCHIVED, time()),
'notification ASC', 'id, name, notification, usercreated as creator, timecreated');
foreach ($scheduled as $sch) {
// Send messages.
badge_assemble_notification($sch);
// Update next cron value.
$nextcron = badges_calculate_message_schedule($sch->notification);
$DB->set_field('badge', 'nextcron', $nextcron, array('id' => $sch->id));
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the blog cron.
*/
class blog_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskblogcron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
$timenow = time();
// Run external blog cron if needed.
if (!empty($CFG->enableblogs) && $CFG->useexternalblogs) {
require_once($CFG->dirroot . '/blog/lib.php');
$sql = "timefetched < ? OR timefetched = 0";
$externalblogs = $DB->get_records_select('blog_external', $sql, array($timenow - $CFG->externalblogcrontime));
foreach ($externalblogs as $eb) {
blog_sync_external_entries($eb);
}
}
// Run blog associations cleanup.
if (!empty($CFG->enableblogs) && $CFG->useblogassociations) {
require_once($CFG->dirroot . '/blog/lib.php');
// Delete entries whose contextids no longer exists.
$DB->delete_records_select('blog_association', 'contextid NOT IN (SELECT id FROM {context})');
}
}
}
@@ -0,0 +1,54 @@
<?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/>.
/**
* Adhoc task that builds and caches all of the site's installed themes.
*
* @package core
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Class that builds and caches all of the site's installed themes.
*
* @package core
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class build_installed_themes_task extends adhoc_task {
/**
* Run the task.
*/
public function execute() {
global $CFG;
require_once("{$CFG->libdir}/outputlib.php");
$themenames = array_keys(\core_component::get_plugin_list('theme'));
// Load the theme configs.
$themeconfigs = array_map(function($themename) {
return \theme_config::load($themename);
}, $themenames);
// Build the list of themes and cache them in local cache.
theme_build_css_for_themes($themeconfigs);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete old cache records.
*/
class cache_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcachecleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
// Remove expired cache flags.
gc_cache_flags();
}
}
+48
View File
@@ -0,0 +1,48 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the cache cron.
*/
class cache_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcachecron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
\cache_helper::cron();
}
}
+81
View File
@@ -0,0 +1,81 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/calendar/lib.php');
/**
* Simple task to run the calendar cron.
*/
class calendar_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcalendarcron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
require_once($CFG->libdir . '/bennu/bennu.inc.php');
$time = time();
$sql = "SELECT *
FROM {event_subscriptions}
WHERE pollinterval > 0
AND lastupdated + pollinterval < :time";
$subscriptions = $DB->get_records_sql($sql, array('time' => $time));
foreach ($subscriptions as $sub) {
mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}");
try {
$log = calendar_update_subscription_events($sub->id);
mtrace($log['eventsimported'] . ' events were imported');
mtrace($log['eventsskipped'] . ' events were skipped');
mtrace($log['eventsupdated'] . ' events were updated');
mtrace($log['eventsdeleted'] . ' events were deleted');
if ($log['haserror']) {
mtrace('Failed to add event');
foreach ($log['errors'] as $error) {
mtrace(trim(strip_tags($error)));
}
}
} catch (\moodle_exception $ex) {
mtrace('Error updating calendar subscription: ' . $ex->getMessage());
}
}
return true;
}
}
@@ -0,0 +1,73 @@
<?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/>.
/**
* Adhoc task handling fixing of events that have had their userid lost.
*
* @package core
* @copyright 2021 onwards Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Class handling fixing of events that have had their userid lost.
*
* @package core
* @copyright 2021 onwards Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_fix_orphaned_events extends adhoc_task {
/**
* Run the task to recover the correct userid from the event.
*
* If the maximum number of records are updated, the task re-queues itself,
* as there may be more events to be fixed.
*/
public function execute() {
// Check for problematic upgrade steps and fix orphaned records.
if ($this->update_events_wrong_userid_remaining()) {
// There are orphaned events to be fixed.
// The task will re-queue itself until all orphaned calendar events have been fixed.
\core\task\manager::queue_adhoc_task(new calendar_fix_orphaned_events());
}
}
/**
* Execute the recovery of events that have been set with userid to zero.
*
* @return bool Whether there are more events to be fixed.
*/
protected function update_events_wrong_userid_remaining(): bool {
global $CFG;
require_once($CFG->libdir . '/db/upgradelib.php');
// Default the max runtime to 60 seconds, unless overridden in config.php.
$maxseconds = $CFG->calendareventsmaxseconds ?? MINSECS;
// Orphaned events found, get those events so it can be recovered.
$eventsinfo = upgrade_calendar_events_status();
// Fix the orphaned events and returns if there are more events to be fixed.
return upgrade_calendar_events_fix_remaining($eventsinfo, true, $maxseconds);
}
}
@@ -0,0 +1,54 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the registration cron.
*/
class check_for_updates_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcheckforupdates', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
// If enabled, fetch information about available updates and eventually notify site admins.
if (empty($CFG->disableupdatenotifications)) {
$updateschecker = \core\update\checker::instance();
$updateschecker->cron();
}
}
}
@@ -0,0 +1,53 @@
<?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/>.
/**
* Adhoc task that clean up data related ro deleted search area.
*
* @package core
* @copyright 2019 Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Class that cleans up data related to deleted search area.
*
* Custom data accepted:
* - areaid -> String search area id .
*
* @package core
* @copyright 2019 Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class clean_up_deleted_search_area_task extends adhoc_task {
/**
* Run the task to clean up deleted search are data.
*/
public function execute() {
$areaid = $this->get_custom_data();
try {
\core_search\manager::clean_up_non_existing_area($areaid);
} catch (\core_search\engine_exception $e) {
mtrace('Search is not configured. Skip deleting index for search area ' . $areaid);
}
}
}
+68
View File
@@ -0,0 +1,68 @@
<?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/>.
/**
* Complete plans task.
*
* @package core_competency
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
use core_competency\api;
use core_competency\plan;
/**
* Complete plans task class.
*
* This task should run relatively often because the plans due dates can be set at
* any time of the day in any timezone.
*
* @package core_competency
* @copyright 2015 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class complete_plans_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task.
*
* @return string
*/
public function get_name() {
return get_string('completeplanstask', 'core_competency');
}
/**
* Do the job.
*/
public function execute() {
if (!api::is_enabled()) {
return;
}
$records = plan::get_recordset_for_due_and_incomplete();
foreach ($records as $record) {
$plan = new plan(0, $record);
api::complete_plan($plan);
}
$records->close();
}
}
+171
View File
@@ -0,0 +1,171 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the daily completion cron.
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class completion_daily_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcompletiondaily', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
if ($CFG->enablecompletion) {
require_once($CFG->libdir . "/completionlib.php");
if (debugging()) {
mtrace('Marking users as started');
}
// This causes it to default to everyone (if there is no student role).
$sqlroles = '';
if (!empty($CFG->gradebookroles)) {
$sqlroles = ' AND ra.roleid IN (' . $CFG->gradebookroles.')';
}
// It's purpose is to locate all the active participants of a course with course completion enabled.
// We also only want the users with no course_completions record as this functions job is to create
// the missing ones :)
// We want to record the user's enrolment start time for the course. This gets tricky because there can be
// multiple enrolment plugins active in a course, hence the possibility of multiple records for each
// couse/user in the results.
$sql = "SELECT c.id AS course, u.id AS userid, crc.id AS completionid, ue.timestart AS timeenrolled,
ue.timecreated
FROM {user} u
INNER JOIN {user_enrolments} ue ON ue.userid = u.id
INNER JOIN {enrol} e ON e.id = ue.enrolid
INNER JOIN {course} c ON c.id = e.courseid
INNER JOIN {context} con ON con.contextlevel = ? AND con.instanceid = c.id
INNER JOIN {role_assignments} ra ON ra.userid = u.id AND ra.contextid = con.id
LEFT JOIN {course_completions} crc ON crc.course = c.id AND crc.userid = u.id
WHERE c.enablecompletion = 1
AND crc.timeenrolled IS NULL
AND ue.status = 0
AND e.status = 0
AND u.deleted = 0
AND ue.timestart < ?
AND (ue.timeend > ? OR ue.timeend = 0)
$sqlroles
ORDER BY course, userid";
$now = time();
$rs = $DB->get_recordset_sql($sql, [CONTEXT_COURSE, $now, $now]);
// Check if result is empty.
if (!$rs->valid()) {
// Not going to iterate (but exit), close rs.
$rs->close();
return;
}
// We are essentially doing a group by in the code here (as I can't find a decent way of doing it
// in the sql). Since there can be multiple enrolment plugins for each course, we can have multiple rows
// for each participant in the query result. This isn't really a problem until you combine it with the fact
// that the enrolment plugins can save the enrol start time in either timestart or timeenrolled.
// The purpose of the loop is to find the earliest enrolment start time for each participant in each course.
$prev = null;
while ($rs->valid() || $prev) {
$current = $rs->current();
if (!isset($current->course)) {
$current = false;
} else {
// Not all enrol plugins fill out timestart correctly, so use whichever is non-zero.
$current->timeenrolled = max($current->timecreated, $current->timeenrolled);
}
// If we are at the last record, or we aren't at the first and the record is for a diff user/course.
if ($prev && (!$rs->valid() ||
($current->course != $prev->course || $current->userid != $prev->userid))) {
$completion = new \completion_completion();
$completion->userid = $prev->userid;
$completion->course = $prev->course;
$completion->timeenrolled = (string) $prev->timeenrolled;
$completion->timestarted = 0;
$completion->reaggregate = time();
if ($prev->completionid) {
$completion->id = $prev->completionid;
}
try {
$completion->mark_enrolled();
if (debugging()) {
mtrace('Marked started user '.$prev->userid.' in course '.$prev->course);
}
} catch (\dml_write_exception $e) {
// Most likely this happened because the completion object was created while we were working.
// So get the record and make sure it has a time enrolled set.
if (debugging()) {
mtrace('Exception while marking started user '.$prev->userid.' in course '.$prev->course.', retrying');
}
$params = ['userid' => $completion->userid, 'course' => $completion->course];
$existing = new \completion_completion($params);
if (!empty($existing->id) && empty($existing->timeenrolled)) {
$existing->timeenrolled = $completion->timeenrolled;
try {
$existing->mark_enrolled();
} catch (\Exception $e) {
// Catch everything, so we can continue on to other records.
if (debugging()) {
mtrace('Exception again while marking started user '.$prev->userid.' in course '.$prev->course.
': '.$e->getMessage()."\n".$e->getTraceAsString());
}
}
}
} catch (\Exception $e) {
// Catch anything else, so we can continue on to other records.
if (debugging()) {
mtrace('Exception while marking started user '.$prev->userid.' in course '.$prev->course.
': '.$e->getMessage()."\n".$e->getTraceAsString());
}
}
} else if ($prev && $current) {
// Else, if this record is for the same user/course use oldest timeenrolled.
$current->timeenrolled = min($current->timeenrolled, $prev->timeenrolled);
}
// Move current record to previous.
$prev = $current;
// Move to next record.
$rs->next();
}
$rs->close();
}
}
}
@@ -0,0 +1,71 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2015 Josh Willcock
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the regular completion cron.
* @copyright 2015 Josh Willcock
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class completion_regular_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcompletionregular', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $COMPLETION_CRITERIA_TYPES, $DB;
if ($CFG->enablecompletion) {
require_once($CFG->libdir . "/completionlib.php");
// Process each criteria type.
foreach ($COMPLETION_CRITERIA_TYPES as $type) {
$object = 'completion_criteria_' . $type;
require_once($CFG->dirroot . '/completion/criteria/' . $object . '.php');
$class = new $object();
// Run the criteria type's cron method, if it has one.
if (method_exists($class, 'cron')) {
if (debugging()) {
mtrace('Running '.$object.'->cron()');
}
$class->cron();
}
}
aggregate_completions(0, true);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete old context records.
*/
class context_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcontextcleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
// Context maintenance stuff.
\context_helper::cleanup_instances();
mtrace(' Cleaned up context instances');
\context_helper::build_all_paths(false);
// If you suspect that the context paths are somehow corrupt
// replace the line below with: context_helper::build_all_paths(true).
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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/>.
/**
* Adhoc task that performs single automated course backup.
*
* @package core
* @copyright 2019 John Yao <johnyao@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
/**
* Adhoc task that performs single automated course backup.
*
* @package core
* @copyright 2019 John Yao <johnyao@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_backup_task extends \core\task\adhoc_task {
/**
* Run the adhoc task and preform the backup.
*/
public function execute() {
global $DB;
$lockfactory = \core\lock\lock_config::get_lock_factory('course_backup_adhoc');
$courseid = $this->get_custom_data()->courseid;
try {
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
} catch (\moodle_exception $e) {
mtrace('Invalid course id: ' . $courseid . ', task aborted.');
return;
}
if (!$lock = $lockfactory->get_lock('course_backup_adhoc_task_' . $courseid, 10)) {
mtrace('Backup adhoc task for: ' . $course->fullname . 'is already running.');
return;
} else {
mtrace('Processing automated backup for course: ' . $course->fullname);
}
try {
$backupcourse = $DB->get_record('backup_courses', array(
'courseid' => $courseid,
'laststatus' => \backup_cron_automated_helper::BACKUP_STATUS_QUEUED
), '*', MUST_EXIST);
$adminid = $this->get_custom_data()->adminid;
$backupcourse->laststarttime = time();
$backupcourse->laststatus = \backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED;
$DB->update_record('backup_courses', $backupcourse);
$backupcourse->laststatus = \backup_cron_automated_helper::launch_automated_backup($course, time(), $adminid);
if ($backupcourse->laststatus == \backup_cron_automated_helper::BACKUP_STATUS_ERROR ||
$backupcourse->laststatus == \backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED) {
mtrace('Automated backup for course: ' . $course->fullname . ' failed.');
// Reset unfinished to error.
$backupcourse->laststatus = \backup_cron_automated_helper::BACKUP_STATUS_ERROR;
}
// Remove excess backups.
$removedcount = \backup_cron_automated_helper::remove_excess_backups($course, time());
$backupcourse->lastendtime = time();
$backupcourse->nextstarttime = \backup_cron_automated_helper::calculate_next_automated_backup(null, time());
$DB->update_record('backup_courses', $backupcourse);
} catch (\moodle_exception $e) {
mtrace('Automated backup for course: ' . $course->fullname . ' encounters an error.');
mtrace('Exception: ' . $e->getMessage());
mtrace('Debug: ' . $e->debuginfo);
} finally {
// Everything is finished release lock.
$lock->release();
mtrace('Automated backup for course: ' . $course->fullname . ' completed.');
}
}
}
+50
View File
@@ -0,0 +1,50 @@
<?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/>.
/**
* Scheduled task class.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to create missing contexts at all levels.
*/
class create_contexts_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskcreatecontexts', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
// Make sure all context instances are properly created - they may be required in auth, enrol, etc.
\context_helper::create_instances();
mtrace(' Created missing context instances');
}
}
+173
View File
@@ -0,0 +1,173 @@
<?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/>.
/**
* Database logger for task logging.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Database logger for task logging.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class database_logger implements task_logger {
/** @var int Type constant for a scheduled task */
const TYPE_SCHEDULED = 0;
/** @var int Type constant for an adhoc task */
const TYPE_ADHOC = 1;
/**
* Whether the task is configured and ready to log.
*
* @return bool
*/
public static function is_configured(): bool {
return true;
}
/**
* Store the log for the specified task.
*
* @param task_base $task The task that the log belongs to.
* @param string $logpath The path to the log on disk
* @param bool $failed Whether the task failed
* @param int $dbreads The number of DB reads
* @param int $dbwrites The number of DB writes
* @param float $timestart The start time of the task
* @param float $timeend The end time of the task
*/
public static function store_log_for_task(task_base $task, string $logpath, bool $failed,
int $dbreads, int $dbwrites, float $timestart, float $timeend) {
global $DB;
// Write this log to the database.
$logdata = (object) [
'type' => is_a($task, scheduled_task::class) ? self::TYPE_SCHEDULED : self::TYPE_ADHOC,
'component' => $task->get_component(),
'classname' => get_class($task),
'userid' => 0,
'timestart' => $timestart,
'timeend' => $timeend,
'dbreads' => $dbreads,
'dbwrites' => $dbwrites,
'result' => (int) $failed,
'output' => file_get_contents($logpath),
'hostname' => $task->get_hostname(),
'pid' => $task->get_pid(),
];
if (is_a($task, adhoc_task::class) && $userid = $task->get_userid()) {
$logdata->userid = $userid;
}
$logdata->id = $DB->insert_record('task_log', $logdata);
}
/**
* Whether this task logger has a report available.
*
* @return bool
*/
public static function has_log_report(): bool {
return true;
}
/**
* Get any URL available for viewing relevant task log reports.
*
* @param string $classname The task class to fetch for
* @return \moodle_url
*/
public static function get_url_for_task_class(string $classname): \moodle_url {
global $CFG;
return new \moodle_url("/{$CFG->admin}/tasklogs.php", [
'filter' => $classname,
]);
}
/**
* Cleanup old task logs.
*/
public static function cleanup() {
global $CFG, $DB;
// Delete logs older than the retention period.
$params = [
'retentionperiod' => time() - $CFG->task_logretention,
];
$logids = $DB->get_fieldset_select('task_log', 'id', 'timestart < :retentionperiod', $params);
self::delete_task_logs($logids);
// Delete logs to retain a minimum number of logs.
$sql = "SELECT classname FROM {task_log} GROUP BY classname HAVING COUNT(classname) > :retaincount";
$params = [
'retaincount' => $CFG->task_logretainruns,
];
$classes = $DB->get_fieldset_sql($sql, $params);
foreach ($classes as $classname) {
$notinsql = "";
$params = [
'classname' => $classname,
];
$retaincount = (int) $CFG->task_logretainruns;
if ($retaincount) {
$keeplogs = $DB->get_records('task_log', [
'classname' => $classname,
], 'timestart DESC', 'id', 0, $retaincount);
if ($keeplogs) {
list($notinsql, $params) = $DB->get_in_or_equal(array_keys($keeplogs), SQL_PARAMS_NAMED, 'p', false);
$params['classname'] = $classname;
$notinsql = " AND id {$notinsql}";
}
}
$logids = $DB->get_fieldset_select('task_log', 'id', "classname = :classname {$notinsql}", $params);
self::delete_task_logs($logids);
}
}
/**
* Delete task logs for the specified logs.
*
* @param array $logids
*/
public static function delete_task_logs(array $logids) {
global $DB;
if (empty($logids)) {
return;
}
$chunks = array_chunk($logids, 1000);
foreach ($chunks as $chunk) {
$DB->delete_records_list('task_log', 'id', $chunk);
}
}
}
@@ -0,0 +1,73 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete user accounts for users who have not completed their profile in time.
*/
class delete_incomplete_users_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskdeleteincompleteusers', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
$timenow = time();
// Delete users who haven't completed profile within required period.
if (!empty($CFG->deleteincompleteusers)) {
$cuttime = $timenow - ($CFG->deleteincompleteusers * 3600);
$rs = $DB->get_recordset_sql ("SELECT *
FROM {user}
WHERE confirmed = 1 AND lastaccess > 0
AND lastaccess < ? AND deleted = 0
AND (lastname = '' OR firstname = '' OR email = '')",
array($cuttime));
foreach ($rs as $user) {
if (isguestuser($user) or is_siteadmin($user)) {
continue;
}
if ($user->lastname !== '' and $user->firstname !== '' and $user->email !== '') {
// This can happen on MySQL - see MDL-52831.
continue;
}
delete_user($user);
mtrace(" Deleted not fully setup user $user->username ($user->id)");
}
$rs->close();
}
}
}
@@ -0,0 +1,64 @@
<?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/>.
/**
* Scheduled task abstract class.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete user accounts for users who have not confirmed in time.
*/
class delete_unconfirmed_users_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskdeleteunconfirmedusers', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
$timenow = time();
// Delete users who haven't confirmed within required period.
if (!empty($CFG->deleteunconfirmed)) {
$cuttime = $timenow - ($CFG->deleteunconfirmed * 3600);
$rs = $DB->get_recordset_sql ("SELECT *
FROM {user}
WHERE confirmed = 0 AND timecreated > 0
AND timecreated < ? AND deleted = 0", array($cuttime));
foreach ($rs as $user) {
delete_user($user);
mtrace(" Deleted unconfirmed user ".fullname($user, true)." ($user->id)");
}
$rs->close();
}
}
}
@@ -0,0 +1,70 @@
<?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 core\task;
use core\hook\task\after_failed_task_max_delay;
use core_user;
use stdClass;
/**
* Hook listener callbacks for tasks in core
*
* @package core
* @category task
* @copyright 2024 Raquel Ortega <raquel.ortega@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class failed_task_callbacks {
/**
* Callback to send a notification when the max fail delay of a task has been reached.
*
* @param after_failed_task_max_delay $hook
*/
public static function send_failed_task_max_delay_message(after_failed_task_max_delay $hook): void {
$task = $hook->get_task();
$admins = get_admins();
if (empty($admins)) {
return;
}
foreach ($admins as $admin) {
$a = new stdClass();
$a->firstname = $admin->firstname;
$a->taskname = $task->get_name();
$a->link = new \moodle_url('/report/status/index.php', ['detail' => 'tool_task_maxfaildelay']);
$messagetxt = get_string('failedtaskbody', 'moodle', $a);
// Create message.
$message = new \core\message\message();
$message->component = 'moodle';
$message->name = 'failedtaskmaxdelay';
$message->userfrom = core_user::get_noreply_user();
$message->userto = $admin;
$message->subject = get_string('failedtasksubject', 'moodle', $task->get_name());
$message->fullmessage = html_to_text($messagetxt);
$message->fullmessageformat = FORMAT_MARKDOWN;
$message->fullmessagehtml = text_to_html($messagetxt);
$message->smallmessage = get_string('failedtasksubject', 'moodle', $task->get_name());
$message->notification = 1;
$message->contexturl = (
new \moodle_url('/report/status/index.php', ['detail' => 'tool_task_maxfaildelay']))->out(false);
$message->contexturlname = get_string('failedtaskcontexturlname', 'moodle');
// Actually send the message.
message_send($message);
}
}
}
+122
View File
@@ -0,0 +1,122 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete temp files older than 1 week.
*/
class file_temp_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasktempfilecleanup', 'admin');
}
/**
* Do the job, given the target directory.
*
* @param string $tmpdir The directory hosting the candidate stale temp files.
*/
protected function execute_on($tmpdir) {
global $CFG;
// Default to last weeks time.
$time = time() - ($CFG->tempdatafoldercleanup * 3600);
$dir = new \RecursiveDirectoryIterator($tmpdir);
// Show all child nodes prior to their parent.
$iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST);
// An array of the full path (key) and date last modified.
$modifieddateobject = array();
// Get the time modified for each directory node. Nodes will be updated
// once a file is deleted, so we need a list of the original values.
for ($iter->rewind(); $iter->valid(); $iter->next()) {
$node = $iter->getRealPath();
if (!is_readable($node)) {
continue;
}
$modifieddateobject[$node] = $iter->getMTime();
}
// Now loop through again and remove old files and directories.
for ($iter->rewind(); $iter->valid(); $iter->next()) {
$node = $iter->getRealPath();
if (!isset($modifieddateobject[$node]) || !is_readable($node)) {
continue;
}
// Check if file or directory is older than the given time.
if ($modifieddateobject[$node] < $time) {
if ($iter->isDir() && !$iter->isDot()) {
// Don't attempt to delete the directory if it isn't empty.
if (!glob($node. DIRECTORY_SEPARATOR . '*')) {
if (@rmdir($node) === false) {
mtrace("Failed removing directory '$node'.");
}
}
}
if ($iter->isFile()) {
if (@unlink($node) === false) {
mtrace("Failed removing file '$node'.");
}
}
} else {
// Return the time modified to the original date only for real files.
if ($iter->isDir() && !$iter->isDot()) {
try {
@touch($node, $modifieddateobject[$node]);
} catch (\Throwable $t) {
null;
}
}
}
}
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
// The directories hosting the candidate stale temp files eventually are $CFG->tempdir and $CFG->backuptempdir.
// Do the job on each of the directories above.
// Let's start with $CFG->tempdir.
$this->execute_on($CFG->tempdir);
// Run on $CFG->backuptempdir too, if different from the default one, '$CFG->tempdir/backup'.
if (realpath(dirname($CFG->backuptempdir)) !== realpath($CFG->tempdir)) {
// The $CFG->backuptempdir setting is different from the default '$CFG->tempdir/backup'.
$this->execute_on($CFG->backuptempdir);
}
}
}
@@ -0,0 +1,51 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the file trash cleanup cron.
*/
class file_trash_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskfiletrashcleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
// Cleanup file trash - not very important.
$fs = get_file_storage();
$fs->cron();
}
}
@@ -0,0 +1,38 @@
<?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 core\task;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/db/upgradelib.php');
/**
* Retroactively fixes file timestamps that are older than the containing folder record.
*
* @package core
* @author Peter Burnett <peterburnett@catalyst-au.net>
* @copyright Catalyst IT, 2021
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fix_file_timestamps_task extends adhoc_task {
/**
* Run the adhoc task and fix the file timestamps.
*/
public function execute() {
upgrade_fix_file_timestamps();
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the grade cron.
*/
class grade_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskgradecron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
require_once($CFG->libdir . '/gradelib.php');
$now = time();
$sql = "SELECT i.*
FROM {grade_items} i
WHERE i.locked = 0
AND i.locktime > 0
AND i.locktime < ?
AND EXISTS (SELECT 'x'
FROM {grade_items} c
WHERE c.itemtype='course'
AND c.needsupdate=0
AND c.courseid=i.courseid)";
// Go through all courses that have proper final grades and lock them if needed.
$rs = $DB->get_recordset_sql($sql, array($now));
foreach ($rs as $item) {
$gradeitem = new \grade_item($item, false);
$gradeitem->locked = $now;
$gradeitem->update('locktime');
}
$rs->close();
$gradeinst = new \grade_grade();
$fields = 'g.' . implode(',g.', $gradeinst->required_fields);
$sql = "SELECT $fields
FROM {grade_grades} g, {grade_items} i
WHERE g.locked = 0
AND g.locktime > 0
AND g.locktime < ?
AND g.itemid = i.id
AND EXISTS (SELECT 'x'
FROM {grade_items} c
WHERE c.itemtype = 'course'
AND c.needsupdate = 0
AND c.courseid = i.courseid)";
// Go through all courses that have proper final grades and lock them if needed.
$rs = $DB->get_recordset_sql($sql, array($now));
foreach ($rs as $grade) {
$gradegrade = new \grade_grade($grade, false);
$gradegrade->locked = $now;
$gradegrade->update('locktime');
}
$rs->close();
}
}
@@ -0,0 +1,67 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Simple task to clean grade history tables.
*
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grade_history_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskgradehistorycleanup', 'admin');
}
/**
* Cleanup history tables.
*/
public function execute() {
global $CFG, $DB;
if (!empty($CFG->gradehistorylifetime)) {
$now = time();
$histlifetime = $now - ($CFG->gradehistorylifetime * DAYSECS);
$tables = [
'grade_outcomes_history',
'grade_categories_history',
'grade_items_history',
'grade_grades_history',
'scale_history'
];
foreach ($tables as $table) {
if ($DB->delete_records_select($table, "timemodified < ?", [$histlifetime])) {
mtrace(" Deleted old grade history records from '$table'");
}
}
}
}
}
@@ -0,0 +1,74 @@
<?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 core\task;
/**
* A schedule task to clean orphaned h5p records (for example for deleted activity).
*
* @package core_h5p
* @copyright 2021 Ilya Tregubov <ilya@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class h5p_clean_orphaned_records_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskh5pcleanup', 'admin');
}
/**
* Execute the task.
*/
public function execute() {
global $DB;
$sql = "SELECT h5p.id
FROM {h5p} h5p
LEFT JOIN {files} f
ON f.pathnamehash = h5p.pathnamehash
WHERE f.pathnamehash IS NULL";
$orphanedrecords = $DB->get_recordset_sql($sql);
foreach ($orphanedrecords as $orphanedrecord) {
$sql = "SELECT f.id, f.pathnamehash
FROM {files} f
WHERE f.itemid = :itemid
AND f.filearea = :filearea
AND f.component = :component";
$params = ['itemid' => $orphanedrecord->id, 'filearea' => 'content', 'component' => 'core_h5p'];
$filerecords = $DB->get_recordset_sql($sql, $params);
foreach ($filerecords as $filerecord) {
$fs = get_file_storage();
$file = $fs->get_file_by_hash($filerecord->pathnamehash);
if ($file) {
$file->delete();
}
}
$DB->delete_records('h5p', ['id' => $orphanedrecord->id]);
$DB->delete_records('h5p_contents_libraries', ['h5pid' => $orphanedrecord->id]);
}
}
}
@@ -0,0 +1,73 @@
<?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 core\task;
use core_h5p\factory;
use invalid_response_exception;
/**
* A task to get the latest content types from the official H5P repository.
*
* @package core
* @copyright 2019 Victor Deniz <victor@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class h5p_get_content_types_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('h5pgetcontenttypestask', 'admin');
}
/**
* Get an \core_h5p\core instance.
*
* @return \core_h5p\core
*/
public function get_core() {
$factory = new factory();
$core = $factory->get_core();
return $core;
}
/**
* Execute the task.
*
* @throws invalid_response_exception If request to get the latest content types fails (usually due to a transient error)
*/
public function execute() {
// MDL-68579, avoid execute the task through behat tests.
if (defined('BEHAT_SITE_RUNNING')) {
return true;
}
$core = $this->get_core();
$result = $core->fetch_latest_content_types();
if (!empty($result->error)) {
throw new invalid_response_exception($result->error);
} else {
$numtypesinstalled = count($result->typesinstalled);
mtrace("{$numtypesinstalled} new content types installed");
}
}
}
@@ -0,0 +1,52 @@
<?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 core\task;
/**
* Simple task to automatically set the course visibility to hidden when the course end date matches the current day.
*
* @package core
* @copyright 2023 Sara Arjona <sara@moodle.com> based on code from 2016 Tim Gagen and Amanda Doughty
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hide_ended_courses_task extends show_started_courses_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('hideendedcoursestask', 'course');
}
protected function get_field_date(): string {
return 'enddate';
}
protected function get_visibility(): int {
return 0;
}
protected function get_trace_message(): string {
return 'Hide';
}
protected function get_event_classname(): string {
return '\core\event\course_ended';
}
}
+112
View File
@@ -0,0 +1,112 @@
<?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/>.
/**
* This file defines a trait to assist with logging in tasks.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* This trait includes functions to assist with logging in tasks.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait logging_trait {
/**
* @var \progress_trace
*/
protected $trace = null;
/**
* @var \stdClass
*/
protected $tracestats = null;
/**
* Get the progress_trace.
*
* @return \progress_trace
*/
protected function get_trace() {
if (null === $this->trace) {
$this->trace = new \text_progress_trace();
$this->tracestats = new \stdClass();
}
return $this->trace;
}
/**
* Log a message to the progress tracer.
*
* @param string $message
* @param int $depth
*/
protected function log($message, $depth = 1) {
$this->get_trace()
->output($message, $depth);
}
/**
* Log a start message to the progress tracer.
*
* @param string $message
* @param int $depth
*/
protected function log_start($message, $depth = 0) {
$this->log($message, $depth);
if (MDL_PERFTOLOG) {
$this->tracestats->$depth = [
'mem' => memory_get_usage(),
'time' => microtime(),
];
}
}
/**
* Log an end message to the progress tracer.
*
* @param string $message
* @param int $depth
*/
protected function log_finish($message, $depth = 0) {
$this->log($message, $depth);
if (isset($this->tracestats->$depth)) {
$startstats = $this->tracestats->$depth;
$this->log(
sprintf("Time taken %s, memory total: %s, Memory growth: %s, Memory peak: %s",
microtime_diff($startstats['time'], microtime()),
display_size(memory_get_usage()),
display_size(memory_get_usage() - $startstats['mem']),
display_size(memory_get_peak_usage())
),
$depth + 1
);
}
}
}
+352
View File
@@ -0,0 +1,352 @@
<?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/>.
/**
* Task log manager.
*
* @package core
* @category task
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Task log manager.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class logmanager {
/** @var int Do not log anything */
const MODE_NONE = 0;
/** @var int Log all tasks */
const MODE_ALL = 1;
/** @var int Only log fails */
const MODE_FAILONLY = 2;
/** @var int The default chunksize to use in ob_start */
const CHUNKSIZE = 1;
/**
* @var \core\task\task_base The task being logged.
*/
protected static $task = null;
/**
* @var \stdClass Metadata about the current log
*/
protected static $taskloginfo = null;
/**
* @var \resource The current filehandle used for logging
*/
protected static $fh = null;
/**
* @var string The path to the log file
*/
protected static $logpath = null;
/**
* @var bool Whether the task logger has been registered with the shutdown handler
*/
protected static $tasklogregistered = false;
/**
* @var int The level of output buffering in place before starting.
*/
protected static $oblevel = null;
/**
* @var bool Output logged content to screen.
*/
protected static $outputloggedcontent = true;
/**
* Create a new task logger for the specified task, and prepare for logging.
*
* @param \core\task\task_base $task The task being run
*/
public static function start_logging(task_base $task) {
global $CFG, $DB;
if (!self::should_log()) {
return;
}
// We register a shutdown handler to ensure that logs causing any failures are correctly disposed of.
// Note: This must happen before the per-request directory is requested because the shutdown handler deletes the logfile.
if (!self::$tasklogregistered) {
\core_shutdown_manager::register_function(function() {
// These will only actually do anything if capturing is current active when the thread ended, which
// constitutes a failure.
\core\task\logmanager::finalise_log(true);
});
// Create a brand new per-request directory basedir.
get_request_storage_directory(true, true);
self::$tasklogregistered = true;
}
if (self::is_current_output_buffer()) {
// We cannot capture when we are already capturing.
throw new \coding_exception('Logging is already in progress for task "' . get_class(self::$task) . '". ' .
'Nested logging is not supported.');
}
// Store the initial data about the task and current state.
self::$task = $task;
self::$taskloginfo = (object) [
'dbread' => $DB->perf_get_reads(),
'dbwrite' => $DB->perf_get_writes(),
'timestart' => microtime(true),
];
// For simplicity's sake we always store logs on disk and flush at the end.
self::$logpath = make_request_directory() . DIRECTORY_SEPARATOR . "task.log";
self::$fh = fopen(self::$logpath, 'w+');
// Note the level of the current output buffer.
// Note: You cannot use ob_get_level() as it will return `1` when the default output buffer is enabled.
if ($obstatus = ob_get_status()) {
self::$oblevel = $obstatus['level'];
} else {
self::$oblevel = null;
}
self::$outputloggedcontent = !empty($CFG->task_logtostdout);
// Start capturing output.
ob_start([\core\task\logmanager::class, 'add_line'], self::CHUNKSIZE);
}
/**
* Whether logging is possible and should be happening.
*
* @return bool
*/
protected static function should_log(): bool {
global $CFG;
// Respect the config setting.
if (isset($CFG->task_logmode) && empty($CFG->task_logmode)) {
return false;
}
$loggerclass = self::get_logger_classname();
if (empty($loggerclass)) {
return false;
}
return $loggerclass::is_configured();
}
/**
* Return the name of the logging class to use.
*
* @return string
*/
public static function get_logger_classname(): string {
global $CFG;
if (!empty($CFG->task_log_class)) {
// Configuration is present to use an alternative task logging class.
return $CFG->task_log_class;
}
// Fall back on the default database logger.
return database_logger::class;
}
/**
* Whether this task logger has a report available.
*
* @return bool
*/
public static function has_log_report(): bool {
$loggerclass = self::get_logger_classname();
return $loggerclass::has_log_report();
}
/**
* Whether to use the standard settings form.
*/
public static function uses_standard_settings(): bool {
$classname = self::get_logger_classname();
if (!class_exists($classname)) {
return false;
}
if (is_a($classname, database_logger::class, true)) {
return true;
}
return false;
}
/**
* Get any URL available for viewing relevant task log reports.
*
* @param string $classname The task class to fetch for
* @return \moodle_url
*/
public static function get_url_for_task_class(string $classname): \moodle_url {
$loggerclass = self::get_logger_classname();
return $loggerclass::get_url_for_task_class($classname);
}
/**
* Whether we are the current log collector.
*
* @return bool
*/
protected static function is_current_output_buffer(): bool {
if (empty(self::$taskloginfo)) {
return false;
}
if ($ob = ob_get_status()) {
return 'core\\task\\logmanager::add_line' == $ob['name'];
}
return false;
}
/**
* Whether we are capturing at all.
*
* @return bool
*/
protected static function is_capturing(): bool {
$buffers = ob_get_status(true);
foreach ($buffers as $ob) {
if ('core\\task\\logmanager::add_line' == $ob['name']) {
return true;
}
}
return false;
}
/**
* Finish writing for the current task.
*
* @param bool $failed
*/
public static function finalise_log(bool $failed = false) {
global $CFG, $DB, $PERF;
if (!self::should_log()) {
return;
}
if (!self::is_capturing()) {
// Not capturing anything.
return;
}
// Ensure that all logs are closed.
$buffers = ob_get_status(true);
foreach (array_reverse($buffers) as $ob) {
if (null !== self::$oblevel) {
if ($ob['level'] <= self::$oblevel) {
// Only close as far as the initial output buffer level.
break;
}
}
// End and flush this buffer.
ob_end_flush();
if ('core\\task\\logmanager::add_line' == $ob['name']) {
break;
}
}
self::$oblevel = null;
// Flush any remaining buffer.
self::flush();
// Close and unset the FH.
fclose(self::$fh);
self::$fh = null;
if ($failed || empty($CFG->task_logmode) || self::MODE_ALL == $CFG->task_logmode) {
// Finalise the log.
$loggerclass = self::get_logger_classname();
$loggerclass::store_log_for_task(
self::$task,
self::$logpath,
$failed,
$DB->perf_get_reads() - self::$taskloginfo->dbread,
$DB->perf_get_writes() - self::$taskloginfo->dbwrite,
self::$taskloginfo->timestart,
microtime(true)
);
}
// Tidy up.
self::$logpath = null;
self::$taskloginfo = null;
}
/**
* Flush the current output buffer.
*
* This function will ensure that we are the current output buffer handler.
*/
public static function flush() {
// We only call ob_flush if the current output buffer belongs to us.
if (self::is_current_output_buffer()) {
ob_flush();
}
}
/**
* Add a log record to the task log.
*
* @param string $log
* @return string
*/
public static function add_line(string $log): string {
if (empty(self::$taskloginfo)) {
return $log;
}
if (empty(self::$fh)) {
return $log;
}
if (self::is_current_output_buffer()) {
fwrite(self::$fh, $log);
}
if (self::$outputloggedcontent) {
return $log;
} else {
return '';
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete old messaging records.
*/
class messaging_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskmessagingcleanup', 'admin');
}
/**
* Do the job. Each message processor also gets the chance to perform it's own cleanup.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
$timenow = time();
$processors = get_message_processors(true);
// Cleanup read and unread notifications.
if (!empty($CFG->messagingdeleteallnotificationsdelay)) {
$notificationdeletetime = $timenow - $CFG->messagingdeleteallnotificationsdelay;
/** @var \message_output $processor */
foreach (array_column($processors, 'object') as $processor) {
$processor->cleanup_all_notifications($notificationdeletetime);
}
$params = array('notificationdeletetime' => $notificationdeletetime);
$DB->delete_records_select('notifications', 'timecreated < :notificationdeletetime', $params);
}
// Cleanup read notifications.
if (!empty($CFG->messagingdeletereadnotificationsdelay)) {
$notificationdeletetime = $timenow - $CFG->messagingdeletereadnotificationsdelay;
/** @var \message_output $processor */
foreach (array_column($processors, 'object') as $processor) {
$processor->cleanup_read_notifications($notificationdeletetime);
}
$params = array('notificationdeletetime' => $notificationdeletetime);
$DB->delete_records_select('notifications', 'timeread < :notificationdeletetime', $params);
}
}
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to delete old password reset records.
*/
class password_reset_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskpasswordresetcleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $DB, $CFG;
// Cleanup user password reset records.
// Delete any reset request records which are expired by more than a day.
// (We keep recently expired requests around so we can give a different error msg to users who
// are trying to user a recently expired reset attempt).
$pwresettime = isset($CFG->pwresettime) ? $CFG->pwresettime : 1800;
$earliestvalid = time() - $pwresettime - DAYSECS;
$DB->delete_records_select('user_password_resets', "timerequested < ?", array($earliestvalid));
mtrace(' Cleaned up old password reset records');
}
}
+65
View File
@@ -0,0 +1,65 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the plagiarism cron.
*/
class plagiarism_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskplagiarismcron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
if (!empty($CFG->enableplagiarism)) {
require_once($CFG->libdir.'/plagiarismlib.php');
$plagiarismplugins = plagiarism_load_available_plugins();
foreach ($plagiarismplugins as $plugin => $dir) {
require_once($dir . '/lib.php');
$plagiarismclass = "plagiarism_plugin_$plugin";
$plagiarismplugin = new $plagiarismclass;
if (method_exists($plagiarismplugin, 'cron')) {
mtrace('Processing cron function for plagiarism_plugin_' . $plugin . '...', '');
\core\cron::trace_time_and_memory();
mtrace('It has been detected the class ' . $plagiarismclass . ' has a legacy cron method
implemented. Plagiarism plugins should implement their own schedule tasks.', '');
$plagiarismplugin->cron();
}
}
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the portfolio cron.
*/
class portfolio_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskportfoliocron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
if ($CFG->enableportfolios) {
require_once($CFG->libdir . '/portfoliolib.php');
require_once($CFG->libdir . '/portfolio/exporter.php');
if ($expired = $DB->get_records_select('portfolio_tempdata', 'expirytime < ?', [time()], '', 'id')) {
foreach ($expired as $tempdata) {
try {
$exporter = \portfolio_exporter::rewaken_object($tempdata->id);
$exporter->process_stage_cleanup(true);
} catch (\Exception $exception) {
mtrace('Exception thrown in portfolio cron while cleaning up ' . $tempdata->id . ': ' .
$exception->getMessage());
}
}
}
$process = $DB->get_records('portfolio_tempdata', ['queued' => 1], 'id ASC', 'id');
foreach ($process as $tempdata) {
try {
$exporter = \portfolio_exporter::rewaken_object($tempdata->id);
$exporter->process_stage_package();
$exporter->process_stage_send();
$exporter->save();
$exporter->process_stage_cleanup();
} catch (\Exception $exception) {
// This will get probably retried in the next cron until it is discarded by the code above.
mtrace('Exception thrown in portfolio cron while processing ' . $tempdata->id . ': ' .
$exception->getMessage());
}
}
}
}
}
@@ -0,0 +1,75 @@
<?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/>.
/**
* Task to cleanup old question previews.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* A task to cleanup old question previews.
*
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_preview_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskquestioncron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/question/engine/lib.php');
// We delete previews that have not been touched for 24 hours.
$lastmodifiedcutoff = time() - DAYSECS;
mtrace("\n Cleaning up old question previews...", '');
$oldpreviews = new \qubaid_join('{question_usages} quba', 'quba.id',
'quba.component = :qubacomponent
AND NOT EXISTS (
SELECT 1
FROM {question_attempts} subq_qa
JOIN {question_attempt_steps} subq_qas ON subq_qas.questionattemptid = subq_qa.id
JOIN {question_usages} subq_qu ON subq_qu.id = subq_qa.questionusageid
WHERE subq_qa.questionusageid = quba.id
AND subq_qu.component = :qubacomponent2
AND (subq_qa.timemodified > :qamodifiedcutoff
OR subq_qas.timecreated > :stepcreatedcutoff)
)
',
['qubacomponent' => 'core_question_preview', 'qubacomponent2' => 'core_question_preview',
'qamodifiedcutoff' => $lastmodifiedcutoff, 'stepcreatedcutoff' => $lastmodifiedcutoff]);
\question_engine::delete_questions_usage_by_activities($oldpreviews);
mtrace('done.');
}
}
@@ -0,0 +1,77 @@
<?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/>.
/**
* Adhoc task that updates all of the existing calendar events for modules that implement the *_refresh_events() hook.
*
* @package core
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use core_plugin_manager;
defined('MOODLE_INTERNAL') || die();
/**
* Class that updates all of the existing calendar events for modules that implement the *_refresh_events() hook.
*
* Custom data accepted:
* - plugins -> Array of plugin names that need to be refreshed. Optional.
*
* @package core
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class refresh_mod_calendar_events_task extends adhoc_task {
/**
* Run the task to refresh calendar events.
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
// Specific list of plugins that need to be refreshed. If not set, then all mod plugins will be refreshed.
$pluginstorefresh = null;
if (isset($this->get_custom_data()->plugins)) {
$pluginstorefresh = $this->get_custom_data()->plugins;
}
// Is course id set?
if (isset($this->get_custom_data()->courseid)) {
$courseid = $this->get_custom_data()->courseid;
} else {
$courseid = 0;
}
$pluginmanager = core_plugin_manager::instance();
$modplugins = $pluginmanager->get_plugins_of_type('mod');
foreach ($modplugins as $plugin) {
// Check if a specific list of plugins is defined and check if it contains the plugin that is currently being evaluated.
if (!empty($pluginstorefresh) && !in_array($plugin->name, $pluginstorefresh)) {
// This plugin is not in the list, move on to the next one.
continue;
}
// Refresh events.
mtrace('Refreshing events for ' . $plugin->name);
course_module_bulk_update_calendar_events($plugin->name, $courseid);
}
}
}
@@ -0,0 +1,48 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the registration cron.
*/
class registration_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskregistrationcron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
\core\hub\registration::update_cron();
}
}
+703
View File
@@ -0,0 +1,703 @@
<?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/>.
/**
* Scheduled task abstract class.
*
* @package core
* @category task
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Abstract class defining a scheduled task.
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class scheduled_task extends task_base {
/** Minimum minute value. */
const MINUTEMIN = 0;
/** Maximum minute value. */
const MINUTEMAX = 59;
/** Minimum hour value. */
const HOURMIN = 0;
/** Maximum hour value. */
const HOURMAX = 23;
/** Minimum day of month value. */
const DAYMIN = 1;
/** Maximum day of month value. */
const DAYMAX = 31;
/** Minimum month value. */
const MONTHMIN = 1;
/** Maximum month value. */
const MONTHMAX = 12;
/** Minimum dayofweek value. */
const DAYOFWEEKMIN = 0;
/** Maximum dayofweek value. */
const DAYOFWEEKMAX = 6;
/** Maximum dayofweek value allowed in input (7 = 0). */
const DAYOFWEEKMAXINPUT = 7;
/**
* Minute field identifier.
*/
const FIELD_MINUTE = 'minute';
/**
* Hour field identifier.
*/
const FIELD_HOUR = 'hour';
/**
* Day-of-month field identifier.
*/
const FIELD_DAY = 'day';
/**
* Month field identifier.
*/
const FIELD_MONTH = 'month';
/**
* Day-of-week field identifier.
*/
const FIELD_DAYOFWEEK = 'dayofweek';
/**
* Time used for the next scheduled time when a task should never run. This is 2222-01-01 00:00 GMT
* which is a large time that still fits in 10 digits.
*/
const NEVER_RUN_TIME = 7952342400;
/** @var string $hour - Pattern to work out the valid hours */
private $hour = '*';
/** @var string $minute - Pattern to work out the valid minutes */
private $minute = '*';
/** @var string $day - Pattern to work out the valid days */
private $day = '*';
/** @var string $month - Pattern to work out the valid months */
private $month = '*';
/** @var string $dayofweek - Pattern to work out the valid dayofweek */
private $dayofweek = '*';
/** @var int $lastruntime - When this task was last run */
private $lastruntime = 0;
/** @var boolean $customised - Has this task been changed from it's default schedule? */
private $customised = false;
/** @var boolean $overridden - Does the task have values set VIA config? */
private $overridden = false;
/** @var int $disabled - Is this task disabled in cron? */
private $disabled = false;
/**
* Get the last run time for this scheduled task.
*
* @return int
*/
public function get_last_run_time() {
return $this->lastruntime;
}
/**
* Set the last run time for this scheduled task.
*
* @param int $lastruntime
*/
public function set_last_run_time($lastruntime) {
$this->lastruntime = $lastruntime;
}
/**
* Has this task been changed from it's default config?
*
* @return bool
*/
public function is_customised() {
return $this->customised;
}
/**
* Set customised for this scheduled task.
*
* @param bool
*/
public function set_customised($customised) {
$this->customised = $customised;
}
/**
* Determine if this task is using its default configuration changed from the default. Returns true
* if it is and false otherwise. Does not rely on the customised field.
*
* @return bool
*/
public function has_default_configuration(): bool {
$defaulttask = \core\task\manager::get_default_scheduled_task($this::class);
if ($defaulttask->get_minute() !== $this->get_minute()) {
return false;
}
if ($defaulttask->get_hour() != $this->get_hour()) {
return false;
}
if ($defaulttask->get_month() != $this->get_month()) {
return false;
}
if ($defaulttask->get_day_of_week() != $this->get_day_of_week()) {
return false;
}
if ($defaulttask->get_day() != $this->get_day()) {
return false;
}
if ($defaulttask->get_disabled() != $this->get_disabled()) {
return false;
}
return true;
}
/**
* Disable the task.
*/
public function disable(): void {
$this->set_disabled(true);
$this->set_customised(!$this->has_default_configuration());
\core\task\manager::configure_scheduled_task($this);
}
/**
* Enable the task.
*/
public function enable(): void {
$this->set_disabled(false);
$this->set_customised(!$this->has_default_configuration());
\core\task\manager::configure_scheduled_task($this);
}
/**
* Has this task been changed from it's default config?
*
* @return bool
*/
public function is_overridden(): bool {
return $this->overridden;
}
/**
* Set the overridden value.
*
* @param bool $overridden
*/
public function set_overridden(bool $overridden): void {
$this->overridden = $overridden;
}
/**
* Setter for $minute. Accepts a special 'R' value
* which will be translated to a random minute.
*
* @param string $minute
* @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
* If false, they are left as 'R'
*/
public function set_minute($minute, $expandr = true) {
if ($minute === 'R' && $expandr) {
$minute = mt_rand(self::MINUTEMIN, self::MINUTEMAX);
}
$this->minute = $minute;
}
/**
* Getter for $minute.
*
* @return string
*/
public function get_minute() {
return $this->minute;
}
/**
* Setter for $hour. Accepts a special 'R' value
* which will be translated to a random hour.
*
* @param string $hour
* @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
* If false, they are left as 'R'
*/
public function set_hour($hour, $expandr = true) {
if ($hour === 'R' && $expandr) {
$hour = mt_rand(self::HOURMIN, self::HOURMAX);
}
$this->hour = $hour;
}
/**
* Getter for $hour.
*
* @return string
*/
public function get_hour() {
return $this->hour;
}
/**
* Setter for $month.
*
* @param string $month
*/
public function set_month($month) {
$this->month = $month;
}
/**
* Getter for $month.
*
* @return string
*/
public function get_month() {
return $this->month;
}
/**
* Setter for $day.
*
* @param string $day
*/
public function set_day($day) {
$this->day = $day;
}
/**
* Getter for $day.
*
* @return string
*/
public function get_day() {
return $this->day;
}
/**
* Setter for $dayofweek.
*
* @param string $dayofweek
* @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
* If false, they are left as 'R'
*/
public function set_day_of_week($dayofweek, $expandr = true) {
if ($dayofweek === 'R' && $expandr) {
$dayofweek = mt_rand(self::DAYOFWEEKMIN, self::DAYOFWEEKMAX);
}
$this->dayofweek = $dayofweek;
}
/**
* Getter for $dayofweek.
*
* @return string
*/
public function get_day_of_week() {
return $this->dayofweek;
}
/**
* Setter for $disabled.
*
* @param bool $disabled
*/
public function set_disabled($disabled) {
$this->disabled = (bool)$disabled;
}
/**
* Getter for $disabled.
* @return bool
*/
public function get_disabled() {
return $this->disabled;
}
/**
* Override this function if you want this scheduled task to run, even if the component is disabled.
*
* @return bool
*/
public function get_run_if_component_disabled() {
return false;
}
/**
* Informs whether the given field is valid.
* Use the constants FIELD_* to identify the field.
* Have to be called after the method set_{field}(string).
*
* @param string $field field identifier; expected values from constants FIELD_*.
*
* @return bool true if given field is valid. false otherwise.
*/
public function is_valid(string $field): bool {
return !empty($this->get_valid($field));
}
/**
* Calculates the list of valid values according to the given field and stored expression.
*
* @param string $field field identifier. Must be one of those FIELD_*.
*
* @return array(int) list of matching values.
*
* @throws \coding_exception when passed an invalid field identifier.
*/
private function get_valid(string $field): array {
switch($field) {
case self::FIELD_MINUTE:
$min = self::MINUTEMIN;
$max = self::MINUTEMAX;
break;
case self::FIELD_HOUR:
$min = self::HOURMIN;
$max = self::HOURMAX;
break;
case self::FIELD_DAY:
$min = self::DAYMIN;
$max = self::DAYMAX;
break;
case self::FIELD_MONTH:
$min = self::MONTHMIN;
$max = self::MONTHMAX;
break;
case self::FIELD_DAYOFWEEK:
$min = self::DAYOFWEEKMIN;
$max = self::DAYOFWEEKMAXINPUT;
break;
default:
throw new \coding_exception("Field '$field' is not a valid crontab identifier.");
}
$result = $this->eval_cron_field($this->{$field}, $min, $max);
if ($field === self::FIELD_DAYOFWEEK) {
// For day of week, 0 and 7 both mean Sunday; if there is a 7 we set 0. The result array is sorted.
if (end($result) === 7) {
// Remove last element.
array_pop($result);
// Insert 0 as first element if it's not there already.
if (reset($result) !== 0) {
array_unshift($result, 0);
}
}
}
return $result;
}
/**
* Take a cron field definition and return an array of valid numbers with the range min-max.
*
* @param string $field - The field definition.
* @param int $min - The minimum allowable value.
* @param int $max - The maximum allowable value.
* @return array(int)
*/
public function eval_cron_field($field, $min, $max) {
// Cleanse the input.
$field = trim($field);
// Format for a field is:
// <fieldlist> := <range>(/<step>)(,<fieldlist>)
// <step> := int
// <range> := <any>|<int>|<min-max>
// <any> := *
// <min-max> := int-int
// End of format BNF.
// This function is complicated but is covered by unit tests.
$range = array();
$matches = array();
preg_match_all('@[0-9]+|\*|,|/|-@', $field, $matches);
$last = 0;
$inrange = false;
$instep = false;
foreach ($matches[0] as $match) {
if ($match == '*') {
array_push($range, range($min, $max));
} else if ($match == '/') {
$instep = true;
} else if ($match == '-') {
$inrange = true;
} else if (is_numeric($match)) {
if ($min > $match || $match > $max) {
// This is a value error: The value lays out of the expected range of values.
return [];
}
if ($instep) {
// Normalise range property, account for "5/10".
$insteprange = $range[count($range) - 1];
if (!is_array($insteprange)) {
$range[count($range) - 1] = range($insteprange, $max);
}
for ($i = 0; $i < count($range[count($range) - 1]); $i++) {
if (($i) % $match != 0) {
$range[count($range) - 1][$i] = -1;
}
}
$instep = false;
} else if ($inrange) {
if (count($range)) {
$range[count($range) - 1] = range($last, $match);
}
$inrange = false;
} else {
array_push($range, $match);
$last = $match;
}
}
}
// If inrange or instep were not processed, there is a syntax error.
// Cleanup any existing values to show up the error.
if ($inrange || $instep) {
return [];
}
// Flatten the result.
$result = array();
foreach ($range as $r) {
if (is_array($r)) {
foreach ($r as $rr) {
if ($rr >= $min && $rr <= $max) {
$result[$rr] = 1;
}
}
} else if (is_numeric($r)) {
if ($r >= $min && $r <= $max) {
$result[$r] = 1;
}
}
}
$result = array_keys($result);
sort($result, SORT_NUMERIC);
return $result;
}
/**
* Assuming $list is an ordered list of items, this function returns the item
* in the list that is greater than or equal to the current value (or 0). If
* no value is greater than or equal, this will return the first valid item in the list.
* If list is empty, this function will return 0.
*
* @param int $current The current value
* @param int[] $list The list of valid items.
* @return int $next.
*/
private function next_in_list($current, $list) {
foreach ($list as $l) {
if ($l >= $current) {
return $l;
}
}
if (count($list)) {
return $list[0];
}
return 0;
}
/**
* Calculate when this task should next be run based on the schedule.
*
* @param int $now Current time, for testing (leave 0 to use default time)
* @return int $nextruntime.
*/
public function get_next_scheduled_time(int $now = 0): int {
if (!$now) {
$now = time();
}
// We need to change to the server timezone before using php date() functions.
\core_date::set_default_server_timezone();
$validminutes = $this->get_valid(self::FIELD_MINUTE);
$validhours = $this->get_valid(self::FIELD_HOUR);
$validdays = $this->get_valid(self::FIELD_DAY);
$validdaysofweek = $this->get_valid(self::FIELD_DAYOFWEEK);
$validmonths = $this->get_valid(self::FIELD_MONTH);
// If any of the fields contain no valid data then the task will never run.
if (!$validminutes || !$validhours || !$validdays || !$validdaysofweek || !$validmonths) {
return self::NEVER_RUN_TIME;
}
$result = self::get_next_scheduled_time_inner($now, $validminutes, $validhours, $validdays, $validdaysofweek, $validmonths);
return $result;
}
/**
* Recursively calculate the next valid time for this task.
*
* @param int $now Start time
* @param array $validminutes Valid minutes
* @param array $validhours Valid hours
* @param array $validdays Valid days
* @param array $validdaysofweek Valid days of week
* @param array $validmonths Valid months
* @param int $originalyear Zero for first call, original year for recursive calls
* @return int Next run time
*/
protected function get_next_scheduled_time_inner(int $now, array $validminutes, array $validhours,
array $validdays, array $validdaysofweek, array $validmonths, int $originalyear = 0) {
$currentyear = (int)date('Y', $now);
if ($originalyear) {
// In recursive calls, check we didn't go more than 8 years ahead, that indicates the
// user has chosen an impossible date. 8 years is the maximum time, considering a task
// set to run on 29 February over a century boundary when a leap year is skipped.
if ($currentyear - $originalyear > 8) {
// Use this time if it's never going to happen.
return self::NEVER_RUN_TIME;
}
$firstyear = $originalyear;
} else {
$firstyear = $currentyear;
}
$currentmonth = (int)date('n', $now);
// Evaluate month first.
$nextvalidmonth = $this->next_in_list($currentmonth, $validmonths);
if ($nextvalidmonth < $currentmonth) {
$currentyear += 1;
}
// If we moved to another month, set the current time to start of month, and restart calculations.
if ($nextvalidmonth !== $currentmonth) {
$newtime = strtotime($currentyear . '-' . $nextvalidmonth . '-01 00:00');
return $this->get_next_scheduled_time_inner($newtime, $validminutes, $validhours, $validdays,
$validdaysofweek, $validmonths, $firstyear);
}
// Special handling for dayofmonth vs dayofweek (see man 5 cron). If both are specified, then
// it is ok to continue when either matches. If only one is specified then it must match.
$currentday = (int)date("j", $now);
$currentdayofweek = (int)date("w", $now);
$nextvaliddayofmonth = self::next_in_list($currentday, $validdays);
$nextvaliddayofweek = self::next_in_list($currentdayofweek, $validdaysofweek);
$daysincrementbymonth = $nextvaliddayofmonth - $currentday;
$daysinmonth = (int)date('t', $now);
if ($nextvaliddayofmonth < $currentday) {
$daysincrementbymonth += $daysinmonth;
}
$daysincrementbyweek = $nextvaliddayofweek - $currentdayofweek;
if ($nextvaliddayofweek < $currentdayofweek) {
$daysincrementbyweek += 7;
}
if ($this->dayofweek == '*') {
$daysincrement = $daysincrementbymonth;
} else if ($this->day == '*') {
$daysincrement = $daysincrementbyweek;
} else {
// Take the smaller increment of days by month or week.
$daysincrement = min($daysincrementbymonth, $daysincrementbyweek);
}
// If we moved day, recurse using new start time.
if ($daysincrement != 0) {
$newtime = strtotime($currentyear . '-' . $currentmonth . '-' . $currentday .
' 00:00 +' . $daysincrement . ' days');
return $this->get_next_scheduled_time_inner($newtime, $validminutes, $validhours, $validdays,
$validdaysofweek, $validmonths, $firstyear);
}
$currenthour = (int)date('H', $now);
$nextvalidhour = $this->next_in_list($currenthour, $validhours);
if ($nextvalidhour != $currenthour) {
if ($nextvalidhour < $currenthour) {
$offset = ' +1 day';
} else {
$offset = '';
}
$newtime = strtotime($currentyear . '-' . $currentmonth . '-' . $currentday . ' ' . $nextvalidhour .
':00' . $offset);
return $this->get_next_scheduled_time_inner($newtime, $validminutes, $validhours, $validdays,
$validdaysofweek, $validmonths, $firstyear);
}
// Round time down to an exact minute because we need to use numeric calculations on it now.
// If we construct times based on all the components, it will mess up around DST changes
// (because there are two times with the same representation).
$now = intdiv($now, 60) * 60;
$currentminute = (int)date('i', $now);
$nextvalidminute = $this->next_in_list($currentminute, $validminutes);
if ($nextvalidminute == $currentminute && !$originalyear) {
// This is not a recursive call so time has not moved on at all yet. We can't use the
// same minute as now because it has already happened, it has to be at least one minute
// later, so update time and retry.
$newtime = $now + 60;
return $this->get_next_scheduled_time_inner($newtime, $validminutes, $validhours, $validdays,
$validdaysofweek, $validmonths, $firstyear);
}
if ($nextvalidminute < $currentminute) {
// The time is in the next hour so we need to recurse. Don't use strtotime at this
// point because it will mess up around DST changes.
$minutesforward = $nextvalidminute + 60 - $currentminute;
$newtime = $now + $minutesforward * 60;
return $this->get_next_scheduled_time_inner($newtime, $validminutes, $validhours, $validdays,
$validdaysofweek, $validmonths, $firstyear);
}
// The next valid minute is in the same hour so it must be valid according to all other
// checks and we can finally return it.
return $now + ($nextvalidminute - $currentminute) * 60;
}
/**
* Informs whether this task can be run.
*
* @return bool true when this task can be run. false otherwise.
*/
public function can_run(): bool {
return $this->is_component_enabled() || $this->get_run_if_component_disabled();
}
/**
* Checks whether the component and the task disabled flag enables to run this task.
* This do not checks whether the task manager allows running them or if the
* site allows tasks to "run now".
*
* @return bool true if task is enabled. false otherwise.
*/
public function is_enabled(): bool {
return $this->can_run() && !$this->get_disabled();
}
/**
* Produces a valid id string to use as id attribute based on the given FQCN class name.
*
* @param string $classname FQCN of a task.
* @return string valid string to be used as id attribute.
*/
public static function get_html_id(string $classname): string {
return str_replace('\\', '-', ltrim($classname, '\\'));
}
}
+72
View File
@@ -0,0 +1,72 @@
<?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/>.
/**
* A scheduled task for global search.
*
* @package core
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Runs global search indexing.
*
* @package core
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_index_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskglobalsearchindex', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
if (!\core_search\manager::is_indexing_enabled()) {
return;
}
$globalsearch = \core_search\manager::instance();
// Get total indexing time limit.
$timelimit = get_config('core', 'searchindextime');
$start = time();
// Do normal indexing.
$globalsearch->index(false, $timelimit, new \text_progress_trace());
// Do requested indexing (if any) for the rest of the time.
if ($timelimit != 0) {
$now = time();
$timelimit -= ($now - $start);
if ($timelimit <= 1) {
// There is hardly any time left, so don't try to do requests.
return;
}
}
$globalsearch->process_index_requests($timelimit, new \text_progress_trace());
}
}
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* A scheduled task for global search.
*
* @package core
* @copyright 2016 Eric Merrill {@link https://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Runs search index optimization.
*
* @package core
* @copyright 2016 Eric Merrill {@link https://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_optimize_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskglobalsearchoptimize', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
if (!\core_search\manager::is_indexing_enabled()) {
return;
}
$globalsearch = \core_search\manager::instance();
// Optimize index at last.
$globalsearch->optimize_index();
}
}
@@ -0,0 +1,190 @@
<?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/>.
/**
* Scheduled task class.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to send notifications about failed login attempts.
*/
class send_failed_login_notifications_task extends scheduled_task {
/** The maximum time period to look back (30 days = 30 * 24 * 3600) */
const NOTIFY_MAXIMUM_TIME = 2592000;
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasksendfailedloginnotifications', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG, $DB;
if (empty($CFG->notifyloginfailures)) {
return;
}
$recip = get_users_from_config($CFG->notifyloginfailures, 'moodle/site:config');
// Do not look back more than 1 month to avoid crashes due to huge number of records.
$maximumlastnotifytime = time() - self::NOTIFY_MAXIMUM_TIME;
if (empty($CFG->lastnotifyfailure) || ($CFG->lastnotifyfailure < $maximumlastnotifytime)) {
$CFG->lastnotifyfailure = $maximumlastnotifytime;
}
// If it has been less than an hour, or if there are no recipients, don't execute.
if (((time() - HOURSECS) < $CFG->lastnotifyfailure) || !is_array($recip) || count($recip) <= 0) {
return;
}
// We need to deal with the threshold stuff first.
if (empty($CFG->notifyloginthreshold)) {
$CFG->notifyloginthreshold = 10; // Default to something sensible.
}
// Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure
// and insert them into the cache_flags temp table.
$logmang = get_log_manager();
/** @var \core\log\sql_internal_table_reader[] $readers */
$readers = $logmang->get_readers('\core\log\sql_internal_table_reader');
$reader = reset($readers);
$readername = key($readers);
if (empty($reader) || empty($readername)) {
// No readers, no processing.
return true;
}
$logtable = $reader->get_internal_log_table_name();
$sql = "SELECT ip, COUNT(*)
FROM {" . $logtable . "}
WHERE eventname = ?
AND timecreated > ?
GROUP BY ip
HAVING COUNT(*) >= ?";
$params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $iprec) {
if (!empty($iprec->ip)) {
set_cache_flag('login_failure_by_ip', $iprec->ip, '1', 0);
}
}
$rs->close();
// Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure
// and insert them into the cache_flags temp table.
$sql = "SELECT userid, count(*)
FROM {" . $logtable . "}
WHERE eventname = ?
AND timecreated > ?
GROUP BY userid
HAVING count(*) >= ?";
$params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold);
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $inforec) {
if (!empty($inforec->info)) {
set_cache_flag('login_failure_by_id', $inforec->userid, '1', 0);
}
}
$rs->close();
// Now, select all the login error logged records belonging to the ips and infos
// since lastnotifyfailure, that we have stored in the cache_flags table.
$userfieldsapi = \core_user\fields::for_name();
$namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$sql = "SELECT * FROM (
SELECT l.*, u.username, $namefields
FROM {" . $logtable . "} l
JOIN {cache_flags} cf ON l.ip = cf.name
LEFT JOIN {user} u ON l.userid = u.id
WHERE l.eventname = ?
AND l.timecreated > ?
AND cf.flagtype = 'login_failure_by_ip'
UNION ALL
SELECT l.*, u.username, $namefields
FROM {" . $logtable . "} l
JOIN {cache_flags} cf ON l.userid = " . $DB->sql_cast_char2int('cf.name') . "
LEFT JOIN {user} u ON l.userid = u.id
WHERE l.eventname = ?
AND l.timecreated > ?
AND cf.flagtype = 'login_failure_by_info') t
ORDER BY t.timecreated DESC";
$params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, '\core\event\user_login_failed', $CFG->lastnotifyfailure);
// Init some variables.
$count = 0;
$messages = '';
// Iterate over the logs recordset.
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $log) {
$a = new \stdClass();
$a->time = userdate($log->timecreated);
if (empty($log->username)) {
// Entries with no valid username. We get attempted username from the event's other field.
$other = \tool_log\local\privacy\helper::decode_other($log->other);
$a->info = empty($other['username']) ? '' : $other['username'];
$a->name = get_string('unknownuser');
} else {
$a->info = $log->username;
$a->name = fullname($log);
}
$a->ip = $log->ip;
$messages .= get_string('notifyloginfailuresmessage', '', $a)."\n";
$count++;
}
$rs->close();
// If we have something useful to report.
if ($count > 0) {
$site = get_site();
$subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
// Calculate the complete body of notification (start + messages + end).
$params = array('id' => 0, 'modid' => 'site_errors', 'chooselog' => '1', 'logreader' => $readername);
$url = new \moodle_url('/report/log/index.php', $params);
$body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) .
(($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" .
$messages .
"\n\n".get_string('notifyloginfailuresmessageend', '', $url->out(false).' ')."\n\n";
// For each destination, send mail.
mtrace('Emailing admins about '. $count .' failed login attempts');
foreach ($recip as $admin) {
// Emailing the admins directly rather than putting these through the messaging system.
email_to_user($admin, \core_user::get_noreply_user(), $subject, $body);
}
}
// Update lastnotifyfailure with current time.
set_config('lastnotifyfailure', time());
// Finally, delete all the temp records we have created in cache_flags.
$DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
}
}
@@ -0,0 +1,96 @@
<?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 core\task;
/**
* Adhoc task that send login notifications.
*
* @package core
* @copyright 2021 Moodle Pty Ltd.
* @author Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_login_notifications extends adhoc_task {
use \core\task\logging_trait;
/**
* Run the adhoc task and preform the backup.
*/
public function execute() {
global $CFG, $DB, $SITE, $USER, $PAGE;
$customdata = $this->get_custom_data();
// First check the mobile app special case, to detect if the user is not using a new device after login from a different IP.
if (!empty($customdata->ismoodleapp)) {
$where = 'userid = ? AND timecreated >= ?';
if (!$DB->count_records_select('user_devices', $where, [$USER->id, $customdata->logintime])) {
// Do nothing, seems to be the same person doing login from a new IP using a known device.
return;
}
}
$this->log_start("Sending login notification to {$USER->username}");
$sitename = format_string($SITE->fullname);
$siteurl = $CFG->wwwroot;
$userfullname = fullname($USER);
$username = $USER->username;
$useremail = ($USER->username != $USER->email) ? $USER->email : '';
$logindevice = $customdata->ismoodleapp ? get_string('mobileapp', 'tool_mobile') : '';
$logindevice .= ' ' . $customdata->useragent;
$loginip = $customdata->loginip;
$logintime = userdate($customdata->logintime);
$changepasswordlink = (new \moodle_url('/user/preferences.php', ['userid' => $USER->id]))->out(false);
// Find a better final URL for changing password.
$userauth = get_auth_plugin($USER->auth);
if ($userauth->can_change_password()) {
if ($changepwurl = $userauth->change_password_url()) {
$changepasswordlink = $changepwurl;
} else {
$changepasswordlink = (new \moodle_url('/login/change_password.php'))->out(false);
}
}
$eventdata = new \core\message\message();
$eventdata->courseid = SITEID;
$eventdata->component = 'moodle';
$eventdata->name = 'newlogin';
$eventdata->userfrom = \core_user::get_noreply_user();
$eventdata->userto = $USER;
$eventdata->notification = 1;
$eventdata->subject = get_string('newloginnotificationtitle', 'moodle', $sitename);
$eventdata->fullmessageformat = FORMAT_HTML;
$info = compact('sitename', 'siteurl', 'userfullname', 'username', 'useremail',
'logindevice', 'logintime', 'loginip', 'changepasswordlink');
$eventdata->fullmessagehtml = get_string('newloginnotificationbodyfull', 'moodle', $info);
$eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
$eventdata->smallmessage = get_string('newloginnotificationbodysmall', 'moodle', $username);
$userpicture = new \user_picture($USER);
$userpicture->size = 1; // Use f1 size.
$userpicture->includetoken = $USER->id; // Generate an out-of-session token for the user receiving the message.
$eventdata->customdata = ['notificationiconurl' => $userpicture->get_url($PAGE)->out(false)];
if (message_send($eventdata)) {
$this->log_finish("Notification successfully sent");
} else {
$this->log_finish("Failed to send notification");
}
}
}
@@ -0,0 +1,79 @@
<?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/>.
/**
* Scheduled task class.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to create accounts and send password emails for new users.
*/
class send_new_user_passwords_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasksendnewuserpasswords', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $DB;
// Generate new password emails for users - ppl expect these generated asap.
if ($DB->count_records('user_preferences', array('name' => 'create_password', 'value' => '1'))) {
mtrace('Creating passwords for new users...');
$userfieldsapi = \core_user\fields::for_name();
$usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$newusers = $DB->get_recordset_sql("SELECT u.id as id, u.email, u.auth, u.deleted,
u.suspended, u.emailstop, u.mnethostid, u.mailformat,
$usernamefields, u.username, u.lang,
p.id as prefid
FROM {user} u
JOIN {user_preferences} p ON u.id=p.userid
WHERE p.name='create_password' AND p.value='1' AND
u.email !='' AND u.suspended = 0 AND
u.auth != 'nologin' AND u.deleted = 0");
// Note: we can not send emails to suspended accounts.
foreach ($newusers as $newuser) {
// Use a low cost factor when generating bcrypt hash otherwise
// hashing would be slow when emailing lots of users. Hashes
// will be automatically updated to a higher cost factor the first
// time the user logs in.
if (setnew_password_and_mail($newuser, true)) {
unset_user_preference('create_password', $newuser);
set_user_preference('auth_forcepasswordchange', 1, $newuser);
} else {
trigger_error("Could not create and mail new user password!");
}
}
$newusers->close();
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<?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/>.
/**
* Scheduled task abstract class.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to cleanup user sessions from a scheduled task.
*/
class session_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasksessioncleanup', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $DB;
$timenow = time();
\core\session\manager::gc();
// Cleanup old session linked tokens.
// Deletes the session linked tokens that are over a day old.
$DB->delete_records_select('external_tokens', 'lastaccess < :onedayago AND tokentype = :tokentype',
array('onedayago' => $timenow - DAYSECS, 'tokentype' => EXTERNAL_TOKEN_EMBEDDED));
}
}
@@ -0,0 +1,149 @@
<?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 core\task;
/**
* Simple task to automatically set the course visibility to shown when the course start date matches the current day.
*
* @package core
* @copyright 2023 Sara Arjona <sara@moodle.com> based on code from 2016 Tim Gagen and Amanda Doughty
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class show_started_courses_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('showstartedcoursestask', 'course');
}
/**
* Update course visibility.
* IMPORTANT: It only processes courses with start/end dates within the past 24 hours and with start/end dates higher than
* the current one, to avoid updating the course visibility early.
*
* @return void
*/
public function execute() {
global $CFG, $DB;
// Use the configured timezone.
date_default_timezone_set($CFG->timezone);
$start = time();
// Get list of courses to update.
mtrace('\n Searching for courses to set visibility to ' . $this->get_trace_message() . ' ...');
$fielddate = $this->get_field_date();
// Only process courses with dates in the past 24 hours with start/end dates higher than the current, to avoid updating
// the course visibility early.
$select = "visible = :visibility AND
{$fielddate} BETWEEN :beginofday AND :endofday";
$params = [
// Get courses that have the opposite visibility to the one we want to set.
'visibility' => !$this->get_visibility(),
'beginofday' => strtotime('-1 day', $start),
'endofday' => $start,
];
$courses = $DB->get_recordset_select('course', $select, $params);
$this->update_courses_visibility($courses, $this->get_visibility());
$courses->close();
$end = time();
mtrace(($end - $start) / 60 . ' mins');
}
/**
* Make course visible or hidden if the start date has become due.
*
* @param \moodle_recordset $courses
* @param int $visibility The given courses will be set to this visibility
* @return void
*/
private function update_courses_visibility(\moodle_recordset $courses, int $visibility): void {
global $DB;
mtrace("\n There are courses to change visibility...");
foreach ($courses as $course) {
if (!$DB->set_field('course', 'visible', $visibility, ['id' => $course->id])) {
mtrace(" Error updating course visibility for {$course->id}: {$course->shortname}.");
} else {
mtrace(" {$course->id}: {$course->shortname} visibility is now '" . $this->get_trace_message() . "'");
$this->trigger_event($course);
}
}
}
/**
* Method to trigger a course event.
*
* @param \stdClass $course The course that has been updated.
*/
private function trigger_event(\stdClass $course): void {
$params = [
'objectid' => $course->id,
'context' => \context_course::instance($course->id),
'other' => [
'shortname' => $course->shortname,
'fullname' => $course->fullname,
'idnumber' => $course->idnumber,
],
];
$event = call_user_func([$this->get_event_classname(), 'create'], $params);
$event->add_record_snapshot('course', $course);
$event->trigger();
}
/**
* Get the database field where the date to check is stored (startdate for showing courses and enddate for hiding courses).
*
* @return string
*/
protected function get_field_date(): string {
return 'startdate';
}
/**
* The expected visibility of the courses after running this task (show = 1 and hidden = 0).
*
* @return int
*/
protected function get_visibility(): int {
return 1;
}
/**
* The text to display in the trace message about the action that has been applied to the course.
*
* @return string
*/
protected function get_trace_message(): string {
return 'Show';
}
/**
* The event classname to be triggered for the courses that need to be updated.
*
* @return string
*/
protected function get_event_classname(): string {
return '\core\event\course_started';
}
}
+62
View File
@@ -0,0 +1,62 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Simple task to run the stats cron.
*/
class stats_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('taskstatscron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
// Run stats as at the end because they are known to take very long time on large sites.
if (!empty($CFG->enablestats) and empty($CFG->disablestatsprocessing)) {
require_once($CFG->dirroot.'/lib/statslib.php');
// Process configured number of days as max (defaulting to 31).
$maxdays = empty($CFG->statsruntimedays) ? 31 : abs($CFG->statsruntimedays);
if (stats_cron_daily($maxdays)) {
if (stats_cron_weekly()) {
if (stats_cron_monthly()) {
stats_clean_old();
}
}
}
\core_php_time_limit::raise();
}
}
}
@@ -0,0 +1,71 @@
<?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/>.
/**
* Synchronise plans from template cohorts.
*
* @package core_competency
* @copyright 2015 Issam Taboubi <issam.taboubi@umontreal.ca>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
use core_competency\api;
use core_competency\template_cohort;
/**
* Synchronise plans from template cohorts.
*
*
* @package core_competency
* @copyright 2015 Issam Taboubi <issam.taboubi@umontreal.ca>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_plans_from_template_cohorts_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task.
*
* @return string
*/
public function get_name() {
return get_string('syncplanscohorts', 'core_competency');
}
/**
* Do the job.
*/
public function execute() {
if (!api::is_enabled()) {
return;
}
$missingplans = template_cohort::get_all_missing_plans(self::get_last_run_time());
foreach ($missingplans as $missingplan) {
foreach ($missingplan['userids'] as $userid) {
try {
api::create_plan_from_template($missingplan['template'], $userid);
} catch (\Exception $e) {
debugging(sprintf('Exception caught while creating plan for user %d from template %d. Message: %s',
$userid, $missingplan['template']->get_id(), $e->getMessage()), DEBUG_DEVELOPER);
}
}
}
}
}
+271
View File
@@ -0,0 +1,271 @@
<?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/>.
/**
* A scheduled task.
*
* @package core
* @copyright 2013 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use core_tag_collection, core_tag_tag, core_tag_area, stdClass;
/**
* Simple task to run the tag cron.
*/
class tag_cron_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasktagcron', 'admin');
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
public function execute() {
global $CFG;
if (!empty($CFG->usetags)) {
$this->compute_correlations();
$this->cleanup();
}
}
/**
* Calculates and stores the correlated tags of all tags.
*
* The correlations are stored in the 'tag_correlation' table.
*
* Two tags are correlated if they appear together a lot. Ex.: Users tagged with "computers"
* will probably also be tagged with "algorithms".
*
* The rationale for the 'tag_correlation' table is performance. It works as a cache
* for a potentially heavy load query done at the 'tag_instance' table. So, the
* 'tag_correlation' table stores redundant information derived from the 'tag_instance' table.
*
* @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified.
*/
public function compute_correlations($mincorrelation = 2) {
global $DB;
// This mighty one line query fetches a row from the database for every
// individual tag correlation. We then need to process the rows collecting
// the correlations for each tag id.
// The fields used by this query are as follows:
// tagid : This is the tag id, there should be at least $mincorrelation
// rows for each tag id.
// correlation : This is the tag id that correlates to the above tagid field.
// correlationid : This is the id of the row in the tag_correlation table that
// relates to the tagid field and will be NULL if there are no
// existing correlations.
$sql = 'SELECT pairs.tagid, pairs.correlation, pairs.ocurrences, co.id AS correlationid
FROM (
SELECT ta.tagid, tb.tagid AS correlation, COUNT(*) AS ocurrences
FROM {tag_instance} ta
JOIN {tag} tga ON ta.tagid = tga.id
JOIN {tag_instance} tb ON (ta.itemtype = tb.itemtype AND ta.component = tb.component
AND ta.itemid = tb.itemid AND ta.tagid <> tb.tagid)
JOIN {tag} tgb ON tb.tagid = tgb.id AND tgb.tagcollid = tga.tagcollid
GROUP BY ta.tagid, tb.tagid
HAVING COUNT(*) > :mincorrelation
) pairs
LEFT JOIN {tag_correlation} co ON co.tagid = pairs.tagid
ORDER BY pairs.tagid ASC, pairs.ocurrences DESC, pairs.correlation ASC';
$rs = $DB->get_recordset_sql($sql, array('mincorrelation' => $mincorrelation));
// Set up an empty tag correlation object.
$tagcorrelation = new stdClass;
$tagcorrelation->id = null;
$tagcorrelation->tagid = null;
$tagcorrelation->correlatedtags = array();
// We store each correlation id in this array so we can remove any correlations
// that no longer exist.
$correlations = array();
// Iterate each row of the result set and build them into tag correlations.
// We add all of a tag's correlations to $tagcorrelation->correlatedtags[]
// then save the $tagcorrelation object.
foreach ($rs as $row) {
if ($row->tagid != $tagcorrelation->tagid) {
// The tag id has changed so we have all of the correlations for this tag.
$tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
if ($tagcorrelationid) {
$correlations[] = $tagcorrelationid;
}
// Now we reset the tag correlation object so we can reuse it and set it
// up for the current record.
$tagcorrelation = new stdClass;
$tagcorrelation->id = $row->correlationid;
$tagcorrelation->tagid = $row->tagid;
$tagcorrelation->correlatedtags = array();
}
// Save the correlation on the tag correlation object.
$tagcorrelation->correlatedtags[] = $row->correlation;
}
// Update the current correlation after the last record.
$tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
if ($tagcorrelationid) {
$correlations[] = $tagcorrelationid;
}
// Close the recordset.
$rs->close();
// Remove any correlations that weren't just identified.
if (empty($correlations)) {
// There are no tag correlations.
$DB->delete_records('tag_correlation');
} else {
list($sql, $params) = $DB->get_in_or_equal($correlations,
SQL_PARAMS_NAMED, 'param0000', false);
$DB->delete_records_select('tag_correlation', 'id '.$sql, $params);
}
}
/**
* Clean up the tag tables, making sure all tagged object still exists.
*
* This method is called from cron.
*
* This should normally not be necessary, but in case related tags are not deleted
* when the tagged record is removed, this should be done once in a while, perhaps
* on an occasional cron run. On a site with lots of tags, this could become an
* expensive function to call.
*/
public function cleanup() {
global $DB;
// Get ids to delete from instances where the tag has been deleted. This should never happen apparently.
$sql = "SELECT ti.id
FROM {tag_instance} ti
LEFT JOIN {tag} t ON t.id = ti.tagid
WHERE t.id IS null";
$tagids = $DB->get_records_sql($sql);
$tagarray = array();
foreach ($tagids as $tagid) {
$tagarray[] = $tagid->id;
}
// Next get ids from instances that have an owner that has been deleted.
$sql = "SELECT ti.id
FROM {tag_instance} ti, {user} u
WHERE ti.itemid = u.id
AND ti.itemtype = 'user'
AND ti.component = 'core'
AND u.deleted = 1";
$tagids = $DB->get_records_sql($sql);
foreach ($tagids as $tagid) {
$tagarray[] = $tagid->id;
}
// Get the other itemtypes.
$sql = "SELECT DISTINCT component, itemtype
FROM {tag_instance}
WHERE itemtype <> 'user' or component <> 'core'";
$tagareas = $DB->get_recordset_sql($sql);
foreach ($tagareas as $tagarea) {
$sql = 'SELECT ti.id
FROM {tag_instance} ti
LEFT JOIN {' . $tagarea->itemtype . '} it ON it.id = ti.itemid
WHERE it.id IS null
AND ti.itemtype = ? AND ti.component = ?';
$tagids = $DB->get_records_sql($sql, array($tagarea->itemtype, $tagarea->component));
foreach ($tagids as $tagid) {
$tagarray[] = $tagid->id;
}
}
$tagareas->close();
// Get instances for each of the ids to be deleted.
if (count($tagarray) > 0) {
list($sqlin, $params) = $DB->get_in_or_equal($tagarray);
$sql = "SELECT ti.*, COALESCE(t.name, 'deleted') AS name, COALESCE(t.rawname, 'deleted') AS rawname
FROM {tag_instance} ti
LEFT JOIN {tag} t ON t.id = ti.tagid
WHERE ti.id $sqlin";
$instances = $DB->get_records_sql($sql, $params);
$this->bulk_delete_instances($instances);
}
core_tag_collection::cleanup_unused_tags();
}
/**
* This function processes a tag correlation and makes changes in the database as required.
*
* The tag correlation object needs have both a tagid property and a correlatedtags property that is an array.
*
* @param stdClass $tagcorrelation
* @return int/bool The id of the tag correlation that was just processed or false.
*/
public function process_computed_correlation(stdClass $tagcorrelation) {
global $DB;
// You must provide a tagid and correlatedtags must be set and be an array.
if (empty($tagcorrelation->tagid) || !isset($tagcorrelation->correlatedtags) ||
!is_array($tagcorrelation->correlatedtags)) {
return false;
}
$tagcorrelation->correlatedtags = join(',', $tagcorrelation->correlatedtags);
if (!empty($tagcorrelation->id)) {
// The tag correlation already exists so update it.
$DB->update_record('tag_correlation', $tagcorrelation);
} else {
// This is a new correlation to insert.
$tagcorrelation->id = $DB->insert_record('tag_correlation', $tagcorrelation);
}
return $tagcorrelation->id;
}
/**
* This function will delete numerous tag instances efficiently.
* This removes tag instances only. It doesn't check to see if it is the last use of a tag.
*
* @param array $instances An array of tag instance objects with the addition of the tagname and tagrawname
* (used for recording a delete event).
*/
public function bulk_delete_instances($instances) {
global $DB;
$instanceids = array();
foreach ($instances as $instance) {
$instanceids[] = $instance->id;
}
// This is a multi db compatible method of creating the correct sql when using the 'IN' value.
// $insql is the sql statement, $params are the id numbers.
list($insql, $params) = $DB->get_in_or_equal($instanceids);
$sql = 'id ' . $insql;
$DB->delete_records_select('tag_instance', $sql, $params);
// Now go through and record each tag individually with the event system.
foreach ($instances as $instance) {
// Trigger tag removed event (i.e. The tag instance has been removed).
\core\event\tag_removed::create_from_tag_instance($instance, $instance->name,
$instance->rawname, true)->trigger();
}
}
}
+314
View File
@@ -0,0 +1,314 @@
<?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/>.
/**
* Abstract class for common properties of scheduled_task and adhoc_task.
*
* @package core
* @category task
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use core_component;
use core_plugin_manager;
use core\check\result;
/**
* Abstract class for common properties of scheduled_task and adhoc_task.
*
* @copyright 2013 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class task_base {
/** @var \core\lock\lock $lock - The lock controlling this task. */
private $lock = null;
/** @var \core\lock\lock $cronlock - The lock controlling the entire cron process. */
private $cronlock = null;
/** @var string $component - The component this task belongs to. */
private $component = '';
/** @var int $faildelay - Exponentially increasing fail delay */
private $faildelay = 0;
/** @var int $nextruntime - When this task is due to run next */
private $nextruntime = 0;
/** @var int $timestarted - When this task was started */
private $timestarted = null;
/** @var string $hostname - Hostname where this task was started and PHP process ID */
private $hostname = null;
/** @var int $pid - PHP process ID that is running the task */
private $pid = null;
/**
* Get a descriptive name for the task (shown to admins)
*
* @return string
*/
abstract public function get_name();
/**
* Set the current lock for this task.
* @param \core\lock\lock $lock
*/
public function set_lock(\core\lock\lock $lock) {
$this->lock = $lock;
}
/**
* Set the current lock for the entire cron process.
* @param \core\lock\lock $lock
*/
public function set_cron_lock(\core\lock\lock $lock) {
$this->cronlock = $lock;
}
/**
* Get the current lock for this task.
* @return \core\lock\lock
*/
public function get_lock() {
return $this->lock;
}
/**
* Get the next run time for this task.
* @return int timestamp
*/
public function get_next_run_time() {
return $this->nextruntime;
}
/**
* Set the next run time for this task.
* @param int $nextruntime
*/
public function set_next_run_time($nextruntime) {
$this->nextruntime = $nextruntime;
}
/**
* Get the current lock for the entire cron.
* @return \core\lock\lock
*/
public function get_cron_lock() {
return $this->cronlock;
}
/**
* Setter for $blocking.
*
* Please note that task blocking is no longer supported.
* If you are using it in older versions of Moodle you are strongly advised to rewrite your code
* as has a detrimental impact upon performance.
*
* @deprecated since Moodle 4.4 See MDL-67667
* @todo Remove in MDL-81509
*/
#[\core\attribute\deprecated(
replacement: null,
since: '4.4',
reason: 'Blocking tasks are no longer supported',
)]
public function set_blocking($blocking) {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
}
/**
* Getter for $blocking.
*
* @return bool
* @deprecated since Moodle 4.4 See MDL-67667
* @todo Remove in MDL-81509
*/
#[\core\attribute\deprecated(
replacement: null,
since: '4.4',
reason: 'Blocking tasks are no longer supported',
)]
public function is_blocking() {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
return false;
}
/**
* Setter for $component.
* @param string $component
*/
public function set_component($component) {
$this->component = $component;
}
/**
* Getter for $component.
* @return string
*/
public function get_component() {
if (empty($this->component)) {
// The component should be the root of the class namespace.
$classname = get_class($this);
$parts = explode('\\', $classname);
if (count($parts) === 1) {
$component = substr($classname, 0, strpos($classname, '_task'));
} else {
[$component] = $parts;
}
// Load component information from plugin manager.
if ($component !== 'core' && strpos($component, 'core_') !== 0) {
$plugininfo = \core_plugin_manager::instance()->get_plugin_info($component);
if ($plugininfo && $plugininfo->component) {
$this->set_component($plugininfo->component);
} else {
debugging("Component not set and the class namespace does not match a valid component ({$component}).");
}
}
}
return $this->component;
}
/**
* Setter for $faildelay.
* @param int $faildelay
*/
public function set_fail_delay($faildelay) {
$this->faildelay = $faildelay;
}
/**
* Getter for $faildelay.
* @return int
*/
public function get_fail_delay() {
return $this->faildelay;
}
/**
* Do the job.
* Throw exceptions on errors (the job will be retried).
*/
abstract public function execute();
/**
* Setter for $timestarted.
* @param int $timestarted
*/
public function set_timestarted($timestarted = null) {
$this->timestarted = $timestarted;
}
/**
* Getter for $timestarted.
* @return int
*/
public function get_timestarted() {
return $this->timestarted;
}
/**
* Setter for $hostname.
* @param string $hostname
*/
public function set_hostname($hostname = null) {
$this->hostname = $hostname;
}
/**
* Getter for $hostname.
* @return string
*/
public function get_hostname() {
return $this->hostname;
}
/**
* Setter for $pid.
* @param int $pid
*/
public function set_pid($pid = null) {
$this->pid = $pid;
}
/**
* Getter for $pid.
* @return int
*/
public function get_pid() {
return $this->pid;
}
/**
* Informs whether the task's component is enabled.
* @return bool true when enabled. false otherwise.
*/
public function is_component_enabled(): bool {
$component = $this->get_component();
// An entire core component type cannot be explicitly disabled.
[$componenttype] = core_component::normalize_component($component);
if ($componenttype === 'core') {
return true;
} else {
$plugininfo = core_plugin_manager::instance()->get_plugin_info($component);
return $plugininfo && ($plugininfo->is_enabled() !== false);
}
}
/**
* Returns task runtime
* @return int
*/
public function get_runtime() {
return time() - $this->timestarted;
}
/**
* Returns if the task has been running for too long
* @return result
*/
public function get_runtime_result() {
global $CFG;
$runtime = $this->get_runtime();
$runtimeerror = $CFG->taskruntimeerror;
$runtimewarn = $CFG->taskruntimewarn;
$status = result::OK;
$details = '';
if ($runtime > $runtimewarn) {
$status = result::WARNING;
$details = get_string('slowtask', 'tool_task', format_time($runtimewarn));
}
if ($runtime > $runtimeerror) {
$status = result::ERROR;
$details = get_string('slowtask', 'tool_task', format_time($runtimeerror));
}
// This result is aggregated with other running tasks checks before display.
return new result($status, '', $details);
}
}
@@ -0,0 +1,53 @@
<?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/>.
/**
* Cleanup adhoc task metadata.
*
* @package core
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @author Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
/**
* Adhoc task metadata cleanup task.
*
* @package core
* @copyright 2022 Catalyst IT Australia Pty Ltd
* @author Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class task_lock_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task.
*
* @return string
*/
public function get_name() {
return get_string('tasklockcleanuptask', 'admin');
}
/**
* Processes task.
*/
public function execute() {
\core\task\manager::cleanup_metadata();
}
}
@@ -0,0 +1,60 @@
<?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/>.
/**
* Task to cleanup task logs.
*
* @package core
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
use core\task\database_logger;
use core\task\logmanager;
/**
* A task to cleanup log entries for tasks.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class task_log_cleanup_task extends scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('tasklogcleanup', 'admin');
}
/**
* Perform the cleanup task.
*/
public function execute() {
$logger = logmanager::get_logger_classname();
if (is_a($logger, database_logger::class, true)) {
$logger::cleanup();
}
// Clean failed ad-hoc tasks.
manager::clean_failed_adhoc_tasks();
}
}
+71
View File
@@ -0,0 +1,71 @@
<?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/>.
/**
* Interface for task logging.
*
* @package core
* @category task
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for task logging.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface task_logger {
/**
* Whether the task is configured and ready to log.
*
* @return bool
*/
public static function is_configured(): bool;
/**
* Store the log for the specified task.
*
* @param task_base $task The task that the log belongs to.
* @param string $logpath The path to the log on disk
* @param bool $failed Whether the task failed
* @param int $dbreads The number of DB reads
* @param int $dbwrites The number of DB writes
* @param float $timestart The start time of the task
* @param float $timeend The end time of the task
*/
public static function store_log_for_task(task_base $task, string $logpath, bool $failed,
int $dbreads, int $dbwrites, float $timestart, float $timeend);
/**
* Whether this task logger has a report available.
*
* @return bool
*/
public static function has_log_report(): bool;
/**
* Get any URL available for viewing relevant task log reports.
*
* @param string $classname The task class to fetch for
* @return \moodle_url
*/
public static function get_url_for_task_class(string $classname): \moodle_url;
}
+59
View File
@@ -0,0 +1,59 @@
<?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 core\task;
/**
* This file defines a trait to assist with unit tests in tasks.
*
* @package core
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait task_trait {
/**
* Helper to execute a particular task.
*
* @param string $taskclass The task class.
*/
protected function execute_task(string $taskclass): void {
// Run the scheduled task.
$this->start_output_buffering();
$task = manager::get_scheduled_task($taskclass);
$task->execute();
$this->stop_output_buffering();
}
/**
* Helper to start output buffering.
*/
protected function start_output_buffering(): void {
ob_start();
}
/**
* Helper to stop output buffering.
*
* @return string|null The output buffer contents or null if output buffering is not active.
*/
protected function stop_output_buffering(): ?string {
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}