Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Scheduled task abstract class. 19 * 20 * @package core 21 * @category task 22 * @copyright 2013 Damyon Wiese 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core\task; 26 27 /** 28 * Abstract class defining a scheduled task. 29 * @copyright 2013 Damyon Wiese 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 abstract class scheduled_task extends task_base { 33 34 /** Minimum minute value. */ 35 const MINUTEMIN = 0; 36 /** Maximum minute value. */ 37 const MINUTEMAX = 59; 38 39 /** Minimum hour value. */ 40 const HOURMIN = 0; 41 /** Maximum hour value. */ 42 const HOURMAX = 23; 43 44 /** Minimum day of month value. */ 45 const DAYMIN = 1; 46 /** Maximum day of month value. */ 47 const DAYMAX = 31; 48 49 /** Minimum month value. */ 50 const MONTHMIN = 1; 51 /** Maximum month value. */ 52 const MONTHMAX = 12; 53 54 /** Minimum dayofweek value. */ 55 const DAYOFWEEKMIN = 0; 56 /** Maximum dayofweek value. */ 57 const DAYOFWEEKMAX = 6; 58 59 /** 60 * Minute field identifier. 61 */ 62 const FIELD_MINUTE = 'minute'; 63 /** 64 * Hour field identifier. 65 */ 66 const FIELD_HOUR = 'hour'; 67 /** 68 * Day-of-month field identifier. 69 */ 70 const FIELD_DAY = 'day'; 71 /** 72 * Month field identifier. 73 */ 74 const FIELD_MONTH = 'month'; 75 /** 76 * Day-of-week field identifier. 77 */ 78 const FIELD_DAYOFWEEK = 'dayofweek'; 79 80 /** @var string $hour - Pattern to work out the valid hours */ 81 private $hour = '*'; 82 83 /** @var string $minute - Pattern to work out the valid minutes */ 84 private $minute = '*'; 85 86 /** @var string $day - Pattern to work out the valid days */ 87 private $day = '*'; 88 89 /** @var string $month - Pattern to work out the valid months */ 90 private $month = '*'; 91 92 /** @var string $dayofweek - Pattern to work out the valid dayofweek */ 93 private $dayofweek = '*'; 94 95 /** @var int $lastruntime - When this task was last run */ 96 private $lastruntime = 0; 97 98 /** @var boolean $customised - Has this task been changed from it's default schedule? */ 99 private $customised = false; 100 101 /** @var boolean $overridden - Does the task have values set VIA config? */ 102 private $overridden = false; 103 104 /** @var int $disabled - Is this task disabled in cron? */ 105 private $disabled = false; 106 107 /** 108 * Get the last run time for this scheduled task. 109 * 110 * @return int 111 */ 112 public function get_last_run_time() { 113 return $this->lastruntime; 114 } 115 116 /** 117 * Set the last run time for this scheduled task. 118 * 119 * @param int $lastruntime 120 */ 121 public function set_last_run_time($lastruntime) { 122 $this->lastruntime = $lastruntime; 123 } 124 125 /** 126 * Has this task been changed from it's default config? 127 * 128 * @return bool 129 */ 130 public function is_customised() { 131 return $this->customised; 132 } 133 134 /** 135 * Has this task been changed from it's default config? 136 * 137 * @param bool 138 */ 139 public function set_customised($customised) { 140 $this->customised = $customised; 141 } 142 143 /** 144 * Has this task been changed from it's default config? 145 * 146 * @return bool 147 */ 148 public function is_overridden(): bool { 149 return $this->overridden; 150 } 151 152 /** 153 * Set the overridden value. 154 * 155 * @param bool $overridden 156 */ 157 public function set_overridden(bool $overridden): void { 158 $this->overridden = $overridden; 159 } 160 161 /** 162 * Setter for $minute. Accepts a special 'R' value 163 * which will be translated to a random minute. 164 * 165 * @param string $minute 166 * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int. 167 * If false, they are left as 'R' 168 */ 169 public function set_minute($minute, $expandr = true) { 170 if ($minute === 'R' && $expandr) { 171 $minute = mt_rand(self::MINUTEMIN, self::MINUTEMAX); 172 } 173 $this->minute = $minute; 174 } 175 176 /** 177 * Getter for $minute. 178 * 179 * @return string 180 */ 181 public function get_minute() { 182 return $this->minute; 183 } 184 185 /** 186 * Setter for $hour. Accepts a special 'R' value 187 * which will be translated to a random hour. 188 * 189 * @param string $hour 190 * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int. 191 * If false, they are left as 'R' 192 */ 193 public function set_hour($hour, $expandr = true) { 194 if ($hour === 'R' && $expandr) { 195 $hour = mt_rand(self::HOURMIN, self::HOURMAX); 196 } 197 $this->hour = $hour; 198 } 199 200 /** 201 * Getter for $hour. 202 * 203 * @return string 204 */ 205 public function get_hour() { 206 return $this->hour; 207 } 208 209 /** 210 * Setter for $month. 211 * 212 * @param string $month 213 */ 214 public function set_month($month) { 215 $this->month = $month; 216 } 217 218 /** 219 * Getter for $month. 220 * 221 * @return string 222 */ 223 public function get_month() { 224 return $this->month; 225 } 226 227 /** 228 * Setter for $day. 229 * 230 * @param string $day 231 */ 232 public function set_day($day) { 233 $this->day = $day; 234 } 235 236 /** 237 * Getter for $day. 238 * 239 * @return string 240 */ 241 public function get_day() { 242 return $this->day; 243 } 244 245 /** 246 * Setter for $dayofweek. 247 * 248 * @param string $dayofweek 249 * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int. 250 * If false, they are left as 'R' 251 */ 252 public function set_day_of_week($dayofweek, $expandr = true) { 253 if ($dayofweek === 'R' && $expandr) { 254 $dayofweek = mt_rand(self::DAYOFWEEKMIN, self::DAYOFWEEKMAX); 255 } 256 $this->dayofweek = $dayofweek; 257 } 258 259 /** 260 * Getter for $dayofweek. 261 * 262 * @return string 263 */ 264 public function get_day_of_week() { 265 return $this->dayofweek; 266 } 267 268 /** 269 * Setter for $disabled. 270 * 271 * @param bool $disabled 272 */ 273 public function set_disabled($disabled) { 274 $this->disabled = (bool)$disabled; 275 } 276 277 /** 278 * Getter for $disabled. 279 * @return bool 280 */ 281 public function get_disabled() { 282 return $this->disabled; 283 } 284 285 /** 286 * Override this function if you want this scheduled task to run, even if the component is disabled. 287 * 288 * @return bool 289 */ 290 public function get_run_if_component_disabled() { 291 return false; 292 } 293 294 /** 295 * Informs whether the given field is valid. 296 * Use the constants FIELD_* to identify the field. 297 * Have to be called after the method set_{field}(string). 298 * 299 * @param string $field field identifier; expected values from constants FIELD_*. 300 * 301 * @return bool true if given field is valid. false otherwise. 302 */ 303 public function is_valid(string $field): bool { 304 return !empty($this->get_valid($field)); 305 } 306 307 /** 308 * Calculates the list of valid values according to the given field and stored expression. 309 * 310 * @param string $field field identifier. Must be one of those FIELD_*. 311 * 312 * @return array(int) list of matching values. 313 * 314 * @throws \coding_exception when passed an invalid field identifier. 315 */ 316 private function get_valid(string $field): array { 317 switch($field) { 318 case self::FIELD_MINUTE: 319 $min = self::MINUTEMIN; 320 $max = self::MINUTEMAX; 321 break; 322 case self::FIELD_HOUR: 323 $min = self::HOURMIN; 324 $max = self::HOURMAX; 325 break; 326 case self::FIELD_DAY: 327 $min = self::DAYMIN; 328 $max = self::DAYMAX; 329 break; 330 case self::FIELD_MONTH: 331 $min = self::MONTHMIN; 332 $max = self::MONTHMAX; 333 break; 334 case self::FIELD_DAYOFWEEK: 335 $min = self::DAYOFWEEKMIN; 336 $max = self::DAYOFWEEKMAX; 337 break; 338 default: 339 throw new \coding_exception("Field '$field' is not a valid crontab identifier."); 340 } 341 342 return $this->eval_cron_field($this->{$field}, $min, $max); 343 } 344 345 /** 346 * Take a cron field definition and return an array of valid numbers with the range min-max. 347 * 348 * @param string $field - The field definition. 349 * @param int $min - The minimum allowable value. 350 * @param int $max - The maximum allowable value. 351 * @return array(int) 352 */ 353 public function eval_cron_field($field, $min, $max) { 354 // Cleanse the input. 355 $field = trim($field); 356 357 // Format for a field is: 358 // <fieldlist> := <range>(/<step>)(,<fieldlist>) 359 // <step> := int 360 // <range> := <any>|<int>|<min-max> 361 // <any> := * 362 // <min-max> := int-int 363 // End of format BNF. 364 365 // This function is complicated but is covered by unit tests. 366 $range = array(); 367 368 $matches = array(); 369 preg_match_all('@[0-9]+|\*|,|/|-@', $field, $matches); 370 371 $last = 0; 372 $inrange = false; 373 $instep = false; 374 foreach ($matches[0] as $match) { 375 if ($match == '*') { 376 array_push($range, range($min, $max)); 377 } else if ($match == '/') { 378 $instep = true; 379 } else if ($match == '-') { 380 $inrange = true; 381 } else if (is_numeric($match)) { 382 if ($min > $match || $match > $max) { 383 // This is a value error: The value lays out of the expected range of values. 384 return []; 385 } 386 if ($instep) { 387 for ($i = 0; $i < count($range[count($range) - 1]); $i++) { 388 if (($i) % $match != 0) { 389 $range[count($range) - 1][$i] = -1; 390 } 391 } 392 $instep = false; 393 } else if ($inrange) { 394 if (count($range)) { 395 $range[count($range) - 1] = range($last, $match); 396 } 397 $inrange = false; 398 } else { 399 array_push($range, $match); 400 $last = $match; 401 } 402 } 403 } 404 405 // If inrange or instep were not processed, there is a syntax error. 406 // Cleanup any existing values to show up the error. 407 if ($inrange || $instep) { 408 return []; 409 } 410 411 // Flatten the result. 412 $result = array(); 413 foreach ($range as $r) { 414 if (is_array($r)) { 415 foreach ($r as $rr) { 416 if ($rr >= $min && $rr <= $max) { 417 $result[$rr] = 1; 418 } 419 } 420 } else if (is_numeric($r)) { 421 if ($r >= $min && $r <= $max) { 422 $result[$r] = 1; 423 } 424 } 425 } 426 $result = array_keys($result); 427 sort($result, SORT_NUMERIC); 428 return $result; 429 } 430 431 /** 432 * Assuming $list is an ordered list of items, this function returns the item 433 * in the list that is greater than or equal to the current value (or 0). If 434 * no value is greater than or equal, this will return the first valid item in the list. 435 * If list is empty, this function will return 0. 436 * 437 * @param int $current The current value 438 * @param int[] $list The list of valid items. 439 * @return int $next. 440 */ 441 private function next_in_list($current, $list) { 442 foreach ($list as $l) { 443 if ($l >= $current) { 444 return $l; 445 } 446 } 447 if (count($list)) { 448 return $list[0]; 449 } 450 451 return 0; 452 } 453 454 /** 455 * Calculate when this task should next be run based on the schedule. 456 * 457 * @return int $nextruntime. 458 */ 459 public function get_next_scheduled_time() { 460 // We need to change to the server timezone before using php date() functions. 461 \core_date::set_default_server_timezone(); 462 463 $validminutes = $this->get_valid(self::FIELD_MINUTE); 464 $validhours = $this->get_valid(self::FIELD_HOUR); 465 $validdays = $this->get_valid(self::FIELD_DAY); 466 $validdaysofweek = $this->get_valid(self::FIELD_DAYOFWEEK); 467 $validmonths = $this->get_valid(self::FIELD_MONTH); 468 469 $nextvalidyear = date('Y'); 470 471 $currentminute = date("i") + 1; 472 $currenthour = date("H"); 473 $currentday = date("j"); 474 $currentmonth = date("n"); 475 $currentdayofweek = date("w"); 476 477 $nextvalidminute = $this->next_in_list($currentminute, $validminutes); 478 if ($nextvalidminute < $currentminute) { 479 $currenthour += 1; 480 } 481 $nextvalidhour = $this->next_in_list($currenthour, $validhours); 482 if ($nextvalidhour < $currenthour) { 483 $currentdayofweek += 1; 484 $currentday += 1; 485 } 486 $nextvaliddayofmonth = $this->next_in_list($currentday, $validdays); 487 $nextvaliddayofweek = $this->next_in_list($currentdayofweek, $validdaysofweek); 488 $daysincrementbymonth = $nextvaliddayofmonth - $currentday; 489 $daysinmonth = date('t'); 490 if ($nextvaliddayofmonth < $currentday) { 491 $daysincrementbymonth += $daysinmonth; 492 } 493 494 $daysincrementbyweek = $nextvaliddayofweek - $currentdayofweek; 495 if ($nextvaliddayofweek < $currentdayofweek) { 496 $daysincrementbyweek += 7; 497 } 498 499 // Special handling for dayofmonth vs dayofweek: 500 // if either field is * - use the other field 501 // otherwise - choose the soonest (see man 5 cron). 502 if ($this->dayofweek == '*') { 503 $daysincrement = $daysincrementbymonth; 504 } else if ($this->day == '*') { 505 $daysincrement = $daysincrementbyweek; 506 } else { 507 // Take the smaller increment of days by month or week. 508 $daysincrement = $daysincrementbymonth; 509 if ($daysincrementbyweek < $daysincrementbymonth) { 510 $daysincrement = $daysincrementbyweek; 511 } 512 } 513 514 $nextvaliddayofmonth = $currentday + $daysincrement; 515 if ($nextvaliddayofmonth > $daysinmonth) { 516 $currentmonth += 1; 517 $nextvaliddayofmonth -= $daysinmonth; 518 } 519 520 $nextvalidmonth = $this->next_in_list($currentmonth, $validmonths); 521 if ($nextvalidmonth < $currentmonth) { 522 $nextvalidyear += 1; 523 } 524 525 // Work out the next valid time. 526 $nexttime = mktime($nextvalidhour, 527 $nextvalidminute, 528 0, 529 $nextvalidmonth, 530 $nextvaliddayofmonth, 531 $nextvalidyear); 532 533 return $nexttime; 534 } 535 536 /** 537 * Informs whether this task can be run. 538 * 539 * @return bool true when this task can be run. false otherwise. 540 */ 541 public function can_run(): bool { 542 return $this->is_component_enabled() || $this->get_run_if_component_disabled(); 543 } 544 545 /** 546 * Checks whether the component and the task disabled flag enables to run this task. 547 * This do not checks whether the task manager allows running them or if the 548 * site allows tasks to "run now". 549 * 550 * @return bool true if task is enabled. false otherwise. 551 */ 552 public function is_enabled(): bool { 553 return $this->can_run() && !$this->get_disabled(); 554 } 555 556 /** 557 * Produces a valid id string to use as id attribute based on the given FQCN class name. 558 * 559 * @param string $classname FQCN of a task. 560 * @return string valid string to be used as id attribute. 561 */ 562 public static function get_html_id(string $classname): string { 563 return str_replace('\\', '-', ltrim($classname, '\\')); 564 } 565 566 /** 567 * Get a descriptive name for this task (shown to admins). 568 * 569 * @return string 570 */ 571 abstract public function get_name(); 572 573 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body