mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 08:26:37 +02:00
MDL-65843 tasks: Allow schedules to be overridden in config
This commit is contained in:
parent
6ef4e66f03
commit
3a232840a5
6 changed files with 294 additions and 10 deletions
|
@ -82,7 +82,7 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||||
$defaulttask = \core\task\manager::get_default_scheduled_task($classname, false);
|
$defaulttask = \core\task\manager::get_default_scheduled_task($classname, false);
|
||||||
|
|
||||||
$customised = $task->is_customised() ? $no : $yes;
|
$customised = $task->is_customised() ? $no : $yes;
|
||||||
if (empty($CFG->preventscheduledtaskchanges)) {
|
if (empty($CFG->preventscheduledtaskchanges) && !$task->is_overridden()) {
|
||||||
$configureurl = new moodle_url('/admin/tool/task/scheduledtasks.php',
|
$configureurl = new moodle_url('/admin/tool/task/scheduledtasks.php',
|
||||||
['action' => 'edit', 'task' => $classname]);
|
['action' => 'edit', 'task' => $classname]);
|
||||||
$editlink = $this->output->action_icon($configureurl, new pix_icon('t/edit',
|
$editlink = $this->output->action_icon($configureurl, new pix_icon('t/edit',
|
||||||
|
@ -100,8 +100,13 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$namecell = new html_table_cell($task->get_name() . "\n" .
|
$namecellcontent = $task->get_name() . "\n" .
|
||||||
html_writer::span('\\' . $classname, 'task-class text-ltr'));
|
html_writer::span('\\' . $classname, 'task-class text-ltr');
|
||||||
|
if ($task->is_overridden()) {
|
||||||
|
// Let the user know the scheduled task is defined in config.
|
||||||
|
$namecellcontent .= "\n" . html_writer::div(get_string('configoverride', 'admin'), 'alert-info');
|
||||||
|
}
|
||||||
|
$namecell = new html_table_cell($namecellcontent);
|
||||||
$namecell->header = true;
|
$namecell->header = true;
|
||||||
|
|
||||||
$plugininfo = core_plugin_manager::instance()->get_plugin_info($task->get_component());
|
$plugininfo = core_plugin_manager::instance()->get_plugin_info($task->get_component());
|
||||||
|
|
|
@ -53,7 +53,7 @@ if ($task) {
|
||||||
|
|
||||||
$renderer = $PAGE->get_renderer('tool_task');
|
$renderer = $PAGE->get_renderer('tool_task');
|
||||||
|
|
||||||
if ($mform && ($mform->is_cancelled() || !empty($CFG->preventscheduledtaskchanges))) {
|
if ($mform && ($mform->is_cancelled() || !empty($CFG->preventscheduledtaskchanges) || $task->is_overridden())) {
|
||||||
redirect($nexturl);
|
redirect($nexturl);
|
||||||
} else if ($action == 'edit' && empty($CFG->preventscheduledtaskchanges)) {
|
} else if ($action == 'edit' && empty($CFG->preventscheduledtaskchanges)) {
|
||||||
|
|
||||||
|
|
|
@ -1084,6 +1084,46 @@ $CFG->admin = 'admin';
|
||||||
// $CFG->alternative_cache_factory_class = 'tool_alternativecache_cache_factory';
|
// $CFG->alternative_cache_factory_class = 'tool_alternativecache_cache_factory';
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
// 17. SCHEDULED TASK OVERRIDES
|
||||||
|
//=========================================================================
|
||||||
|
//
|
||||||
|
// It is now possible to define scheduled tasks directly within config.
|
||||||
|
// The overridden value will take precedence over the values that have been set VIA the UI from the
|
||||||
|
// next time the task is run.
|
||||||
|
//
|
||||||
|
// Tasks are configured as an array of tasks that can override a task's schedule, as well as setting
|
||||||
|
// the task as disabled. I.e:
|
||||||
|
//
|
||||||
|
// $CFG->scheduled_tasks = [
|
||||||
|
// '\local_plugin\task\my_task' => [
|
||||||
|
// 'schedule' => '*/15 0 0 0 0',
|
||||||
|
// 'disabled' => 0,
|
||||||
|
// ],
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
// The format for the schedule definition is: '{minute} {hour} {day} {dayofweek} {month}'.
|
||||||
|
//
|
||||||
|
// The classname of the task also supports wildcards:
|
||||||
|
//
|
||||||
|
// $CFG->scheduled_tasks = [
|
||||||
|
// '\local_plugin\*' => [
|
||||||
|
// 'schedule' => '*/15 0 0 0 0',
|
||||||
|
// 'disabled' => 0,
|
||||||
|
// ],
|
||||||
|
// '*' => [
|
||||||
|
// 'schedule' => '0 0 0 0 0',
|
||||||
|
// 'disabled' => 0,
|
||||||
|
// ],
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
// In this example, any task classnames matching '\local_plugin\*' would match the first rule and
|
||||||
|
// use that schedule the next time the task runs. Note that even though the 'local_plugin' tasks match
|
||||||
|
// the second rule as well, the highest rule takes precedence. Therefore, the second rule would be
|
||||||
|
// applied to all tasks, except for tasks within '\local_plugin\'.
|
||||||
|
//
|
||||||
|
// When the full classname is used, this rule always takes priority over any wildcard rules.
|
||||||
|
//
|
||||||
|
//=========================================================================
|
||||||
// ALL DONE! To continue installation, visit your main page with a browser
|
// ALL DONE! To continue installation, visit your main page with a browser
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class manager {
|
||||||
|
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
$record = (object) $task;
|
$record = (object) $task;
|
||||||
$scheduledtask = self::scheduled_task_from_record($record, $expandr);
|
$scheduledtask = self::scheduled_task_from_record($record, $expandr, false);
|
||||||
// Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
|
// Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
|
||||||
if ($scheduledtask) {
|
if ($scheduledtask) {
|
||||||
$scheduledtask->set_component($componentname);
|
$scheduledtask->set_component($componentname);
|
||||||
|
@ -338,9 +338,10 @@ class manager {
|
||||||
* @param \stdClass $record
|
* @param \stdClass $record
|
||||||
* @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
|
* @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'
|
* If false, they are left as 'R'
|
||||||
|
* @param bool $override - if true loads overridden settings from config.
|
||||||
* @return \core\task\scheduled_task|false
|
* @return \core\task\scheduled_task|false
|
||||||
*/
|
*/
|
||||||
public static function scheduled_task_from_record($record, $expandr = true) {
|
public static function scheduled_task_from_record($record, $expandr = true, $override = true) {
|
||||||
$classname = self::get_canonical_class_name($record->classname);
|
$classname = self::get_canonical_class_name($record->classname);
|
||||||
if (!class_exists($classname)) {
|
if (!class_exists($classname)) {
|
||||||
debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER);
|
debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER);
|
||||||
|
@ -348,6 +349,12 @@ class manager {
|
||||||
}
|
}
|
||||||
/** @var \core\task\scheduled_task $task */
|
/** @var \core\task\scheduled_task $task */
|
||||||
$task = new $classname;
|
$task = new $classname;
|
||||||
|
|
||||||
|
if ($override) {
|
||||||
|
// Update values with those defined in the config, if any are set.
|
||||||
|
$record = self::get_record_with_config_overrides($record);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($record->lastruntime)) {
|
if (isset($record->lastruntime)) {
|
||||||
$task->set_last_run_time($record->lastruntime);
|
$task->set_last_run_time($record->lastruntime);
|
||||||
}
|
}
|
||||||
|
@ -391,6 +398,7 @@ class manager {
|
||||||
if (isset($record->pid)) {
|
if (isset($record->pid)) {
|
||||||
$task->set_pid($record->pid);
|
$task->set_pid($record->pid);
|
||||||
}
|
}
|
||||||
|
$task->set_overridden(self::scheduled_task_has_override($classname));
|
||||||
|
|
||||||
return $task;
|
return $task;
|
||||||
}
|
}
|
||||||
|
@ -701,10 +709,12 @@ class manager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the task data is unchanged.
|
if (!self::scheduled_task_has_override($record->classname)) {
|
||||||
if (!$DB->record_exists('task_scheduled', (array) $record)) {
|
// Make sure the task data is unchanged unless an override is being used.
|
||||||
$lock->release();
|
if (!$DB->record_exists('task_scheduled', (array)$record)) {
|
||||||
continue;
|
$lock->release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The global cron lock is under the most contention so request it
|
// The global cron lock is under the most contention so request it
|
||||||
|
@ -1106,4 +1116,91 @@ class manager {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a given scheduled task record, this method will check to see if any overrides have
|
||||||
|
* been applied in config and return a copy of the record with any overridden values.
|
||||||
|
*
|
||||||
|
* The format of the config value is:
|
||||||
|
* $CFG->scheduled_tasks = array(
|
||||||
|
* '$classname' => array(
|
||||||
|
* 'schedule' => '* * * * *',
|
||||||
|
* 'disabled' => 1,
|
||||||
|
* ),
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* Where $classname is the value of the task's classname, i.e. '\core\task\grade_cron_task'.
|
||||||
|
*
|
||||||
|
* @param \stdClass $record scheduled task record
|
||||||
|
* @return \stdClass scheduled task with any configured overrides
|
||||||
|
*/
|
||||||
|
protected static function get_record_with_config_overrides(\stdClass $record): \stdClass {
|
||||||
|
global $CFG;
|
||||||
|
|
||||||
|
$scheduledtaskkey = self::scheduled_task_get_override_key($record->classname);
|
||||||
|
$overriddenrecord = $record;
|
||||||
|
|
||||||
|
if ($scheduledtaskkey) {
|
||||||
|
$overriddenrecord->customised = true;
|
||||||
|
$taskconfig = $CFG->scheduled_tasks[$scheduledtaskkey];
|
||||||
|
|
||||||
|
if (isset($taskconfig['disabled'])) {
|
||||||
|
$overriddenrecord->disabled = $taskconfig['disabled'];
|
||||||
|
}
|
||||||
|
if (isset($taskconfig['schedule'])) {
|
||||||
|
list (
|
||||||
|
$overriddenrecord->minute,
|
||||||
|
$overriddenrecord->hour,
|
||||||
|
$overriddenrecord->day,
|
||||||
|
$overriddenrecord->dayofweek,
|
||||||
|
$overriddenrecord->month) = explode(' ', $taskconfig['schedule']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $overriddenrecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This checks whether or not there is a value set in config
|
||||||
|
* for a scheduled task.
|
||||||
|
*
|
||||||
|
* @param string $classname Scheduled task's classname
|
||||||
|
* @return bool true if there is an entry in config
|
||||||
|
*/
|
||||||
|
public static function scheduled_task_has_override(string $classname): bool {
|
||||||
|
return self::scheduled_task_get_override_key($classname) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key within the scheduled tasks config object that
|
||||||
|
* for a classname.
|
||||||
|
*
|
||||||
|
* @param string $classname the scheduled task classname to find
|
||||||
|
* @return string the key if found, otherwise null
|
||||||
|
*/
|
||||||
|
public static function scheduled_task_get_override_key(string $classname): ?string {
|
||||||
|
global $CFG;
|
||||||
|
|
||||||
|
if (isset($CFG->scheduled_tasks)) {
|
||||||
|
// Firstly, attempt to get a match against the full classname.
|
||||||
|
if (isset($CFG->scheduled_tasks[$classname])) {
|
||||||
|
return $classname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if there is a wildcard matching the classname.
|
||||||
|
foreach (array_keys($CFG->scheduled_tasks) as $key) {
|
||||||
|
if (strpos($key, '*') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/' . str_replace('\\', '\\\\', str_replace('*', '.*', $key)) . '/';
|
||||||
|
|
||||||
|
if (preg_match($pattern, $classname)) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,9 @@ abstract class scheduled_task extends task_base {
|
||||||
/** @var boolean $customised - Has this task been changed from it's default schedule? */
|
/** @var boolean $customised - Has this task been changed from it's default schedule? */
|
||||||
private $customised = false;
|
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? */
|
/** @var int $disabled - Is this task disabled in cron? */
|
||||||
private $disabled = false;
|
private $disabled = false;
|
||||||
|
|
||||||
|
@ -102,6 +105,22 @@ abstract class scheduled_task extends task_base {
|
||||||
$this->customised = $customised;
|
$this->customised = $customised;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Setter for $minute. Accepts a special 'R' value
|
||||||
* which will be translated to a random minute.
|
* which will be translated to a random minute.
|
||||||
|
|
|
@ -517,6 +517,129 @@ class core_scheduled_task_testcase extends advanced_testcase {
|
||||||
$this->assertLessThan($before + 70, $task->get_next_run_time());
|
$this->assertLessThan($before + 70, $task->get_next_run_time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider for test_tool_health_category_find_missing_parents.
|
||||||
|
*/
|
||||||
|
public static function provider_schedule_overrides(): array {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'scheduled_tasks' => array(
|
||||||
|
'\core\task\scheduled_test_task' => array(
|
||||||
|
'schedule' => '10 13 1 2 4',
|
||||||
|
'disabled' => 0,
|
||||||
|
),
|
||||||
|
'\core\task\scheduled_test2_task' => array(
|
||||||
|
'schedule' => '* * * * *',
|
||||||
|
'disabled' => 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'task_full_classnames' => array(
|
||||||
|
'\core\task\scheduled_test_task',
|
||||||
|
'\core\task\scheduled_test2_task',
|
||||||
|
),
|
||||||
|
'expected' => array(
|
||||||
|
'\core\task\scheduled_test_task' => array(
|
||||||
|
'min' => '10',
|
||||||
|
'hour' => '13',
|
||||||
|
'day' => '1',
|
||||||
|
'week' => '2',
|
||||||
|
'month' => '4',
|
||||||
|
'disabled' => 0,
|
||||||
|
),
|
||||||
|
'\core\task\scheduled_test2_task' => array(
|
||||||
|
'min' => '*',
|
||||||
|
'hour' => '*',
|
||||||
|
'day' => '*',
|
||||||
|
'week' => '*',
|
||||||
|
'month' => '*',
|
||||||
|
'disabled' => 1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'scheduled_tasks' => array(
|
||||||
|
'\core\task\*' => array(
|
||||||
|
'schedule' => '1 2 3 4 5',
|
||||||
|
'disabled' => 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'task_full_classnames' => array(
|
||||||
|
'\core\task\scheduled_test_task',
|
||||||
|
'\core\task\scheduled_test2_task',
|
||||||
|
),
|
||||||
|
'expected' => array(
|
||||||
|
'\core\task\scheduled_test_task' => array(
|
||||||
|
'min' => '1',
|
||||||
|
'hour' => '2',
|
||||||
|
'day' => '3',
|
||||||
|
'week' => '4',
|
||||||
|
'month' => '5',
|
||||||
|
'disabled' => 0,
|
||||||
|
),
|
||||||
|
'\core\task\scheduled_test2_task' => array(
|
||||||
|
'min' => '1',
|
||||||
|
'hour' => '2',
|
||||||
|
'day' => '3',
|
||||||
|
'week' => '4',
|
||||||
|
'month' => '5',
|
||||||
|
'disabled' => 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to ensure scheduled tasks are updated by values set in config.
|
||||||
|
*
|
||||||
|
* @param array $overrides
|
||||||
|
* @param array $tasks
|
||||||
|
* @param array $expected
|
||||||
|
* @dataProvider provider_schedule_overrides
|
||||||
|
*/
|
||||||
|
public function test_scheduled_task_override_values(array $overrides, array $tasks, array $expected): void {
|
||||||
|
global $CFG, $DB;
|
||||||
|
|
||||||
|
$this->resetAfterTest();
|
||||||
|
|
||||||
|
// Add overrides to the config.
|
||||||
|
$CFG->scheduled_tasks = $overrides;
|
||||||
|
|
||||||
|
// Set up test scheduled task record.
|
||||||
|
$record = new stdClass();
|
||||||
|
$record->component = 'test_scheduled_task';
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
$record->classname = $task;
|
||||||
|
$DB->insert_record('task_scheduled', $record);
|
||||||
|
|
||||||
|
$scheduledtask = \core\task\manager::get_scheduled_task($task);
|
||||||
|
$expectedresults = $expected[$task];
|
||||||
|
|
||||||
|
// Check that the task is actually overridden.
|
||||||
|
$this->assertTrue($scheduledtask->is_overridden(), 'Is overridden');
|
||||||
|
|
||||||
|
// Check minute is correct.
|
||||||
|
$this->assertEquals($expectedresults['min'], $scheduledtask->get_minute(), 'Minute check');
|
||||||
|
|
||||||
|
// Check day is correct.
|
||||||
|
$this->assertEquals($expectedresults['day'], $scheduledtask->get_day(), 'Day check');
|
||||||
|
|
||||||
|
// Check hour is correct.
|
||||||
|
$this->assertEquals($expectedresults['hour'], $scheduledtask->get_hour(), 'Hour check');
|
||||||
|
|
||||||
|
// Check week is correct.
|
||||||
|
$this->assertEquals($expectedresults['week'], $scheduledtask->get_day_of_week(), 'Day of week check');
|
||||||
|
|
||||||
|
// Check week is correct.
|
||||||
|
$this->assertEquals($expectedresults['month'], $scheduledtask->get_month(), 'Month check');
|
||||||
|
|
||||||
|
// Check to see if the task is disabled.
|
||||||
|
$this->assertEquals($expectedresults['disabled'], $scheduledtask->get_disabled(), 'Disabled check');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the specified tasks are equal.
|
* Assert that the specified tasks are equal.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue