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