1 <?php
2
3 namespace Alo;
4
5 if (!defined('GEN_START')) {
6 http_response_code(404);
7 die();
8 }
9
10 use Alo\Exception\CronException as CE;
11 use Log;
12
13 /**
14 * The crontab editor class. You must call the commit() method to save your changes.
15 *
16 * @author Art <a.molcanovas@gmail.com>
17 */
18 class Cron {
19
20 /**
21 * Defines the day of the week as Sunday
22 *
23 * @var int
24 */
25 const WEEKDAY_SUN = 0;
26
27 /**
28 * Defines the day of the week as Monday
29 *
30 * @var int
31 */
32 const WEEKDAY_MON = 1;
33
34 /**
35 * Defines the day of the week as Tuesday
36 *
37 * @var int
38 */
39 const WEEKDAY_TUE = 2;
40
41 /**
42 * Defines the day of the week as Wednesday
43 *
44 * @var int
45 */
46 const WEEKDAY_WED = 3;
47
48 /**
49 * Defines the day of the week as Thursday
50 *
51 * @var int
52 */
53 const WEEKDAY_THU = 4;
54
55 /**
56 * Defines the day of the week as Friday
57 *
58 * @var int
59 */
60 const WEEKDAY_FRI = 5;
61
62 /**
63 * Defines the day of the week as Saturday
64 *
65 * @var int
66 */
67 const WEEKDAY_SAT = 6;
68
69 /**
70 * A pre-setting to run the cronjob yearly at 1 Jan, 00:00
71 *
72 * @var string
73 */
74 const CONST_YEARLY = '0 0 1 1 *';
75
76 /**
77 * A pre-setting to run the cronjob monthly at 00:00
78 *
79 * @var string
80 */
81 const CONST_MONTHLY = '0 0 1 * *';
82
83 /**
84 * A pre-setting to run the cronjob weekly on Sunday, 00:00
85 *
86 * @var string
87 */
88 const CONST_WEEKLY = '0 0 * * 0';
89
90 /**
91 * A pre-setting to run the cronjob daily at 00:00
92 *
93 * @var string
94 */
95 const CONST_DAILY = '0 0 * * *';
96
97 /**
98 * A pre-setting to run the cronjob hourly at 00 minutes
99 *
100 * @var string
101 */
102 const CONST_HOURLY = '0 * * * *';
103
104 /**
105 * A pre-setting to run the cronjob on server startup
106 *
107 * @var string
108 */
109 const CONST_REBOOT = '@reboot';
110
111 /**
112 * Defines the month as January
113 *
114 * @var int
115 */
116 const MONTH_JAN = 1;
117
118 /**
119 * Defines the month as February
120 *
121 * @var int
122 */
123 const MONTH_FEB = 2;
124
125 /**
126 * Defines the month as March
127 *
128 * @var int
129 */
130 const MONTH_MAR = 3;
131
132 /**
133 * Defines the month as April
134 *
135 * @var int
136 */
137 const MONTH_APR = 4;
138
139 /**
140 * Defines the month as May
141 *
142 * @var int
143 */
144 const MONTH_MAY = 5;
145
146 /**
147 * Defines the month as June
148 *
149 * @var int
150 */
151 const MONTH_JUN = 6;
152
153 /**
154 * Defines the month as July
155 *
156 * @var int
157 */
158 const MONTH_JUL = 7;
159
160 /**
161 * Defines the month as August
162 *
163 * @var int
164 */
165 const MONTH_AUG = 8;
166
167 /**
168 * Defines the month as September
169 *
170 * @var int
171 */
172 const MONTH_SEP = 9;
173
174 /**
175 * Defines the month as October
176 *
177 * @var int
178 */
179 const MONTH_OCT = 10;
180
181 /**
182 * Defines the month as November
183 *
184 * @var int
185 */
186 const MONTH_NOV = 11;
187
188 /**
189 * Defines the month as December
190 *
191 * @var int
192 */
193 const MONTH_DEC = 12;
194
195 /**
196 * Array of valid CRON constants
197 *
198 * @var array
199 */
200 protected static $validConstants = [
201 '@yearly',
202 '@annually',
203 '@monthly',
204 '@weekly',
205 '@daily',
206 '@hourly',
207 '@reboot'
208 ];
209
210 /**
211 * The current crontab data
212 *
213 * @var array
214 */
215 protected $crontab;
216
217 /**
218 * Whether changes should be autocommited automatically
219 *
220 * @var bool
221 */
222 protected $autocommit;
223
224 /**
225 * Instantiates the crontab handler
226 *
227 * @author Art <a.molcanovas@gmail.com>
228 * @throws CE When the machine is running Windows
229 */
230 function __construct() {
231 if (\server_is_windows()) {
232 throw new CE('Windows does not support cron!', CE::E_WINDOWS);
233 } else {
234 $this->autocommit = false;
235 $this->reloadCrontab();
236 }
237 }
238
239 /**
240 * (re)loads the cron job array
241 *
242 * @author Art <a.molcanovas@gmail.com>
243 * @return Cron
244 */
245 function reloadCrontab() {
246 $this->crontab = shell_exec('crontab -l');
247
248 if ($this->crontab) {
249 $this->crontab = trim($this->crontab);
250 $this->crontab = explode("\n", $this->crontab);
251
252 //Make sure it's really empty
253 $last_index = count($this->crontab) - 1;
254
255 while ($last_index >= 0 && !$this->crontab[$last_index]) {
256 unset($this->crontab[$last_index]);
257 $last_index--;
258 }
259 } else {
260 $this->crontab = [];
261 }
262
263 Log::debug('(Re)loaded crontab contents');
264
265 return $this;
266 }
267
268 /**
269 * Edits the cron job at index $index. Can be substituded with the full
270 * CRON expression (schedule + command) to perform a search -
271 * use with caution!
272 *
273 * @author Art <a.molcanovas@gmail.com>
274 * @param int $index The job index in the crontab array
275 * @param string $command The command to run
276 * @param int|string $minute_const The minute parameter
277 * @param int|string $hour The hour parameter
278 * @param int|string $day The day of the month parameter
279 * @param int|string $month The month parameter
280 * @param int|string $weekday The day of the week parameter
281 * @return Cron
282 */
283 function editCronJob($index, $command, $minute_const = '*', $hour = '*', $day = '*', $month = '*', $weekday = '*') {
284 if (!is_numeric($index)) {
285 $search = array_search($index, $this->crontab);
286
287 if ($search !== false) {
288 $index = $search;
289 }
290 }
291
292 return $this->editCrontab($index, $command, $minute_const, $hour, $day, $month, $weekday);
293 }
294
295 /**
296 * Performs modifiation on the crontab file
297 *
298 * @author Art <a.molcanovas@gmail.com>
299 * @param int $index The job index in the crontab array
300 * @param string $command The command to run
301 * @param int|string $minute_const The minute parameter
302 * @param int|string $hour The hour parameter
303 * @param int|string $day The day of the month parameter
304 * @param int|string $month The month parameter
305 * @param int|string $weekday The day of the week parameter
306 * @return Cron
307 * @throws CE When the minute expression is invalid
308 * @throws CE When the parameters are invalid
309 * @throws CE When one or more parameters are non-scalar
310 */
311 protected function editCrontab($index, $command, $minute_const = '*', $hour = '*', $day = '*', $month = '*', $weekday = '*') {
312 $schedule = '';
313
314 if (!is_scalar($command) || !is_scalar($minute_const) || !is_scalar($hour) || !is_scalar($day) || !is_scalar($month) || !is_scalar($weekday)) {
315 throw new CE('All cron attributes must be scalar!', CE::E_ARGS_NONSCALAR);
316 } elseif (preg_match('/^(\@[a-z]+|[0-9 \*]{9})$/i', $minute_const)) {
317 $atSign = stripos($minute_const, '@');
318
319 if ($atSign === false || ($atSign === 0 && in_array($minute_const, self::$validConstants))) {
320 $schedule = $minute_const;
321 } else {
322 throw new CE('Invalid schedule minute expression: ' . $minute_const, CE::E_INVALID_MIN);
323 }
324 } elseif (!self::formatOK($minute_const, $hour, $day, $month, $weekday)) {
325 throw new CE('Invalid schedule parameters: ' . json_encode([
326 'minute/constant' => $minute_const,
327 'hour' => $hour,
328 'day' => $day,
329 'month' => $month,
330 'weekday' => $weekday,
331 'cmd' => $command,
332 'index' => $index
333 ]), CE::E_INVALID_EXPR);
334 } else {
335 $add = $minute_const . ' ' . $hour . ' ' . $day . ' ' . $month . ' ' . $weekday . ' ' . $command;
336
337 if ($index === null) {
338 $this->crontab[] = $add;
339 Log::debug('Appended crontab with ' . $add);
340 } else {
341 $this->crontab[$index] = $add;
342 Log::debug('Edited crontab index ' . $index . ' with ' . $add);
343 }
344
345 if ($this->autocommit) {
346 $this->commit();
347 }
348 }
349
350 return $this;
351 }
352
353 /**
354 * Checks whether all the fields are formatted properly
355 *
356 * @author Art <a.molcanovas@gmail.com>
357 * @param int|string $min The minute parameter
358 * @param int|string $hour The hour parameter
359 * @param int|string $day The day of the month parameter
360 * @param int|string $month The month parameter
361 * @param int|string $weekday The day of the week parameter
362 * @return boolean
363 */
364 protected static function formatOK($min, $hour, $day, $month, $weekday) {
365 $pat_min_h_mth = '/^(\*|[0-9]{1,2}|\*\/[0-9]{1,2}|[0-9,]+|[0-9\-]+)$/';
366 $pat_day = '/^(\*|[0-9]{1,2}|[0-9]{1,2}(L|W)|\*\/[0-9]{1,2}|[0-9,]+|[0-9\-]+)$/';
367 $pat_weekday = '/^(\*|[0-9]{1,2}|[0-9]{1,2}L|\*\/[0-9]{1,2}|[0-9,]+|[0-9\-]+)$/';
368
369 return preg_match($pat_min_h_mth, $min) && preg_match($pat_min_h_mth, $hour) && preg_match($pat_min_h_mth, $month) && preg_match($pat_day, $day) && preg_match($pat_weekday, $weekday);
370 }
371
372 /**
373 * Appends the crontab file
374 *
375 * @author Art <a.molcanovas@gmail.com>
376 * @param string $command The command to run
377 * @param int|string $minute_const The minute parameter
378 * @param int|string $hour The hour parameter
379 * @param int|string $day The day of the month parameter
380 * @param int|string $month The month parameter
381 * @param int|string $weekday The day of the week parameter
382 * @return Cron
383 */
384 function appendCrontab($command, $minute_const = '*', $hour = '*', $day = '*', $month = '*', $weekday = '*') {
385 return $this->editCrontab(null, $command, $minute_const, $hour, $day, $month, $weekday);
386 }
387
388 /**
389 * Saves any changes made
390 *
391 * @author Art <a.molcanovas@gmail.com>
392 * @return Cron
393 */
394 function commit() {
395 $commit = trim(implode("\n", $this->crontab)) . "\n";
396 file_put_contents("/tmp/crontab.txt", $commit);
397 echo shell_exec('crontab /tmp/crontab.txt');
398
399 Log::debug('Saved crontab changes');
400
401 return $this;
402 }
403
404 /**
405 * Removes the cron job @ index $index
406 *
407 * @author Art <a.molcanovas@gmail.com>
408 * @param int $index The cron job's index in the array
409 * @return Cron
410 */
411 function deleteJob($index) {
412 unset($this->crontab[$index]);
413 Log::debug('Deleted crontab entry @ index ' . $index);
414
415 if ($this->autocommit) {
416 $this->commit();
417 }
418
419 return $this;
420 }
421
422 /**
423 * Clears the crontab
424 *
425 * @author Art <a.molcanovas@gmail.com>
426 * @return Cron
427 */
428 function clearCrontab() {
429 $this->crontab = [];
430
431 if ($this->autocommit) {
432 $this->commit();
433 }
434
435 return $this;
436 }
437
438 /**
439 * If no parameter is passed or the parameter isn't TRUE/FALSE, returns the current autocommit setting, otherwise
440 * sets it. Use with caution!
441 *
442 * @author Art <a.molcanovas@gmail.com>
443 * @param bool|null $set The desired setting if changing
444 * @return Cron|bool $this if not changing the autocommit value or the value otherwise
445 */
446 function autocommit($set = null) {
447 if (is_bool($set)) {
448 $this->autocommit = $set;
449
450 return $this;
451 } else {
452 return $this->autocommit;
453 }
454 }
455
456 /**
457 * Returns crontab entry at index $i
458 *
459 * @author Art <a.molcanovas@gmail.com>
460 * @param int $i The index
461 * @return null|string
462 */
463 function getAtIndex($i) {
464 return \get($this->crontab[$i]);
465 }
466
467 /**
468 * Returns the crontab array
469 *
470 * @author Art <a.molcanovas@gmail.com>
471 * @return array
472 */
473 function getCrontab() {
474 return $this->crontab;
475 }
476
477 /**
478 * Returns a string representation of the object data
479 *
480 * @author Art <a.molcanovas@gmail.com>
481 * @return string
482 */
483 function __toString() {
484 return \lite_debug($this);
485 }
486
487 }
488