PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/task/scheduled_task.php

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