PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/task/scheduled_task.php

https://gitlab.com/unofficial-mirrors/moodle
PHP | 414 lines | 206 code | 53 blank | 155 comment | 45 complexity | 5e92f59300c686aaf77a5816c7c091b9 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 int $disabled - Is this task disabled in cron? */
  58. private $disabled = false;
  59. /**
  60. * Get the last run time for this scheduled task.
  61. * @return int
  62. */
  63. public function get_last_run_time() {
  64. return $this->lastruntime;
  65. }
  66. /**
  67. * Set the last run time for this scheduled task.
  68. * @param int $lastruntime
  69. */
  70. public function set_last_run_time($lastruntime) {
  71. $this->lastruntime = $lastruntime;
  72. }
  73. /**
  74. * Has this task been changed from it's default config?
  75. * @return bool
  76. */
  77. public function is_customised() {
  78. return $this->customised;
  79. }
  80. /**
  81. * Has this task been changed from it's default config?
  82. * @param bool
  83. */
  84. public function set_customised($customised) {
  85. $this->customised = $customised;
  86. }
  87. /**
  88. * Setter for $minute. Accepts a special 'R' value
  89. * which will be translated to a random minute.
  90. * @param string $minute
  91. */
  92. public function set_minute($minute) {
  93. if ($minute === 'R') {
  94. $minute = mt_rand(self::HOURMIN, self::HOURMAX);
  95. }
  96. $this->minute = $minute;
  97. }
  98. /**
  99. * Getter for $minute.
  100. * @return string
  101. */
  102. public function get_minute() {
  103. return $this->minute;
  104. }
  105. /**
  106. * Setter for $hour. Accepts a special 'R' value
  107. * which will be translated to a random hour.
  108. * @param string $hour
  109. */
  110. public function set_hour($hour) {
  111. if ($hour === 'R') {
  112. $hour = mt_rand(self::HOURMIN, self::HOURMAX);
  113. }
  114. $this->hour = $hour;
  115. }
  116. /**
  117. * Getter for $hour.
  118. * @return string
  119. */
  120. public function get_hour() {
  121. return $this->hour;
  122. }
  123. /**
  124. * Setter for $month.
  125. * @param string $month
  126. */
  127. public function set_month($month) {
  128. $this->month = $month;
  129. }
  130. /**
  131. * Getter for $month.
  132. * @return string
  133. */
  134. public function get_month() {
  135. return $this->month;
  136. }
  137. /**
  138. * Setter for $day.
  139. * @param string $day
  140. */
  141. public function set_day($day) {
  142. $this->day = $day;
  143. }
  144. /**
  145. * Getter for $day.
  146. * @return string
  147. */
  148. public function get_day() {
  149. return $this->day;
  150. }
  151. /**
  152. * Setter for $dayofweek.
  153. * @param string $dayofweek
  154. */
  155. public function set_day_of_week($dayofweek) {
  156. if ($dayofweek === 'R') {
  157. $dayofweek = mt_rand(self::DAYOFWEEKMIN, self::DAYOFWEEKMAX);
  158. }
  159. $this->dayofweek = $dayofweek;
  160. }
  161. /**
  162. * Getter for $dayofweek.
  163. * @return string
  164. */
  165. public function get_day_of_week() {
  166. return $this->dayofweek;
  167. }
  168. /**
  169. * Setter for $disabled.
  170. * @param bool $disabled
  171. */
  172. public function set_disabled($disabled) {
  173. $this->disabled = (bool)$disabled;
  174. }
  175. /**
  176. * Getter for $disabled.
  177. * @return bool
  178. */
  179. public function get_disabled() {
  180. return $this->disabled;
  181. }
  182. /**
  183. * Override this function if you want this scheduled task to run, even if the component is disabled.
  184. *
  185. * @return bool
  186. */
  187. public function get_run_if_component_disabled() {
  188. return false;
  189. }
  190. /**
  191. * Take a cron field definition and return an array of valid numbers with the range min-max.
  192. *
  193. * @param string $field - The field definition.
  194. * @param int $min - The minimum allowable value.
  195. * @param int $max - The maximum allowable value.
  196. * @return array(int)
  197. */
  198. public function eval_cron_field($field, $min, $max) {
  199. // Cleanse the input.
  200. $field = trim($field);
  201. // Format for a field is:
  202. // <fieldlist> := <range>(/<step>)(,<fieldlist>)
  203. // <step> := int
  204. // <range> := <any>|<int>|<min-max>
  205. // <any> := *
  206. // <min-max> := int-int
  207. // End of format BNF.
  208. // This function is complicated but is covered by unit tests.
  209. $range = array();
  210. $matches = array();
  211. preg_match_all('@[0-9]+|\*|,|/|-@', $field, $matches);
  212. $last = 0;
  213. $inrange = false;
  214. $instep = false;
  215. foreach ($matches[0] as $match) {
  216. if ($match == '*') {
  217. array_push($range, range($min, $max));
  218. } else if ($match == '/') {
  219. $instep = true;
  220. } else if ($match == '-') {
  221. $inrange = true;
  222. } else if (is_numeric($match)) {
  223. if ($instep) {
  224. $i = 0;
  225. for ($i = 0; $i < count($range[count($range) - 1]); $i++) {
  226. if (($i) % $match != 0) {
  227. $range[count($range) - 1][$i] = -1;
  228. }
  229. }
  230. $inrange = false;
  231. } else if ($inrange) {
  232. if (count($range)) {
  233. $range[count($range) - 1] = range($last, $match);
  234. }
  235. $inrange = false;
  236. } else {
  237. if ($match >= $min && $match <= $max) {
  238. array_push($range, $match);
  239. }
  240. $last = $match;
  241. }
  242. }
  243. }
  244. // Flatten the result.
  245. $result = array();
  246. foreach ($range as $r) {
  247. if (is_array($r)) {
  248. foreach ($r as $rr) {
  249. if ($rr >= $min && $rr <= $max) {
  250. $result[$rr] = 1;
  251. }
  252. }
  253. } else if (is_numeric($r)) {
  254. if ($r >= $min && $r <= $max) {
  255. $result[$r] = 1;
  256. }
  257. }
  258. }
  259. $result = array_keys($result);
  260. sort($result, SORT_NUMERIC);
  261. return $result;
  262. }
  263. /**
  264. * Assuming $list is an ordered list of items, this function returns the item
  265. * in the list that is greater than or equal to the current value (or 0). If
  266. * no value is greater than or equal, this will return the first valid item in the list.
  267. * If list is empty, this function will return 0.
  268. *
  269. * @param int $current The current value
  270. * @param int[] $list The list of valid items.
  271. * @return int $next.
  272. */
  273. private function next_in_list($current, $list) {
  274. foreach ($list as $l) {
  275. if ($l >= $current) {
  276. return $l;
  277. }
  278. }
  279. if (count($list)) {
  280. return $list[0];
  281. }
  282. return 0;
  283. }
  284. /**
  285. * Calculate when this task should next be run based on the schedule.
  286. * @return int $nextruntime.
  287. */
  288. public function get_next_scheduled_time() {
  289. global $CFG;
  290. $validminutes = $this->eval_cron_field($this->minute, self::MINUTEMIN, self::MINUTEMAX);
  291. $validhours = $this->eval_cron_field($this->hour, self::HOURMIN, self::HOURMAX);
  292. // We need to change to the server timezone before using php date() functions.
  293. \core_date::set_default_server_timezone();
  294. $daysinmonth = date("t");
  295. $validdays = $this->eval_cron_field($this->day, 1, $daysinmonth);
  296. $validdaysofweek = $this->eval_cron_field($this->dayofweek, 0, 7);
  297. $validmonths = $this->eval_cron_field($this->month, 1, 12);
  298. $nextvalidyear = date('Y');
  299. $currentminute = date("i") + 1;
  300. $currenthour = date("H");
  301. $currentday = date("j");
  302. $currentmonth = date("n");
  303. $currentdayofweek = date("w");
  304. $nextvalidminute = $this->next_in_list($currentminute, $validminutes);
  305. if ($nextvalidminute < $currentminute) {
  306. $currenthour += 1;
  307. }
  308. $nextvalidhour = $this->next_in_list($currenthour, $validhours);
  309. if ($nextvalidhour < $currenthour) {
  310. $currentdayofweek += 1;
  311. $currentday += 1;
  312. }
  313. $nextvaliddayofmonth = $this->next_in_list($currentday, $validdays);
  314. $nextvaliddayofweek = $this->next_in_list($currentdayofweek, $validdaysofweek);
  315. $daysincrementbymonth = $nextvaliddayofmonth - $currentday;
  316. if ($nextvaliddayofmonth < $currentday) {
  317. $daysincrementbymonth += $daysinmonth;
  318. }
  319. $daysincrementbyweek = $nextvaliddayofweek - $currentdayofweek;
  320. if ($nextvaliddayofweek < $currentdayofweek) {
  321. $daysincrementbyweek += 7;
  322. }
  323. // Special handling for dayofmonth vs dayofweek:
  324. // if either field is * - use the other field
  325. // otherwise - choose the soonest (see man 5 cron).
  326. if ($this->dayofweek == '*') {
  327. $daysincrement = $daysincrementbymonth;
  328. } else if ($this->day == '*') {
  329. $daysincrement = $daysincrementbyweek;
  330. } else {
  331. // Take the smaller increment of days by month or week.
  332. $daysincrement = $daysincrementbymonth;
  333. if ($daysincrementbyweek < $daysincrementbymonth) {
  334. $daysincrement = $daysincrementbyweek;
  335. }
  336. }
  337. $nextvaliddayofmonth = $currentday + $daysincrement;
  338. if ($nextvaliddayofmonth > $daysinmonth) {
  339. $currentmonth += 1;
  340. $nextvaliddayofmonth -= $daysinmonth;
  341. }
  342. $nextvalidmonth = $this->next_in_list($currentmonth, $validmonths);
  343. if ($nextvalidmonth < $currentmonth) {
  344. $nextvalidyear += 1;
  345. }
  346. // Work out the next valid time.
  347. $nexttime = mktime($nextvalidhour,
  348. $nextvalidminute,
  349. 0,
  350. $nextvalidmonth,
  351. $nextvaliddayofmonth,
  352. $nextvalidyear);
  353. return $nexttime;
  354. }
  355. /**
  356. * Get a descriptive name for this task (shown to admins).
  357. *
  358. * @return string
  359. */
  360. public abstract function get_name();
  361. }