PageRenderTime 27ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/classes/task/scheduled_task.php

https://github.com/ankitagarwal/moodle
PHP | 466 lines | 222 code | 59 blank | 185 comment | 50 complexity | f5d82e57484774f0673499175b09b77f MD5 | raw file
  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. * Scheduled task abstract class.
  18. *
  19. * @package core
  20. * @category task
  21. * @copyright 2013 Damyon Wiese
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. namespace core\task;
  25. /**
  26. * Abstract class defining a scheduled task.
  27. * @copyright 2013 Damyon Wiese
  28. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29. */
  30. abstract class scheduled_task extends task_base {
  31. /** Minimum minute value. */
  32. const MINUTEMIN = 0;
  33. /** Maximum minute value. */
  34. const MINUTEMAX = 59;
  35. /** Minimum hour value. */
  36. const HOURMIN = 0;
  37. /** Maximum hour value. */
  38. const HOURMAX = 23;
  39. /** Minimum dayofweek value. */
  40. const DAYOFWEEKMIN = 0;
  41. /** Maximum dayofweek value. */
  42. const DAYOFWEEKMAX = 6;
  43. /** @var string $hour - Pattern to work out the valid hours */
  44. private $hour = '*';
  45. /** @var string $minute - Pattern to work out the valid minutes */
  46. private $minute = '*';
  47. /** @var string $day - Pattern to work out the valid days */
  48. private $day = '*';
  49. /** @var string $month - Pattern to work out the valid months */
  50. private $month = '*';
  51. /** @var string $dayofweek - Pattern to work out the valid dayofweek */
  52. private $dayofweek = '*';
  53. /** @var int $lastruntime - When this task was last run */
  54. private $lastruntime = 0;
  55. /** @var boolean $customised - Has this task been changed from it's default schedule? */
  56. private $customised = false;
  57. /** @var boolean $overridden - Does the task have values set VIA config? */
  58. private $overridden = false;
  59. /** @var int $disabled - Is this task disabled in cron? */
  60. private $disabled = false;
  61. /**
  62. * Get the last run time for this scheduled task.
  63. * @return int
  64. */
  65. public function get_last_run_time() {
  66. return $this->lastruntime;
  67. }
  68. /**
  69. * Set the last run time for this scheduled task.
  70. * @param int $lastruntime
  71. */
  72. public function set_last_run_time($lastruntime) {
  73. $this->lastruntime = $lastruntime;
  74. }
  75. /**
  76. * Has this task been changed from it's default config?
  77. * @return bool
  78. */
  79. public function is_customised() {
  80. return $this->customised;
  81. }
  82. /**
  83. * Has this task been changed from it's default config?
  84. * @param bool
  85. */
  86. public function set_customised($customised) {
  87. $this->customised = $customised;
  88. }
  89. /**
  90. * Has this task been changed from it's default config?
  91. * @return bool
  92. */
  93. public function is_overridden(): bool {
  94. return $this->overridden;
  95. }
  96. /**
  97. * Set the overridden value.
  98. * @param bool $overridden
  99. */
  100. public function set_overridden(bool $overridden): void {
  101. $this->overridden = $overridden;
  102. }
  103. /**
  104. * Setter for $minute. Accepts a special 'R' value
  105. * which will be translated to a random minute.
  106. * @param string $minute
  107. * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
  108. * If false, they are left as 'R'
  109. */
  110. public function set_minute($minute, $expandr = true) {
  111. if ($minute === 'R' && $expandr) {
  112. $minute = mt_rand(self::HOURMIN, self::HOURMAX);
  113. }
  114. $this->minute = $minute;
  115. }
  116. /**
  117. * Getter for $minute.
  118. * @return string
  119. */
  120. public function get_minute() {
  121. return $this->minute;
  122. }
  123. /**
  124. * Setter for $hour. Accepts a special 'R' value
  125. * which will be translated to a random hour.
  126. * @param string $hour
  127. * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
  128. * If false, they are left as 'R'
  129. */
  130. public function set_hour($hour, $expandr = true) {
  131. if ($hour === 'R' && $expandr) {
  132. $hour = mt_rand(self::HOURMIN, self::HOURMAX);
  133. }
  134. $this->hour = $hour;
  135. }
  136. /**
  137. * Getter for $hour.
  138. * @return string
  139. */
  140. public function get_hour() {
  141. return $this->hour;
  142. }
  143. /**
  144. * Setter for $month.
  145. * @param string $month
  146. */
  147. public function set_month($month) {
  148. $this->month = $month;
  149. }
  150. /**
  151. * Getter for $month.
  152. * @return string
  153. */
  154. public function get_month() {
  155. return $this->month;
  156. }
  157. /**
  158. * Setter for $day.
  159. * @param string $day
  160. */
  161. public function set_day($day) {
  162. $this->day = $day;
  163. }
  164. /**
  165. * Getter for $day.
  166. * @return string
  167. */
  168. public function get_day() {
  169. return $this->day;
  170. }
  171. /**
  172. * Setter for $dayofweek.
  173. * @param string $dayofweek
  174. * @param bool $expandr - if true (default) an 'R' value in a time is expanded to an appropriate int.
  175. * If false, they are left as 'R'
  176. */
  177. public function set_day_of_week($dayofweek, $expandr = true) {
  178. if ($dayofweek === 'R' && $expandr) {
  179. $dayofweek = mt_rand(self::DAYOFWEEKMIN, self::DAYOFWEEKMAX);
  180. }
  181. $this->dayofweek = $dayofweek;
  182. }
  183. /**
  184. * Getter for $dayofweek.
  185. * @return string
  186. */
  187. public function get_day_of_week() {
  188. return $this->dayofweek;
  189. }
  190. /**
  191. * Setter for $disabled.
  192. * @param bool $disabled
  193. */
  194. public function set_disabled($disabled) {
  195. $this->disabled = (bool)$disabled;
  196. }
  197. /**
  198. * Getter for $disabled.
  199. * @return bool
  200. */
  201. public function get_disabled() {
  202. return $this->disabled;
  203. }
  204. /**
  205. * Override this function if you want this scheduled task to run, even if the component is disabled.
  206. *
  207. * @return bool
  208. */
  209. public function get_run_if_component_disabled() {
  210. return false;
  211. }
  212. /**
  213. * Take a cron field definition and return an array of valid numbers with the range min-max.
  214. *
  215. * @param string $field - The field definition.
  216. * @param int $min - The minimum allowable value.
  217. * @param int $max - The maximum allowable value.
  218. * @return array(int)
  219. */
  220. public function eval_cron_field($field, $min, $max) {
  221. // Cleanse the input.
  222. $field = trim($field);
  223. // Format for a field is:
  224. // <fieldlist> := <range>(/<step>)(,<fieldlist>)
  225. // <step> := int
  226. // <range> := <any>|<int>|<min-max>
  227. // <any> := *
  228. // <min-max> := int-int
  229. // End of format BNF.
  230. // This function is complicated but is covered by unit tests.
  231. $range = array();
  232. $matches = array();
  233. preg_match_all('@[0-9]+|\*|,|/|-@', $field, $matches);
  234. $last = 0;
  235. $inrange = false;
  236. $instep = false;
  237. foreach ($matches[0] as $match) {
  238. if ($match == '*') {
  239. array_push($range, range($min, $max));
  240. } else if ($match == '/') {
  241. $instep = true;
  242. } else if ($match == '-') {
  243. $inrange = true;
  244. } else if (is_numeric($match)) {
  245. if ($instep) {
  246. $i = 0;
  247. for ($i = 0; $i < count($range[count($range) - 1]); $i++) {
  248. if (($i) % $match != 0) {
  249. $range[count($range) - 1][$i] = -1;
  250. }
  251. }
  252. $inrange = false;
  253. } else if ($inrange) {
  254. if (count($range)) {
  255. $range[count($range) - 1] = range($last, $match);
  256. }
  257. $inrange = false;
  258. } else {
  259. if ($match >= $min && $match <= $max) {
  260. array_push($range, $match);
  261. }
  262. $last = $match;
  263. }
  264. }
  265. }
  266. // Flatten the result.
  267. $result = array();
  268. foreach ($range as $r) {
  269. if (is_array($r)) {
  270. foreach ($r as $rr) {
  271. if ($rr >= $min && $rr <= $max) {
  272. $result[$rr] = 1;
  273. }
  274. }
  275. } else if (is_numeric($r)) {
  276. if ($r >= $min && $r <= $max) {
  277. $result[$r] = 1;
  278. }
  279. }
  280. }
  281. $result = array_keys($result);
  282. sort($result, SORT_NUMERIC);
  283. return $result;
  284. }
  285. /**
  286. * Assuming $list is an ordered list of items, this function returns the item
  287. * in the list that is greater than or equal to the current value (or 0). If
  288. * no value is greater than or equal, this will return the first valid item in the list.
  289. * If list is empty, this function will return 0.
  290. *
  291. * @param int $current The current value
  292. * @param int[] $list The list of valid items.
  293. * @return int $next.
  294. */
  295. private function next_in_list($current, $list) {
  296. foreach ($list as $l) {
  297. if ($l >= $current) {
  298. return $l;
  299. }
  300. }
  301. if (count($list)) {
  302. return $list[0];
  303. }
  304. return 0;
  305. }
  306. /**
  307. * Calculate when this task should next be run based on the schedule.
  308. * @return int $nextruntime.
  309. */
  310. public function get_next_scheduled_time() {
  311. global $CFG;
  312. $validminutes = $this->eval_cron_field($this->minute, self::MINUTEMIN, self::MINUTEMAX);
  313. $validhours = $this->eval_cron_field($this->hour, self::HOURMIN, self::HOURMAX);
  314. // We need to change to the server timezone before using php date() functions.
  315. \core_date::set_default_server_timezone();
  316. $daysinmonth = date("t");
  317. $validdays = $this->eval_cron_field($this->day, 1, $daysinmonth);
  318. $validdaysofweek = $this->eval_cron_field($this->dayofweek, 0, 7);
  319. $validmonths = $this->eval_cron_field($this->month, 1, 12);
  320. $nextvalidyear = date('Y');
  321. $currentminute = date("i") + 1;
  322. $currenthour = date("H");
  323. $currentday = date("j");
  324. $currentmonth = date("n");
  325. $currentdayofweek = date("w");
  326. $nextvalidminute = $this->next_in_list($currentminute, $validminutes);
  327. if ($nextvalidminute < $currentminute) {
  328. $currenthour += 1;
  329. }
  330. $nextvalidhour = $this->next_in_list($currenthour, $validhours);
  331. if ($nextvalidhour < $currenthour) {
  332. $currentdayofweek += 1;
  333. $currentday += 1;
  334. }
  335. $nextvaliddayofmonth = $this->next_in_list($currentday, $validdays);
  336. $nextvaliddayofweek = $this->next_in_list($currentdayofweek, $validdaysofweek);
  337. $daysincrementbymonth = $nextvaliddayofmonth - $currentday;
  338. if ($nextvaliddayofmonth < $currentday) {
  339. $daysincrementbymonth += $daysinmonth;
  340. }
  341. $daysincrementbyweek = $nextvaliddayofweek - $currentdayofweek;
  342. if ($nextvaliddayofweek < $currentdayofweek) {
  343. $daysincrementbyweek += 7;
  344. }
  345. // Special handling for dayofmonth vs dayofweek:
  346. // if either field is * - use the other field
  347. // otherwise - choose the soonest (see man 5 cron).
  348. if ($this->dayofweek == '*') {
  349. $daysincrement = $daysincrementbymonth;
  350. } else if ($this->day == '*') {
  351. $daysincrement = $daysincrementbyweek;
  352. } else {
  353. // Take the smaller increment of days by month or week.
  354. $daysincrement = $daysincrementbymonth;
  355. if ($daysincrementbyweek < $daysincrementbymonth) {
  356. $daysincrement = $daysincrementbyweek;
  357. }
  358. }
  359. $nextvaliddayofmonth = $currentday + $daysincrement;
  360. if ($nextvaliddayofmonth > $daysinmonth) {
  361. $currentmonth += 1;
  362. $nextvaliddayofmonth -= $daysinmonth;
  363. }
  364. $nextvalidmonth = $this->next_in_list($currentmonth, $validmonths);
  365. if ($nextvalidmonth < $currentmonth) {
  366. $nextvalidyear += 1;
  367. }
  368. // Work out the next valid time.
  369. $nexttime = mktime($nextvalidhour,
  370. $nextvalidminute,
  371. 0,
  372. $nextvalidmonth,
  373. $nextvaliddayofmonth,
  374. $nextvalidyear);
  375. return $nexttime;
  376. }
  377. /**
  378. * Informs whether this task can be run.
  379. * @return bool true when this task can be run. false otherwise.
  380. */
  381. public function can_run(): bool {
  382. return $this->is_component_enabled() || $this->get_run_if_component_disabled();
  383. }
  384. /**
  385. * Checks whether the component and the task disabled flag enables to run this task.
  386. * This do not checks whether the task manager allows running them or if the
  387. * site allows tasks to "run now".
  388. * @return bool true if task is enabled. false otherwise.
  389. */
  390. public function is_enabled(): bool {
  391. return $this->can_run() && !$this->get_disabled();
  392. }
  393. /**
  394. * Produces a valid id string to use as id attribute based on the given FQCN class name.
  395. * @param string $classname FQCN of a task.
  396. * @return string valid string to be used as id attribute.
  397. */
  398. public static function get_html_id(string $classname): string {
  399. return str_replace('\\', '-', ltrim($classname, '\\'));
  400. }
  401. /**
  402. * Get a descriptive name for this task (shown to admins).
  403. *
  404. * @return string
  405. */
  406. public abstract function get_name();
  407. }