PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / system / cron.php
vikappointments / libraries / system Last commit date
assets.php 1 month ago body.php 4 years ago builder.php 1 month ago cron.php 4 years ago feedback.php 4 years ago gutenberg.php 2 years ago install.php 1 month ago mce.php 4 years ago rssfeeds.php 5 months ago screen.php 4 years ago
cron.php
596 lines
1 <?php
2 /**
3 * @package VikAppointments - Libraries
4 * @subpackage system
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Class used to handle the cron jobs execution.
16 *
17 * @since 1.0
18 */
19 class VikAppointmentsCron
20 {
21 /**
22 * Schedules the published cron jobs to be executed according
23 * to the selected recurrence.
24 *
25 * @return void
26 *
27 * @since 1.2.3
28 */
29 public static function setup()
30 {
31 static $setup = false;
32
33 if ($setup)
34 {
35 // setup is allowed only once
36 return;
37 }
38
39 // prevent double setup
40 $setup = true;
41
42 /**
43 * Filters the non-default cron schedules.
44 * Adds support to repeat the cron jobs every half hour, every 2 hours
45 * and every month.
46 *
47 * @param array $schedules An array of non-default cron schedules.
48 */
49 add_filter('cron_schedules', function($schedules)
50 {
51 // add support for "every 15 minutes" recurrence
52 $schedules['every_15_minutes'] = array(
53 'interval' => HOUR_IN_SECONDS / 4,
54 'display' => __('Every 15 Minutes', 'vikappointments'),
55 );
56
57 // add support for "every 30 minutes" recurrence
58 $schedules['half_hour'] = array(
59 'interval' => HOUR_IN_SECONDS / 2,
60 'display' => __('Every Half Hour', 'vikappointments'),
61 );
62
63 // add support for "every 2 hours" recurrence
64 $schedules['every_2_hours'] = array(
65 'interval' => HOUR_IN_SECONDS * 2,
66 'display' => __('Every 2 Hours', 'vikappointments'),
67 );
68
69 // add support for "every month" recurrence
70 $schedules['monthly'] = array(
71 'interval' => MONTH_IN_SECONDS,
72 'display' => __('Monthly', 'vikappointments'),
73 );
74
75 return $schedules;
76 });
77
78 /**
79 * Trigger event to allow the plugins to include custom HTML within the view.
80 * It is possible to return an associative array to group the HTML strings
81 * under different fieldsets. Plain/html string will be always pushed within
82 * the "custom" fieldset instead.
83 *
84 * Displays the field to select the cron job recurrence.
85 *
86 * @param mixed $forms The HTML to display.
87 * @param mixed $view The current view instance.
88 *
89 * @since 1.2.3
90 */
91 add_filter('vikappointments_display_view_cronjob', array('VikAppointmentsCron', 'addScheduleControlForm'), 10, 2);
92
93 /**
94 * Trigger event to allow the plugins to make something before saving
95 * a record in the database. Used to extend the cron jobs management
96 * in order to support the selection of the recurrence.
97 *
98 * @param bool $save False to abort the saving process.
99 * @param array $args The saved record.
100 *
101 * @since 1.2.3
102 */
103 add_filter('vikappointments_before_save_cronjob', array('VikAppointmentsCron', 'saveScheduleControl'), 10, 2);
104
105 /**
106 * Trigger event to allow the plugins to make something after saving
107 * a record in the database. Used to schedule the cron job execution.
108 *
109 * @param mixed $status The saving status.
110 * @param array $args The saved record.
111 * @param bool $is_new True in case of insert.
112 *
113 * @since 1.2.3
114 */
115 add_filter('vikappointments_after_save_cronjob', array('VikAppointmentsCron', 'checkSchedulingAfterSave'), 10, 3);
116
117 /**
118 * Trigger event to allow the plugins to make something after publishing or
119 * unpublishing one or more records. Used to unschedule the unpublished cron jobs.
120 *
121 * @param mixed $return The saving status.
122 * @param int $id The ID of the updated record.
123 * @param int $state The publishing state.
124 *
125 * @since 1.2.3
126 */
127 add_filter('vikappointments_after_publish_cronjob', array('VikAppointmentsCron', 'checkSchedulingAfterPublish'), 10, 3);
128
129 /**
130 * Trigger event to allow the plugins to make something after publishing or
131 * unpublishing one or more records. Used to unschedule the unpublished cron jobs.
132 *
133 * @param bool $delete False to abort the deleting process.
134 * @param array $ids An array of IDs to delete.
135 *
136 * @since 1.2.3
137 */
138 add_filter('vikappointments_before_delete_cronjob', array('VikAppointmentsCron', 'checkSchedulingBeforeDelete'), 10, 2);
139
140 // fetch list of cron jobs
141 $crons = static::getJobs();
142
143 if (!$crons)
144 {
145 // nothing to execute
146 return;
147 }
148
149 $model = JModelVAP::getInstance('cronjob');
150
151 // iterate cron jobs list
152 foreach ($crons as $id_cron)
153 {
154 // get cron job details
155 $cron = $model->getItem($id_cron);
156
157 if (!$cron)
158 {
159 // cron not found, go ahead
160 continue;
161 }
162
163 // fetch recurrence interval
164 $interval = !empty($cron->schedule_key) ? $cron->schedule_key : 'hourly';
165
166 // build cron listener hook
167 $hook = static::getScheduleHook($cron);
168
169 /**
170 * Action used to execute all the cron jobs that have been created
171 * through the VikAppointments panel. Only published cron jobs can
172 * be executed.
173 *
174 * The scheduling of this hook must be registered through
175 * WordPress in order to be executed.
176 */
177 add_action($hook, function() use ($id_cron)
178 {
179 VikAppointmentsCron::runJob($id_cron);
180 });
181
182 // Make sure the cron event hasn't been yet scheduled.
183 // After its execution, wp_next_scheduled will return false and
184 // we will be able to register it again.
185 if (!wp_next_scheduled($hook))
186 {
187 // schedule event starting from the current time for every minute, by
188 // launching the cron listener hook (3rd argument)
189 wp_schedule_event(time(), $interval, $hook);
190 }
191 }
192 }
193
194 /**
195 * Executes all the published jobs.
196 *
197 * The usage of this method is strongly discouraged.
198 *
199 * @return integer The number of executed jobs.
200 */
201 public static function run()
202 {
203 // fetch list of cron jobs
204 $crons = static::getJobs();
205
206 $count = 0;
207
208 if (!$crons)
209 {
210 // nothing to execute
211 return $count;
212 }
213
214 // iterate cron jobs list
215 foreach ($crons as $id_cron)
216 {
217 // dispatch cron job
218 if (static::runJob($id_cron))
219 {
220 $count++;
221 }
222 }
223
224 return $count;
225 }
226
227 /**
228 * Executes the specified cron job.
229 *
230 * @param integer $id_cron The cron job ID (changed from object @since 1.2).
231 *
232 * @return boolean True on success, false otherwise.
233 */
234 public static function runJob($id_cron)
235 {
236 $error = null;
237 $status = false;
238
239 // force the option in request to prevent weird behaviors
240 JFactory::getApplication()->input->set('option', 'com_vikappointments');
241
242 try
243 {
244 // get CRON JOB model
245 $model = JModelVAP::getInstance('cronjob');
246 // dispatch cron job
247 $status = $model->dispatch($id_cron);
248
249 if (!$status)
250 {
251 // get any registered error
252 $error = $model->getError($last = null, $string = true);
253 }
254 }
255 catch (Exception $e)
256 {
257 // catch any exception and go ahead
258 $error = $e->getMessage();
259 }
260 catch (Throwable $e)
261 {
262 // catche any failure and go ahead
263 $error = $e->getMessage();
264 }
265
266 // check if we caught an error
267 if ($error)
268 {
269 // store log details
270 JModelVAP::getInstance('cronjoblog')->save(array(
271 'content' => $error,
272 'status' => 0,
273 'id_cronjob' => (int) $id_cron,
274 ));
275 }
276
277 return $status;
278 }
279
280 /**
281 * Returns a list of published cron jobs.
282 *
283 * @return array An array of cron jobs.
284 *
285 * @since 1.2.3
286 */
287 public static function getJobs($all = false)
288 {
289 $dbo = JFactory::getDbo();
290
291 // retrieve all the published cron jobs
292 $q = $dbo->getQuery(true)
293 ->from($dbo->qn('#__vikappointments_cronjob'))
294 ->where($dbo->qn('published') . ' = 1');
295
296 if ($all)
297 {
298 $q->select('*');
299 }
300 else
301 {
302 $q->select($dbo->qn('id'));
303 }
304
305 $dbo->setQuery($q);
306 $dbo->execute();
307
308 if (!$dbo->getNumRows())
309 {
310 return [];
311 }
312
313 if ($all)
314 {
315 return $dbo->loadObjectList();
316 }
317
318 return $dbo->loadColumn();
319 }
320
321 /**
322 * Creates the HTML to support a field for the selection of the
323 * recurrence while creating/editing a cron job.
324 *
325 * @param array $forms An associative array containing the HTML.
326 * @param object $view The view instance.
327 *
328 * @return array $forms The resulting HTML array.
329 *
330 * @since 1.2.3
331 */
332 public static function addScheduleControlForm($forms, $view)
333 {
334 if (!is_array($forms))
335 {
336 $forms = [];
337 }
338
339 if (!isset($forms['details']))
340 {
341 // init details section
342 $forms['details'] = '';
343 }
344
345 // check whether the schedule key property exists
346 if (!property_exists($view->cron, 'schedule_key'))
347 {
348 static::installScheduleControl();
349 $view->cron->schedule_key = null;
350 }
351
352 $vik = VAPApplication::getInstance();
353
354 // fetch list of supported schedule intervals
355 $schedules = wp_get_schedules();
356
357 // sort the options in ascending order
358 uasort($schedules, function($a, $b)
359 {
360 return $a['interval'] - $b['interval'];
361 });
362
363 $options = [];
364
365 foreach ($schedules as $k => $schedule)
366 {
367 $options[] = JHtml::fetch('select.option', $k, $schedule['display']);
368 }
369
370 // build HTML control
371 $forms['details'] .= $vik->openControl(__('Recurrence', 'vikappointments'));
372 $forms['details'] .= '<select name="schedule_key">' . JHtml::fetch('select.options', $options, 'value', 'text', $view->cron->schedule_key) . '</select>';
373 $forms['details'] .= $vik->closeControl();
374
375 return $forms;
376 }
377
378 /**
379 * Executes while saving a cron job, so that it is possible to
380 * inject within the array to save the selected recurrence.
381 *
382 * @param boolean $save The saving flag.
383 * @param array &$src The array holding the data to save.
384 *
385 * @return boolean False to abort the saving process.
386 *
387 * @since 1.2.3
388 */
389 public static function saveScheduleControl($save, &$src)
390 {
391 if (is_null($save))
392 {
393 $save = true;
394 }
395
396 $src = (array) $src;
397
398 $input = JFactory::getApplication()->input;
399
400 // fetch schedule key from request
401 $schedule_key = $input->get('schedule_key', null, 'string');
402
403 // make sure the schedule key has been specified
404 if (!is_null($schedule_key))
405 {
406 // inject schedule key into the array to save
407 $src['schedule_key'] = $schedule_key;
408 }
409
410 return $save;
411 }
412
413 /**
414 * Checks the cron job scheduling after updating a record.
415 *
416 * @param boolean $status Dummy argument for WordPress bc.
417 * @param array $cron The cron jobs details.
418 * @param boolean $is_new True in case of insert.
419 *
420 * @return void
421 *
422 * @since 1.2.3
423 */
424 public static function checkSchedulingAfterSave($status, $cron, $is_new)
425 {
426 if (!$is_new)
427 {
428 // check only in case of update
429 static::checkJobScheduling($cron);
430 }
431 }
432
433 /**
434 * Checks the cron job scheduling after updating a record.
435 *
436 * @param boolean $return Dummy argument for WordPress bc.
437 * @param array $cron The cron jobs details.
438 * @param integer $state The publishing state.
439 *
440 * @return void
441 *
442 * @since 1.2.3
443 */
444 public static function checkSchedulingAfterPublish($return, $id, $state)
445 {
446 static::checkJobScheduling(['id' => $id]);
447 }
448
449 /**
450 * Unschedules the cron jobs that are going to be deleted.
451 *
452 * @param boolean $delete The deleting flag.
453 * @param array $ids An array of IDs to delete.
454 *
455 * @return boolean False to abort the deleting process.
456 *
457 * @since 1.2.3
458 */
459 public static function checkSchedulingBeforeDelete($delete, $ids)
460 {
461 if (is_null($delete))
462 {
463 $delete = true;
464 }
465
466 if ($delete)
467 {
468 $model = JModelVAP::getInstance('cronjob');
469
470 foreach ($ids as $id)
471 {
472 // fetch cron details
473 $cron = $model->getItem($id);
474
475 if (!$cron)
476 {
477 // cron not found, go ahead...
478 continue;
479 }
480
481 // turn off publishing to detach the scheduling
482 $cron->published = 0;
483
484 // trigger changes
485 static::checkJobScheduling($cron);
486 }
487 }
488
489 return $delete;
490 }
491
492 /**
493 * Checks whether an existing schedule should be recreated as a result
494 * of any significant changes.
495 *
496 * @param array $cron The cron record.
497 *
498 * @return void
499 *
500 * @since 1.2.3
501 */
502 protected static function checkJobScheduling($cron)
503 {
504 $cron = (array) $cron;
505
506 if (!array_key_exists('published', $cron) || !array_key_exists('schedule_key', $cron))
507 {
508 // reload all the details of the updated cron job
509 $cron = JModelVAP::getInstance('cronjob')->getItem(@$cron['id']);
510 }
511 else
512 {
513 // cast to object
514 $cron = (object) $cron;
515 }
516
517 if (!$cron)
518 {
519 // ops...
520 return;
521 }
522
523 if (!isset($cron->schedule_key))
524 {
525 // property not yet installed, use the default one to properly
526 // unschedule the registered event
527 $cron->schedule_key = 'hourly';
528 }
529
530 // build cron listener hook
531 $hook = static::getScheduleHook($cron);
532
533 // fetch the next scheduled event
534 $event = wp_get_scheduled_event($hook);
535
536 if (!$event)
537 {
538 // no scheduled event, do nothing...
539 return;
540 }
541
542 if (!$cron->published || $cron->schedule_key !== $event->schedule)
543 {
544 // unschedule the event
545 wp_unschedule_event($event->timestamp, $hook);
546 }
547 }
548
549 /**
550 * Returns the hook to be used while scheduling the execution
551 * of a cron job.
552 *
553 * @param object $cron The cron object.
554 *
555 * @return string The resulting hook.
556 *
557 * @since 1.2.3
558 */
559 protected static function getScheduleHook($cron)
560 {
561 $cron = (object) $cron;
562
563 // build cron listener hook
564 return implode('_', [
565 'vikappointments',
566 'cron',
567 preg_replace("/\.php$/", '', $cron->class),
568 $cron->id,
569 ]);
570 }
571
572 /**
573 * Install the resources needed to support a custom recurrence for
574 * the created cron jobs.
575 *
576 * @return void
577 *
578 * @since 1.2.3
579 */
580 protected static function installScheduleControl()
581 {
582 $cron = JModelVAP::getInstance('cronjob')->getItem(0, $blank = true);
583
584 if (!property_exists($cron, 'schedule_key'))
585 {
586 $dbo = JFactory::getDbo();
587
588 // alter the table to allow the storage of the selected recurrence
589 $q = "ALTER TABLE `#__vikappointments_cronjob` ADD COLUMN `schedule_key` varchar(64) DEFAULT NULL AFTER `published`";
590
591 $dbo->setQuery($q);
592 $dbo->execute();
593 }
594 }
595 }
596