mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-65843 tasks: Allow schedules to be overridden in config
This commit is contained in:
parent
6ef4e66f03
commit
3a232840a5
@ -82,7 +82,7 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
$defaulttask = \core\task\manager::get_default_scheduled_task($classname, false);
|
||||
|
||||
$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',
|
||||
['action' => 'edit', 'task' => $classname]);
|
||||
$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" .
|
||||
html_writer::span('\\' . $classname, 'task-class text-ltr'));
|
||||
$namecellcontent = $task->get_name() . "\n" .
|
||||
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;
|
||||
|
||||
$plugininfo = core_plugin_manager::instance()->get_plugin_info($task->get_component());
|
||||
|
@ -53,7 +53,7 @@ if ($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);
|
||||
} else if ($action == 'edit' && empty($CFG->preventscheduledtaskchanges)) {
|
||||
|
||||
|
@ -1084,6 +1084,46 @@ $CFG->admin = 'admin';
|
||||
// $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
|
||||
//=========================================================================
|
||||
|
||||
|
@ -67,7 +67,7 @@ class manager {
|
||||
|
||||
foreach ($tasks as $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).
|
||||
if ($scheduledtask) {
|
||||
$scheduledtask->set_component($componentname);
|
||||
@ -338,9 +338,10 @@ class manager {
|
||||
* @param \stdClass $record
|
||||
* @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'
|
||||
* @param bool $override - if true loads overridden settings from config.
|
||||
* @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);
|
||||
if (!class_exists($classname)) {
|
||||
debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER);
|
||||
@ -348,6 +349,12 @@ class manager {
|
||||
}
|
||||
/** @var \core\task\scheduled_task $task */
|
||||
$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)) {
|
||||
$task->set_last_run_time($record->lastruntime);
|
||||
}
|
||||
@ -391,6 +398,7 @@ class manager {
|
||||
if (isset($record->pid)) {
|
||||
$task->set_pid($record->pid);
|
||||
}
|
||||
$task->set_overridden(self::scheduled_task_has_override($classname));
|
||||
|
||||
return $task;
|
||||
}
|
||||
@ -701,10 +709,12 @@ class manager {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the task data is unchanged.
|
||||
if (!$DB->record_exists('task_scheduled', (array) $record)) {
|
||||
$lock->release();
|
||||
continue;
|
||||
if (!self::scheduled_task_has_override($record->classname)) {
|
||||
// Make sure the task data is unchanged unless an override is being used.
|
||||
if (!$DB->record_exists('task_scheduled', (array)$record)) {
|
||||
$lock->release();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The global cron lock is under the most contention so request it
|
||||
@ -1106,4 +1116,91 @@ class manager {
|
||||
|
||||
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? */
|
||||
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;
|
||||
|
||||
@ -102,6 +105,22 @@ abstract class scheduled_task extends task_base {
|
||||
$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
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user