├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── resources
└── views
│ └── index.blade.php
└── src
├── CronSchedule.php
├── Scheduling.php
├── SchedulingController.php
└── SchedulingServiceProvider.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | phpunit.phar
3 | /vendor
4 | composer.phar
5 | composer.lock
6 | *.project
7 | .idea/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jens Segers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Scheduling task manager for laravel-admin
2 | ============================
3 |
4 | [](https://styleci.io/repos/99676857)
5 | [](https://packagist.org/packages/laravel-admin-ext/scheduling)
6 | [](https://packagist.org/packages/laravel-admin-ext/scheduling)
7 | []()
8 |
9 | A web interface for manage task scheduling in laravel.
10 |
11 | [Documentation](http://laravel-admin.org/docs/#/en/extension-scheduling) | [中文文档](http://laravel-admin.org/docs/#/zh/extension-scheduling)
12 |
13 | ## Screenshot
14 |
15 | 
16 |
17 | ## Installation
18 |
19 | ```
20 | $ composer require laravel-admin-ext/scheduling
21 |
22 |
23 | $ php artisan admin:import scheduling
24 | ```
25 |
26 | Open `http://your-host/admin/scheduling`.
27 |
28 | Try to add a scheduling task in `app/Console/Kernel.php` like this:
29 |
30 | ```php
31 | class Kernel extends ConsoleKernel
32 | {
33 | protected function schedule(Schedule $schedule)
34 | {
35 | $schedule->command('inspire')->everyTenMinutes();
36 |
37 | $schedule->command('route:list')->dailyAt('02:00');
38 | }
39 | }
40 |
41 | ```
42 |
43 | And you can find these tasks in scheduling panel.
44 |
45 | License
46 | ------------
47 | Licensed under [The MIT License (MIT)](LICENSE).
48 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-admin-ext/scheduling",
3 | "description": "Task scheduling extension for laravel-admin",
4 | "type": "library",
5 | "keywords": ["laravel-admin", "task", "Scheduling"],
6 | "homepage": "https://github.com/laravel-admin-extensions/scheduling",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "z-song",
11 | "email": "zosong@126.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=7.0.0",
16 | "encore/laravel-admin": "~1.5"
17 | },
18 | "require-dev": {
19 | "phpunit/phpunit": "~6.0",
20 | "laravel/laravel": "~5.5"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Encore\\Admin\\Scheduling\\": "src/"
25 | }
26 | },
27 | "extra": {
28 | "laravel": {
29 | "providers": [
30 | "Encore\\Admin\\Scheduling\\SchedulingServiceProvider"
31 | ]
32 |
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/resources/views/index.blade.php:
--------------------------------------------------------------------------------
1 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | # |
40 | Task |
41 | Run at |
42 | Next run time |
43 | Description |
44 | Run |
45 |
46 | @foreach($events as $index => $event)
47 |
48 | {{ $index+1 }}. |
49 | {{ $event['task']['name'] }} |
50 | {{ $event['expression'] }} {{ $event['readable'] }} |
51 | {{ $event['nextRunDate'] }} |
52 | {{ $event['description'] }} |
53 | Run |
54 |
55 | @endforeach
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/CronSchedule.php:
--------------------------------------------------------------------------------
1 | next() returns the first scheduled datetime after in array format.
16 | * ->nextAsString() does the same with an ISO string as the result.
17 | * ->nextAsTime() does the same with a UNIX timestamp as the result.
18 | *
19 | * ->previous() returns the first scheduled datetime before in array format.
20 | * ->previousAsString() does the same with an ISO string as the result.
21 | * ->previousAsTime() does the same with a UNIX timestamp as the result.
22 | *
23 | * ->asNaturalLanguage() returns the entire schedule in natural language form.
24 | *
25 | * In the next and previous functions, can be a UNIX timestamp, an ISO string or an array format such as returned by
26 | * next() and previous().
27 | *
28 | * Copyright: 2012 Joost Brugman (joost@brugmanholding.com, joost@joostbrugman.com)
29 | *
30 | * This file is part of the Streamline plugin "StreamlineFoundation" and referenced in the next paragraphs inside this comment block as "this
31 | * plugin". It is based on the Streamline application framework.
32 | *
33 | * This plugin is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as
34 | * published by the Free Software Foundation, either version 3 of the License, or any later version. This plugin is distributed in the
35 | * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
36 | * PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public
37 | * License along with Streamline. If not, see .
38 | */
39 |
40 | class CronSchedule
41 | {
42 | // The actual minutes, hours, daysOfMonth, months, daysOfWeek and years selected by the provided cron specification.
43 | private $_minutes = [];
44 | private $_hours = [];
45 | private $_daysOfMonth = [];
46 | private $_months = [];
47 | private $_daysOfWeek = [];
48 | private $_years = [];
49 |
50 | // The original cron specification in compiled form.
51 | private $_cronMinutes = [];
52 | private $_cronHours = [];
53 | private $_cronDaysOfMonth = [];
54 | private $_cronMonths = [];
55 | private $_cronDaysOfWeek = [];
56 | private $_cronYears = [];
57 |
58 | // The language table
59 | private $_lang = false;
60 |
61 | /**
62 | * Minimum and maximum years to cope with the Year 2038 problem in UNIX. We run PHP which most likely runs on a UNIX environment so we
63 | * must assume vulnerability.
64 | */
65 | protected $RANGE_YEARS_MIN = 1970; // Must match date range supported by date(). See also: http://en.wikipedia.org/wiki/Year_2038_problem
66 | protected $RANGE_YEARS_MAX = 2037; // Must match date range supported by date(). See also: http://en.wikipedia.org/wiki/Year_2038_problem
67 |
68 | /**
69 | * Function: __construct.
70 | *
71 | * Description: Performs only base initialization, including language initialization.
72 | *
73 | * Parameters: $language The languagecode of the chosen language.
74 | */
75 | public function __construct($language = 'en')
76 | {
77 | $this->initLang($language);
78 | }
79 |
80 | //
81 | // Function: fromCronString
82 | //
83 | // Description: Creates a new Schedule object based on a Cron specification.
84 | //
85 | // Parameters: $cronSpec A string containing a cron specification.
86 | // $language The language to use to create a natural language representation of the string
87 | //
88 | // Result: A new Schedule object. An \Exception is thrown if the specification is invalid.
89 | //
90 |
91 | final public static function fromCronString($cronSpec = '* * * * * *', $language = 'en')
92 | {
93 |
94 | // Split input liberal. Single or multiple Spaces, Tabs and Newlines are all allowed as separators.
95 | if (count($elements = preg_split('/\s+/', $cronSpec)) < 5) {
96 | throw new Exception('Invalid specification.');
97 | }
98 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
99 | // Named ranges in cron entries
100 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101 | $arrMonths = ['JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6, 'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' => 10, 'NOV' => 11, 'DEC' => 12];
102 | $arrDaysOfWeek = ['SUN' => 0, 'MON' => 1, 'TUE' => 2, 'WED' => 3, 'THU' => 4, 'FRI' => 5, 'SAT' => 6];
103 |
104 | // Translate the cron specification into arrays that hold specifications of the actual dates
105 | $newCron = new self($language);
106 | $newCron->_cronMinutes = $newCron->cronInterpret($elements[0], 0, 59, [], 'minutes');
107 | $newCron->_cronHours = $newCron->cronInterpret($elements[1], 0, 23, [], 'hours');
108 | $newCron->_cronDaysOfMonth = $newCron->cronInterpret($elements[2], 1, 31, [], 'daysOfMonth');
109 | $newCron->_cronMonths = $newCron->cronInterpret($elements[3], 1, 12, $arrMonths, 'months');
110 | $newCron->_cronDaysOfWeek = $newCron->cronInterpret($elements[4], 0, 6, $arrDaysOfWeek, 'daysOfWeek');
111 |
112 | $newCron->_minutes = $newCron->cronCreateItems($newCron->_cronMinutes);
113 | $newCron->_hours = $newCron->cronCreateItems($newCron->_cronHours);
114 | $newCron->_daysOfMonth = $newCron->cronCreateItems($newCron->_cronDaysOfMonth);
115 | $newCron->_months = $newCron->cronCreateItems($newCron->_cronMonths);
116 | $newCron->_daysOfWeek = $newCron->cronCreateItems($newCron->_cronDaysOfWeek);
117 |
118 | if (isset($elements[5])) {
119 | $newCron->_cronYears = $newCron->cronInterpret($elements[5], $newCron->RANGE_YEARS_MIN, $newCron->RANGE_YEARS_MAX, [], 'years');
120 | $newCron->_years = $newCron->cronCreateItems($newCron->_cronYears);
121 | }
122 |
123 | return $newCron;
124 | }
125 |
126 | /*
127 | * Function: cronInterpret
128 | *
129 | * Description: Interprets a single field from a cron specification. Throws an \Exception if the specification is in some way invalid.
130 | *
131 | * Parameters: $specification The actual text from the spefication, such as 12-38/3
132 | * $rangeMin The lowest value for specification.
133 | * $rangeMax The highest value for specification
134 | * $namesItems A key/value pair where value is a value between $rangeMin and $rangeMax and key is the name for that value.
135 | * $errorName The name of the category to use in case of an error.
136 | *
137 | * Result: An array with entries, each of which is an array with the following fields:
138 | * 'number1' The first number of the range or the number specified
139 | * 'number2' The second number of the range if a range is specified
140 | * 'hasInterval' TRUE if a range is specified. FALSE otherwise
141 | * 'interval' The interval if a range is specified.
142 | */
143 | final private function cronInterpret($specification, $rangeMin, $rangeMax, $namedItems, $errorName)
144 | {
145 | if ((!is_string($specification)) && (!(is_int($specification)))) {
146 | throw new Exception('Invalid specification.');
147 | }
148 | // Multiple values, separated by comma
149 | $specs = [];
150 | $specs['rangeMin'] = $rangeMin;
151 | $specs['rangeMax'] = $rangeMax;
152 | $specs['elements'] = [];
153 | $arrSegments = explode(',', $specification);
154 | foreach ($arrSegments as $segment) {
155 | $hasRange = (($posRange = strpos($segment, '-')) !== false);
156 | $hasInterval = (($posIncrement = strpos($segment, '/')) !== false);
157 |
158 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
159 | // Check: Increment without range is invalid
160 |
161 | //if(!$hasRange && $hasInterval) throw new \Exception("Invalid Range ($errorName).");
162 |
163 | // Check: Increment must be final specification
164 |
165 | if ($hasRange && $hasInterval) {
166 | if ($posIncrement < $posRange) {
167 | throw new \Exception("Invalid order ($errorName).");
168 | }
169 | }
170 | // GetSegments
171 |
172 | $segmentNumber1 = $segment;
173 | $segmentNumber2 = '';
174 | $segmentIncrement = '';
175 | $intIncrement = 1;
176 | if ($hasInterval) {
177 | $segmentNumber1 = substr($segment, 0, $posIncrement);
178 | $segmentIncrement = substr($segment, $posIncrement + 1);
179 | }
180 |
181 | if ($hasRange) {
182 | $segmentNumber2 = substr($segmentNumber1, $posRange + 1);
183 | $segmentNumber1 = substr($segmentNumber1, 0, $posRange);
184 | }
185 |
186 | // Get and validate first value in range
187 |
188 | if ($segmentNumber1 == '*') {
189 | $intNumber1 = $rangeMin;
190 | $intNumber2 = $rangeMax;
191 | $hasRange = true;
192 | } else {
193 | if (array_key_exists(strtoupper($segmentNumber1), $namedItems)) {
194 | $segmentNumber1 = $namedItems[strtoupper($segmentNumber1)];
195 | }
196 | if (((string) ($intNumber1 = (int) $segmentNumber1)) != $segmentNumber1) {
197 | throw new \Exception("Invalid symbol ($errorName).");
198 | }
199 | if (($intNumber1 < $rangeMin) || ($intNumber1 > $rangeMax)) {
200 | throw new \Exception("Out of bounds ($errorName).");
201 | }
202 | // Get and validate second value in range
203 |
204 | if ($hasRange) {
205 | if (array_key_exists(strtoupper($segmentNumber2), $namedItems)) {
206 | $segmentNumber2 = $namedItems[strtoupper($segmentNumber2)];
207 | }
208 | if (((string) ($intNumber2 = (int) $segmentNumber2)) != $segmentNumber2) {
209 | throw new \Exception("Invalid symbol ($errorName).");
210 | }
211 | if (($intNumber2 < $rangeMin) || ($intNumber2 > $rangeMax)) {
212 | throw new \Exception("Out of bounds ($errorName).");
213 | }
214 | if ($intNumber1 > $intNumber2) {
215 | throw new \Exception("Invalid range ($errorName).");
216 | }
217 | }
218 | }
219 |
220 | // Get and validate increment
221 |
222 | if ($hasInterval) {
223 | if (($intIncrement = (int) $segmentIncrement) != $segmentIncrement) {
224 | throw new \Exception("Invalid symbol ($errorName).");
225 | }
226 | if ($intIncrement < 1) {
227 | throw new \Exception("Out of bounds ($errorName).");
228 | }
229 | }
230 |
231 | // Apply range and increment
232 |
233 | $elem = [];
234 | $elem['number1'] = $intNumber1;
235 | $elem['hasInterval'] = $hasRange;
236 | if ($hasRange) {
237 | $elem['number2'] = $intNumber2;
238 | $elem['interval'] = $intIncrement;
239 | }
240 | $specs['elements'][] = $elem;
241 | }
242 |
243 | return $specs;
244 | }
245 |
246 | //
247 | // Function: cronCreateItems
248 | //
249 | // Description: Uses the interpreted cron specification of a single item from a cron specification to create an array with keys that match the
250 | // selected items.
251 | //
252 | // Parameters: $cronInterpreted The interpreted specification
253 | //
254 | // Result: An array where each key identifies a matching entry. E.g. the cron specification */10 for minutes will yield an array
255 | // [0] => 1
256 | // [10] => 1
257 | // [20] => 1
258 | // [30] => 1
259 | // [40] => 1
260 | // [50] => 1
261 | //
262 |
263 | final private function cronCreateItems($cronInterpreted)
264 | {
265 | $items = [];
266 |
267 | foreach ($cronInterpreted['elements'] as $elem) {
268 | if (!$elem['hasInterval']) {
269 | $items[$elem['number1']] = true;
270 | } else {
271 | for ($number = $elem['number1']; $number <= $elem['number2']; $number += $elem['interval']) {
272 | $items[$number] = true;
273 | }
274 | }
275 | }
276 | ksort($items);
277 |
278 | return $items;
279 | }
280 |
281 | //
282 | // Function: dtFromParameters
283 | //
284 | // Description: Transforms a flexible parameter passing of a datetime specification into an internally used array.
285 | //
286 | // Parameters: $time If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
287 | // If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
288 | // If a string, interpreted as unix time.
289 | // If omitted or specified FALSE, defaults to the current time.
290 | //
291 | // Result: An array with indices 0-4 holding the actual interpreted values for $minute, $hour, $day, $month and $year.
292 | //
293 |
294 | final private function dtFromParameters($time = false)
295 | {
296 | if ($time === false) {
297 | $arrTime = getdate();
298 |
299 | return [$arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']];
300 | } elseif (is_array($time)) {
301 | return $time;
302 | } elseif (is_string($time)) {
303 | $arrTime = getdate(strtotime($time));
304 |
305 | return [$arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']];
306 | } elseif (is_int($time)) {
307 | $arrTime = getdate($time);
308 |
309 | return [$arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']];
310 | }
311 | }
312 |
313 | final private function dtAsString($arrDt)
314 | {
315 | if ($arrDt === false) {
316 | return false;
317 | }
318 |
319 | return $arrDt[4].'-'.(strlen($arrDt[3]) == 1 ? '0' : '').$arrDt[3].'-'.(strlen($arrDt[2]) == 1 ? '0' : '').$arrDt[2].' '.(strlen($arrDt[1]) == 1 ? '0' : '').$arrDt[1].':'.(strlen($arrDt[0]) == 1 ? '0' : '').$arrDt[0].':00';
320 | }
321 |
322 | //
323 | // Function: match
324 | //
325 | // Description: Returns TRUE if the specified date and time corresponds to a scheduled point in time. FALSE otherwise.
326 | //
327 | // Parameters: $time If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
328 | // If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
329 | // If a string, interpreted as unix time.
330 | // If omitted or specified FALSE, defaults to the current time.
331 | //
332 | // Result: TRUE if the schedule matches the specified datetime. FALSE otherwise.
333 | //
334 |
335 | final public function match($time = false)
336 | {
337 |
338 | // Convert parameters to array datetime
339 |
340 | $arrDT = $this->dtFromParameters($time);
341 |
342 | // Verify match
343 |
344 | // Years
345 | if (!array_key_exists($arrDT[4], $this->_years)) {
346 | return false;
347 | }
348 | // Day of week
349 | if (!array_key_exists(date('w', strtotime($arrDT[4].'-'.$arrDT[3].'-'.$arrDT[2])), $this->_daysOfWeek)) {
350 | return false;
351 | }
352 | // Month
353 | if (!array_key_exists($arrDT[3], $this->_months)) {
354 | return false;
355 | }
356 | // Day of month
357 | if (!array_key_exists($arrDT[2], $this->_daysOfMonth)) {
358 | return false;
359 | }
360 | // Hours
361 | if (!array_key_exists($arrDT[1], $this->_hours)) {
362 | return false;
363 | }
364 | // Minutes
365 | if (!array_key_exists($arrDT[0], $this->_minutes)) {
366 | return false;
367 | }
368 |
369 | return true;
370 | }
371 |
372 | //
373 | // Function: next
374 | //
375 | // Description: Acquires the first scheduled datetime beyond the provided one.
376 | //
377 | // Parameters: $time If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
378 | // If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
379 | // If a string, interpreted as unix time.
380 | // If omitted or specified FALSE, defaults to the current time.
381 | //
382 | // Result: An array with the following keys:
383 | // 0 Next scheduled minute
384 | // 1 Next scheduled hour
385 | // 2 Next scheduled date
386 | // 3 Next scheduled month
387 | // 4 Next scheduled year
388 | //
389 |
390 | final public function next($time = false)
391 | {
392 |
393 | // Convert parameters to array datetime
394 |
395 | $arrDT = $this->dtFromParameters($time);
396 |
397 | while (1) {
398 |
399 | // Verify the current date is in range. If not, move into range and consider this the next position
400 |
401 | if (!array_key_exists($arrDT[4], $this->_years)) {
402 | if (($arrDT[4] = $this->getEarliestItem($this->_years, $arrDT[4], false)) === false) {
403 | return false;
404 | }
405 | $arrDT[3] = $this->getEarliestItem($this->_months);
406 | $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth);
407 | $arrDT[1] = $this->getEarliestItem($this->_hours);
408 | $arrDT[0] = $this->getEarliestItem($this->_minutes);
409 | break;
410 | } elseif (!array_key_exists($arrDT[3], $this->_months)) {
411 | $arrDT[3] = $this->getEarliestItem($this->_months, $arrDT[3]);
412 | $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth);
413 | $arrDT[1] = $this->getEarliestItem($this->_hours);
414 | $arrDT[0] = $this->getEarliestItem($this->_minutes);
415 | break;
416 | } elseif (!array_key_exists($arrDT[2], $this->_daysOfMonth)) {
417 | $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth, $arrDT[2]);
418 | $arrDT[1] = $this->getEarliestItem($this->_hours);
419 | $arrDT[0] = $this->getEarliestItem($this->_minutes);
420 | break;
421 | } elseif (!array_key_exists($arrDT[1], $this->_hours)) {
422 | $arrDT[1] = $this->getEarliestItem($this->_hours, $arrDT[1]);
423 | $arrDT[0] = $this->getEarliestItem($this->_minutes);
424 | break;
425 | } elseif (!array_key_exists($arrDT[1], $this->_hours)) {
426 | $arrDT[0] = $this->getEarliestItem($this->_minutes, $arrDT[0]);
427 | break;
428 | }
429 |
430 | // Advance minute, hour, date, month and year while overflowing.
431 |
432 | $daysInThisMonth = date('t', strtotime($arrDT[4].'-'.$arrDT[3]));
433 | if ($this->advanceItem($this->_minutes, 0, 59, $arrDT[0])) {
434 | if ($this->advanceItem($this->_hours, 0, 23, $arrDT[1])) {
435 | if ($this->advanceItem($this->_daysOfMonth, 0, $daysInThisMonth, $arrDT[2])) {
436 | if ($this->advanceItem($this->_months, 1, 12, $arrDT[3])) {
437 | if ($this->advanceItem($this->_years, $this->RANGE_YEARS_MIN, $this->RANGE_YEARS_MAX, $arrDT[4])) {
438 | return false;
439 | }
440 | }
441 | }
442 | }
443 | }
444 | break;
445 | }
446 |
447 | // If Datetime now points to a day that is schedule then return.
448 |
449 | $dayOfWeek = date('w', strtotime($this->dtAsString($arrDT)));
450 | if (array_key_exists($dayOfWeek, $this->_daysOfWeek)) {
451 | return $arrDT;
452 | }
453 |
454 | // Otherwise move to next scheduled date
455 |
456 | return $this->next($arrDT);
457 | }
458 |
459 | final public function nextAsString($time = false)
460 | {
461 | return $this->dtAsString($this->next($time));
462 | }
463 |
464 | final public function nextAsTime($time = false)
465 | {
466 | return strtotime($this->dtAsString($this->next($time)));
467 | }
468 |
469 | //
470 | // Function: advanceItem
471 | //
472 | // Description: Advances the current item to the next one (the next minute, the next hour, etc.).
473 | //
474 | // Parameters: $arrItems A reference to the collection in which to advance.
475 | // $rangeMin The lowest possible value for $current.
476 | // $rangeMax The highest possible value for $current
477 | // $current The index that is being incremented.
478 | //
479 | // Result: FALSE if current did not overflow (reset back to the earliest possible value). TRUE if it did.
480 | //
481 |
482 | final private function advanceItem($arrItems, $rangeMin, $rangeMax, &$current)
483 | {
484 |
485 | // Advance pointer
486 |
487 | $current++;
488 |
489 | // If still before start, move to earliest
490 |
491 | if ($current < $rangeMin) {
492 | $current = $this->getEarliestItem($arrItems);
493 | }
494 |
495 | // Parse items until found or overflow
496 |
497 | for (; $current <= $rangeMax; $current++) {
498 | if (array_key_exists($current, $arrItems)) {
499 | return false;
500 | }
501 | } // We did not overflow
502 |
503 | // Or overflow
504 |
505 | $current = $this->getEarliestItem($arrItems);
506 |
507 | return true;
508 | }
509 |
510 | //
511 | // Function: getEarliestItem
512 | //
513 | // Description: Retrieves the earliest item in a collection, e.g. the earliest minute or the earliest month.
514 | //
515 | // Parameters: $arrItems A reference to the collection in which to search.
516 | // $afterItem The highest index that is to be skipped.
517 | //
518 |
519 | final private function getEarliestItem($arrItems, $afterItem = false, $allowOverflow = true)
520 | {
521 |
522 | // If no filter is specified, return the earliest listed item.
523 |
524 | if ($afterItem === false) {
525 | reset($arrItems);
526 |
527 | return key($arrItems);
528 | }
529 |
530 | // Or parse until we passed $afterItem
531 |
532 | foreach ($arrItems as $key => $value) {
533 | if ($key > $afterItem) {
534 | return $key;
535 | }
536 | }
537 |
538 | // If still nothing found, we may have exhausted our options.
539 |
540 | if (!$allowOverflow) {
541 | return false;
542 | }
543 | reset($arrItems);
544 |
545 | return key($arrItems);
546 | }
547 |
548 | //
549 | // Function: previous
550 | //
551 | // Description: Acquires the first scheduled datetime before the provided one.
552 | //
553 | // Parameters: $time If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
554 | // If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
555 | // If a string, interpreted as unix time.
556 | // If omitted or specified FALSE, defaults to the current time.
557 | //
558 | // Result: An array with the following keys:
559 | // 0 Previous scheduled minute
560 | // 1 Previous scheduled hour
561 | // 2 Previous scheduled date
562 | // 3 Previous scheduled month
563 | // 4 Previous scheduled year
564 | //
565 |
566 | final public function previous($time = false)
567 | {
568 |
569 | // Convert parameters to array datetime
570 |
571 | $arrDT = $this->dtFromParameters($time);
572 |
573 | while (1) {
574 |
575 | // Verify the current date is in range. If not, move into range and consider this the previous position
576 |
577 | if (!array_key_exists($arrDT[4], $this->_years)) {
578 | if (($arrDT[4] = $this->getLatestItem($this->_years, $arrDT[4], false)) === false) {
579 | return false;
580 | }
581 | $arrDT[3] = $this->getLatestItem($this->_months);
582 | $arrDT[2] = $this->getLatestItem($this->_daysOfMonth);
583 | $arrDT[1] = $this->getLatestItem($this->_hours);
584 | $arrDT[0] = $this->getLatestItem($this->_minutes);
585 | break;
586 | } elseif (!array_key_exists($arrDT[3], $this->_months)) {
587 | $arrDT[3] = $this->getLatestItem($this->_months, $arrDT[3]);
588 | $arrDT[2] = $this->getLatestItem($this->_daysOfMonth);
589 | $arrDT[1] = $this->getLatestItem($this->_hours);
590 | $arrDT[0] = $this->getLatestItem($this->_minutes);
591 | break;
592 | } elseif (!array_key_exists($arrDT[2], $this->_daysOfMonth)) {
593 | $arrDT[2] = $this->getLatestItem($this->_daysOfMonth, $arrDT[2]);
594 | $arrDT[1] = $this->getLatestItem($this->_hours);
595 | $arrDT[0] = $this->getLatestItem($this->_minutes);
596 | break;
597 | } elseif (!array_key_exists($arrDT[1], $this->_hours)) {
598 | $arrDT[1] = $this->getLatestItem($this->_hours, $arrDT[1]);
599 | $arrDT[0] = $this->getLatestItem($this->_minutes);
600 | break;
601 | } elseif (!array_key_exists($arrDT[1], $this->_hours)) {
602 | $arrDT[0] = $this->getLatestItem($this->_minutes, $arrDT[0]);
603 | break;
604 | }
605 |
606 | // Recede minute, hour, date, month and year while overflowing.
607 |
608 | $daysInPreviousMonth = date('t', strtotime('-1 month', strtotime($arrDT[4].'-'.$arrDT[3])));
609 | if ($this->recedeItem($this->_minutes, 0, 59, $arrDT[0])) {
610 | if ($this->recedeItem($this->_hours, 0, 23, $arrDT[1])) {
611 | if ($this->recedeItem($this->_daysOfMonth, 0, $daysInPreviousMonth, $arrDT[2])) {
612 | if ($this->recedeItem($this->_months, 1, 12, $arrDT[3])) {
613 | if ($this->recedeItem($this->_years, $this->RANGE_YEARS_MIN, $this->RANGE_YEARS_MAX, $arrDT[4])) {
614 | return false;
615 | }
616 | }
617 | }
618 | }
619 | }
620 | break;
621 | }
622 |
623 | // If Datetime now points to a day that is schedule then return.
624 |
625 | $dayOfWeek = date('w', strtotime($this->dtAsString($arrDT)));
626 | if (array_key_exists($dayOfWeek, $this->_daysOfWeek)) {
627 | return $arrDT;
628 | }
629 |
630 | // Otherwise move to next scheduled date
631 |
632 | return $this->previous($arrDT);
633 | }
634 |
635 | final public function previousAsString($time = false)
636 | {
637 | return $this->dtAsString($this->previous($time));
638 | }
639 |
640 | final public function previousAsTime($time = false)
641 | {
642 | return strtotime($this->dtAsString($this->previous($time)));
643 | }
644 |
645 | //
646 | // Function: recedeItem
647 | //
648 | // Description: Recedes the current item to the previous one (the previous minute, the previous hour, etc.).
649 | //
650 | // Parameters: $arrItems A reference to the collection in which to recede.
651 | // $rangeMin The lowest possible value for $current.
652 | // $rangeMax The highest possible value for $current
653 | // $current The index that is being decremented.
654 | //
655 | // Result: FALSE if current did not overflow (reset back to the highest possible value). TRUE if it did.
656 | //
657 |
658 | final private function recedeItem($arrItems, $rangeMin, $rangeMax, &$current)
659 | {
660 |
661 | // Recede pointer
662 |
663 | $current--;
664 |
665 | // If still above highest, move to highest
666 |
667 | if ($current > $rangeMax) {
668 | $current = $this->getLatestItem($arrItems, $rangeMax + 1);
669 | }
670 |
671 | // Parse items until found or overflow
672 |
673 | for (; $current >= $rangeMin; $current--) {
674 | if (array_key_exists($current, $arrItems)) {
675 | return false;
676 | }
677 | } // We did not overflow
678 |
679 | // Or overflow
680 |
681 | $current = $this->getLatestItem($arrItems, $rangeMax + 1);
682 |
683 | return true;
684 | }
685 |
686 | //
687 | // Function: getLatestItem
688 | //
689 | // Description: Retrieves the latest item in a collection, e.g. the latest minute or the latest month.
690 | //
691 | // Parameters: $arrItems A reference to the collection in which to search.
692 | // $beforeItem The lowest index that is to be skipped.
693 | //
694 |
695 | final private function getLatestItem($arrItems, $beforeItem = false, $allowOverflow = true)
696 | {
697 |
698 | // If no filter is specified, return the latestlisted item.
699 |
700 | if ($beforeItem === false) {
701 | end($arrItems);
702 |
703 | return key($arrItems);
704 | }
705 |
706 | // Or parse until we passed $beforeItem
707 |
708 | end($arrItems);
709 | do {
710 | if (($key = key($arrItems)) < $beforeItem) {
711 | return $key;
712 | }
713 | } while (prev($arrItems));
714 |
715 | // If still nothing found, we may have exhausted our options.
716 |
717 | if (!$allowOverflow) {
718 | return false;
719 | }
720 | end($arrItems);
721 |
722 | return key($arrItems);
723 | }
724 |
725 | //
726 | // Function:
727 | //
728 | // Description:
729 | //
730 | // Parameters:
731 | //
732 | // Result:
733 | //
734 |
735 | final private function getClass($spec)
736 | {
737 | if (!$this->classIsSpecified($spec)) {
738 | return '0';
739 | }
740 | if ($this->classIsSingleFixed($spec)) {
741 | return '1';
742 | }
743 |
744 | return '2';
745 | }
746 |
747 | //
748 | // Function:
749 | //
750 | // Description: Returns TRUE if the Cron Specification is specified. FALSE otherwise. This is true if the specification has more than one entry
751 | // or is anything than the entire approved range ("*").
752 | //
753 | // Parameters:
754 | //
755 | // Result:
756 | //
757 |
758 | final private function classIsSpecified($spec)
759 | {
760 | if ($spec['elements'][0]['hasInterval'] == false) {
761 | return true;
762 | }
763 | if ($spec['elements'][0]['number1'] != $spec['rangeMin']) {
764 | return true;
765 | }
766 | if ($spec['elements'][0]['number2'] != $spec['rangeMax']) {
767 | return true;
768 | }
769 | if ($spec['elements'][0]['interval'] != 1) {
770 | return true;
771 | }
772 |
773 | return false;
774 | }
775 |
776 | //
777 | // Function:
778 | //
779 | // Description: Returns TRUE if the Cron Specification is specified as a single value. FALSE otherwise. This is true only if there is only
780 | // one entry and the entry is only a single number (e.g. "10")
781 | //
782 | // Parameters:
783 | //
784 | // Result:
785 | //
786 |
787 | final private function classIsSingleFixed($spec)
788 | {
789 | return (count($spec['elements']) == 1) && (!$spec['elements'][0]['hasInterval']);
790 | }
791 |
792 | final private function initLang($language = 'en')
793 | {
794 | switch ($language) {
795 | case 'en':
796 | $this->_lang['elemMin: at_the_hour'] = 'at the hour';
797 | $this->_lang['elemMin: after_the_hour_every_X_minute'] = 'every minute';
798 | $this->_lang['elemMin: after_the_hour_every_X_minute_plural'] = 'every @1 minutes';
799 | $this->_lang['elemMin: every_consecutive_minute'] = 'every consecutive minute';
800 | $this->_lang['elemMin: every_consecutive_minute_plural'] = 'every consecutive @1 minutes';
801 | $this->_lang['elemMin: every_minute'] = 'every minute';
802 | $this->_lang['elemMin: between_X_and_Y'] = 'from the @1 to the @2';
803 | $this->_lang['elemMin: at_X:Y'] = 'At @1:@2';
804 | $this->_lang['elemHour: past_X:00'] = 'past @1:00';
805 | $this->_lang['elemHour: between_X:00_and_Y:59'] = 'between @1:00 and @2:59';
806 | $this->_lang['elemHour: in_the_60_minutes_past_'] = 'in the 60 minutes past every consecutive hour';
807 | $this->_lang['elemHour: in_the_60_minutes_past__plural'] = 'in the 60 minutes past every consecutive @1 hours';
808 | $this->_lang['elemHour: past_every_consecutive_'] = 'past every consecutive hour';
809 | $this->_lang['elemHour: past_every_consecutive__plural'] = 'past every consecutive @1 hours';
810 | $this->_lang['elemHour: past_every_hour'] = 'past every hour';
811 | $this->_lang['elemDOM: the_X'] = 'the @1';
812 | $this->_lang['elemDOM: every_consecutive_day'] = 'every consecutive day';
813 | $this->_lang['elemDOM: every_consecutive_day_plural'] = 'every consecutive @1 days';
814 | $this->_lang['elemDOM: on_every_day'] = 'on every day';
815 | $this->_lang['elemDOM: between_the_Xth_and_Yth'] = 'between the @1 and the @2';
816 | $this->_lang['elemDOM: on_the_X'] = 'on the @1';
817 | $this->_lang['elemDOM: on_X'] = 'on @1';
818 | $this->_lang['elemMonth: every_X'] = 'every @1';
819 | $this->_lang['elemMonth: every_consecutive_month'] = 'every consecutive month';
820 | $this->_lang['elemMonth: every_consecutive_month_plural'] = 'every consecutive @1 months';
821 | $this->_lang['elemMonth: between_X_and_Y'] = 'from @1 to @2';
822 | $this->_lang['elemMonth: of_every_month'] = 'of every month';
823 | $this->_lang['elemMonth: during_every_X'] = 'during every @1';
824 | $this->_lang['elemMonth: during_X'] = 'during @1';
825 | $this->_lang['elemYear: in_X'] = 'in @1';
826 | $this->_lang['elemYear: every_consecutive_year'] = 'every consecutive year';
827 | $this->_lang['elemYear: every_consecutive_year_plural'] = 'every consecutive @1 years';
828 | $this->_lang['elemYear: from_X_through_Y'] = 'from @1 through @2';
829 | $this->_lang['elemDOW: on_every_day'] = 'on every day';
830 | $this->_lang['elemDOW: on_X'] = 'on @1';
831 | $this->_lang['elemDOW: but_only_on_X'] = 'but only if the event takes place on @1';
832 | $this->_lang['separator_and'] = 'and';
833 | $this->_lang['separator_or'] = 'or';
834 | $this->_lang['day: 0_plural'] = 'Sundays';
835 | $this->_lang['day: 1_plural'] = 'Mondays';
836 | $this->_lang['day: 2_plural'] = 'Tuesdays';
837 | $this->_lang['day: 3_plural'] = 'Wednesdays';
838 | $this->_lang['day: 4_plural'] = 'Thursdays';
839 | $this->_lang['day: 5_plural'] = 'Fridays';
840 | $this->_lang['day: 6_plural'] = 'Saturdays';
841 | $this->_lang['month: 1'] = 'January';
842 | $this->_lang['month: 2'] = 'February';
843 | $this->_lang['month: 3'] = 'March';
844 | $this->_lang['month: 4'] = 'April';
845 | $this->_lang['month: 5'] = 'May';
846 | $this->_lang['month: 6'] = 'June';
847 | $this->_lang['month: 7'] = 'July';
848 | $this->_lang['month: 8'] = 'Augustus';
849 | $this->_lang['month: 9'] = 'September';
850 | $this->_lang['month: 10'] = 'October';
851 | $this->_lang['month: 11'] = 'November';
852 | $this->_lang['month: 12'] = 'December';
853 | $this->_lang['ordinal: 1'] = '1st';
854 | $this->_lang['ordinal: 2'] = '2nd';
855 | $this->_lang['ordinal: 3'] = '3rd';
856 | $this->_lang['ordinal: 4'] = '4th';
857 | $this->_lang['ordinal: 5'] = '5th';
858 | $this->_lang['ordinal: 6'] = '6th';
859 | $this->_lang['ordinal: 7'] = '7th';
860 | $this->_lang['ordinal: 8'] = '8th';
861 | $this->_lang['ordinal: 9'] = '9th';
862 | $this->_lang['ordinal: 10'] = '10th';
863 | $this->_lang['ordinal: 11'] = '11th';
864 | $this->_lang['ordinal: 12'] = '12th';
865 | $this->_lang['ordinal: 13'] = '13th';
866 | $this->_lang['ordinal: 14'] = '14th';
867 | $this->_lang['ordinal: 15'] = '15th';
868 | $this->_lang['ordinal: 16'] = '16th';
869 | $this->_lang['ordinal: 17'] = '17th';
870 | $this->_lang['ordinal: 18'] = '18th';
871 | $this->_lang['ordinal: 19'] = '19th';
872 | $this->_lang['ordinal: 20'] = '20th';
873 | $this->_lang['ordinal: 21'] = '21st';
874 | $this->_lang['ordinal: 22'] = '22nd';
875 | $this->_lang['ordinal: 23'] = '23rd';
876 | $this->_lang['ordinal: 24'] = '24th';
877 | $this->_lang['ordinal: 25'] = '25th';
878 | $this->_lang['ordinal: 26'] = '26th';
879 | $this->_lang['ordinal: 27'] = '27th';
880 | $this->_lang['ordinal: 28'] = '28th';
881 | $this->_lang['ordinal: 29'] = '29th';
882 | $this->_lang['ordinal: 30'] = '30th';
883 | $this->_lang['ordinal: 31'] = '31st';
884 | $this->_lang['ordinal: 32'] = '32nd';
885 | $this->_lang['ordinal: 33'] = '33rd';
886 | $this->_lang['ordinal: 34'] = '34th';
887 | $this->_lang['ordinal: 35'] = '35th';
888 | $this->_lang['ordinal: 36'] = '36th';
889 | $this->_lang['ordinal: 37'] = '37th';
890 | $this->_lang['ordinal: 38'] = '38th';
891 | $this->_lang['ordinal: 39'] = '39th';
892 | $this->_lang['ordinal: 40'] = '40th';
893 | $this->_lang['ordinal: 41'] = '41st';
894 | $this->_lang['ordinal: 42'] = '42nd';
895 | $this->_lang['ordinal: 43'] = '43rd';
896 | $this->_lang['ordinal: 44'] = '44th';
897 | $this->_lang['ordinal: 45'] = '45th';
898 | $this->_lang['ordinal: 46'] = '46th';
899 | $this->_lang['ordinal: 47'] = '47th';
900 | $this->_lang['ordinal: 48'] = '48th';
901 | $this->_lang['ordinal: 49'] = '49th';
902 | $this->_lang['ordinal: 50'] = '50th';
903 | $this->_lang['ordinal: 51'] = '51st';
904 | $this->_lang['ordinal: 52'] = '52nd';
905 | $this->_lang['ordinal: 53'] = '53rd';
906 | $this->_lang['ordinal: 54'] = '54th';
907 | $this->_lang['ordinal: 55'] = '55th';
908 | $this->_lang['ordinal: 56'] = '56th';
909 | $this->_lang['ordinal: 57'] = '57th';
910 | $this->_lang['ordinal: 58'] = '58th';
911 | $this->_lang['ordinal: 59'] = '59th';
912 | break;
913 |
914 | case 'nl':
915 | $this->_lang['elemMin: at_the_hour'] = 'op het hele uur';
916 | $this->_lang['elemMin: after_the_hour_every_X_minute'] = 'elke minuut';
917 | $this->_lang['elemMin: after_the_hour_every_X_minute_plural'] = 'elke @1 minuten';
918 | $this->_lang['elemMin: every_consecutive_minute'] = 'elke opeenvolgende minuut';
919 | $this->_lang['elemMin: every_consecutive_minute_plural'] = 'elke opeenvolgende @1 minuten';
920 | $this->_lang['elemMin: every_minute'] = 'elke minuut';
921 | $this->_lang['elemMin: between_X_and_Y'] = 'van de @1 tot en met de @2';
922 | $this->_lang['elemMin: at_X:Y'] = 'Om @1:@2';
923 | $this->_lang['elemHour: past_X:00'] = 'na @1:00';
924 | $this->_lang['elemHour: between_X:00_and_Y:59'] = 'tussen @1:00 en @2:59';
925 | $this->_lang['elemHour: in_the_60_minutes_past_'] = 'in de 60 minuten na elk opeenvolgend uur';
926 | $this->_lang['elemHour: in_the_60_minutes_past__plural'] = 'in de 60 minuten na elke opeenvolgende @1 uren';
927 | $this->_lang['elemHour: past_every_consecutive_'] = 'na elk opeenvolgend uur';
928 | $this->_lang['elemHour: past_every_consecutive__plural'] = 'na elke opeenvolgende @1 uren';
929 | $this->_lang['elemHour: past_every_hour'] = 'na elk uur';
930 | $this->_lang['elemDOM: the_X'] = 'de @1';
931 | $this->_lang['elemDOM: every_consecutive_day'] = 'elke opeenvolgende dag';
932 | $this->_lang['elemDOM: every_consecutive_day_plural'] = 'elke opeenvolgende @1 dagen';
933 | $this->_lang['elemDOM: on_every_day'] = 'op elke dag';
934 | $this->_lang['elemDOM: between_the_Xth_and_Yth'] = 'tussen de @1 en de @2';
935 | $this->_lang['elemDOM: on_the_X'] = 'op de @1';
936 | $this->_lang['elemDOM: on_X'] = 'op @1';
937 | $this->_lang['elemMonth: every_X'] = 'elke @1';
938 | $this->_lang['elemMonth: every_consecutive_month'] = 'elke opeenvolgende maand';
939 | $this->_lang['elemMonth: every_consecutive_month_plural'] = 'elke opeenvolgende @1 maanden';
940 | $this->_lang['elemMonth: between_X_and_Y'] = 'van @1 tot @2';
941 | $this->_lang['elemMonth: of_every_month'] = 'van elke maand';
942 | $this->_lang['elemMonth: during_every_X'] = 'tijdens elke @1';
943 | $this->_lang['elemMonth: during_X'] = 'tijdens @1';
944 | $this->_lang['elemYear: in_X'] = 'in @1';
945 | $this->_lang['elemYear: every_consecutive_year'] = 'elk opeenvolgend jaar';
946 | $this->_lang['elemYear: every_consecutive_year_plural'] = 'elke opeenvolgende @1 jaren';
947 | $this->_lang['elemYear: from_X_through_Y'] = 'van @1 tot en met @2';
948 | $this->_lang['elemDOW: on_every_day'] = 'op elke dag';
949 | $this->_lang['elemDOW: on_X'] = 'op @1';
950 | $this->_lang['elemDOW: but_only_on_X'] = 'maar alleen als het plaatsvindt op @1';
951 | $this->_lang['separator_and'] = 'en';
952 | $this->_lang['separator_of'] = 'of';
953 | $this->_lang['day: 0_plural'] = 'zondagen';
954 | $this->_lang['day: 1_plural'] = 'maandagen';
955 | $this->_lang['day: 2_plural'] = 'dinsdagen';
956 | $this->_lang['day: 3_plural'] = 'woensdagen';
957 | $this->_lang['day: 4_plural'] = 'donderdagen';
958 | $this->_lang['day: 5_plural'] = 'vrijdagen';
959 | $this->_lang['day: 6_plural'] = 'zaterdagen';
960 | $this->_lang['month: 1'] = 'januari';
961 | $this->_lang['month: 2'] = 'februari';
962 | $this->_lang['month: 3'] = 'maart';
963 | $this->_lang['month: 4'] = 'april';
964 | $this->_lang['month: 5'] = 'mei';
965 | $this->_lang['month: 6'] = 'juni';
966 | $this->_lang['month: 7'] = 'juli';
967 | $this->_lang['month: 8'] = 'augustus';
968 | $this->_lang['month: 9'] = 'september';
969 | $this->_lang['month: 10'] = 'october';
970 | $this->_lang['month: 11'] = 'november';
971 | $this->_lang['month: 12'] = 'december';
972 | $this->_lang['ordinal: 1'] = '1e';
973 | $this->_lang['ordinal: 2'] = '2e';
974 | $this->_lang['ordinal: 3'] = '3e';
975 | $this->_lang['ordinal: 4'] = '4e';
976 | $this->_lang['ordinal: 5'] = '5e';
977 | $this->_lang['ordinal: 6'] = '6e';
978 | $this->_lang['ordinal: 7'] = '7e';
979 | $this->_lang['ordinal: 8'] = '8e';
980 | $this->_lang['ordinal: 9'] = '9e';
981 | $this->_lang['ordinal: 10'] = '10e';
982 | $this->_lang['ordinal: 11'] = '11e';
983 | $this->_lang['ordinal: 12'] = '12e';
984 | $this->_lang['ordinal: 13'] = '13e';
985 | $this->_lang['ordinal: 14'] = '14e';
986 | $this->_lang['ordinal: 15'] = '15e';
987 | $this->_lang['ordinal: 16'] = '16e';
988 | $this->_lang['ordinal: 17'] = '17e';
989 | $this->_lang['ordinal: 18'] = '18e';
990 | $this->_lang['ordinal: 19'] = '19e';
991 | $this->_lang['ordinal: 20'] = '20e';
992 | $this->_lang['ordinal: 21'] = '21e';
993 | $this->_lang['ordinal: 22'] = '22e';
994 | $this->_lang['ordinal: 23'] = '23e';
995 | $this->_lang['ordinal: 24'] = '24e';
996 | $this->_lang['ordinal: 25'] = '25e';
997 | $this->_lang['ordinal: 26'] = '26e';
998 | $this->_lang['ordinal: 27'] = '27e';
999 | $this->_lang['ordinal: 28'] = '28e';
1000 | $this->_lang['ordinal: 29'] = '29e';
1001 | $this->_lang['ordinal: 30'] = '30e';
1002 | $this->_lang['ordinal: 31'] = '31e';
1003 | $this->_lang['ordinal: 32'] = '32e';
1004 | $this->_lang['ordinal: 33'] = '33e';
1005 | $this->_lang['ordinal: 34'] = '34e';
1006 | $this->_lang['ordinal: 35'] = '35e';
1007 | $this->_lang['ordinal: 36'] = '36e';
1008 | $this->_lang['ordinal: 37'] = '37e';
1009 | $this->_lang['ordinal: 38'] = '38e';
1010 | $this->_lang['ordinal: 39'] = '39e';
1011 | $this->_lang['ordinal: 40'] = '40e';
1012 | $this->_lang['ordinal: 41'] = '41e';
1013 | $this->_lang['ordinal: 42'] = '42e';
1014 | $this->_lang['ordinal: 43'] = '43e';
1015 | $this->_lang['ordinal: 44'] = '44e';
1016 | $this->_lang['ordinal: 45'] = '45e';
1017 | $this->_lang['ordinal: 46'] = '46e';
1018 | $this->_lang['ordinal: 47'] = '47e';
1019 | $this->_lang['ordinal: 48'] = '48e';
1020 | $this->_lang['ordinal: 49'] = '49e';
1021 | $this->_lang['ordinal: 50'] = '50e';
1022 | $this->_lang['ordinal: 51'] = '51e';
1023 | $this->_lang['ordinal: 52'] = '52e';
1024 | $this->_lang['ordinal: 53'] = '53e';
1025 | $this->_lang['ordinal: 54'] = '54e';
1026 | $this->_lang['ordinal: 55'] = '55e';
1027 | $this->_lang['ordinal: 56'] = '56e';
1028 | $this->_lang['ordinal: 57'] = '57e';
1029 | $this->_lang['ordinal: 58'] = '58e';
1030 | $this->_lang['ordinal: 59'] = '59e';
1031 | break;
1032 | }
1033 | }
1034 |
1035 | final private function natlangPad2($number)
1036 | {
1037 | return (strlen($number) == 1 ? '0' : '').$number;
1038 | }
1039 |
1040 | final private function natlangApply($id, $p1 = false, $p2 = false, $p3 = false, $p4 = false, $p5 = false, $p6 = false)
1041 | {
1042 | $txt = $this->_lang[$id];
1043 |
1044 | if ($p1 !== false) {
1045 | $txt = str_replace('@1', $p1, $txt);
1046 | }
1047 | if ($p2 !== false) {
1048 | $txt = str_replace('@2', $p2, $txt);
1049 | }
1050 | if ($p3 !== false) {
1051 | $txt = str_replace('@3', $p3, $txt);
1052 | }
1053 | if ($p4 !== false) {
1054 | $txt = str_replace('@4', $p4, $txt);
1055 | }
1056 | if ($p5 !== false) {
1057 | $txt = str_replace('@5', $p5, $txt);
1058 | }
1059 | if ($p6 !== false) {
1060 | $txt = str_replace('@6', $p6, $txt);
1061 | }
1062 |
1063 | return $txt;
1064 | }
1065 |
1066 | //
1067 | // Function: natlangRange
1068 | //
1069 | // Description: Converts a range into natural language
1070 | //
1071 | // Parameters:
1072 | //
1073 | // Result:
1074 | //
1075 |
1076 | final private function natlangRange($spec, $entryFunction, $p1 = false)
1077 | {
1078 | $arrIntervals = [];
1079 | foreach ($spec['elements'] as $elem) {
1080 | $arrIntervals[] = call_user_func($entryFunction, $elem, $p1);
1081 | }
1082 |
1083 | $txt = '';
1084 | for ($index = 0; $index < count($arrIntervals); $index++) {
1085 | $txt .= ($index == 0 ? '' : ($index == (count($arrIntervals) - 1) ? ' '.$this->natlangApply('separator_and').' ' : ', ')).$arrIntervals[$index];
1086 | }
1087 |
1088 | return $txt;
1089 | }
1090 |
1091 | //
1092 | // Function: natlangElementMinute
1093 | //
1094 | // Description: Converts an entry from the minute specification to natural language.
1095 | //
1096 |
1097 | final private function natlangElementMinute($elem)
1098 | {
1099 | if (!$elem['hasInterval']) {
1100 | if ($elem['number1'] == 0) {
1101 | return $this->natlangApply('elemMin: at_the_hour');
1102 | } else {
1103 | return $this->natlangApply('elemMin: after_the_hour_every_X_minute'.($elem['number1'] == 1 ? '' : '_plural'), $elem['number1']);
1104 | }
1105 | }
1106 |
1107 | $txt = $this->natlangApply('elemMin: every_consecutive_minute'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1108 | if (($elem['number1'] != $this->_cronMinutes['rangeMin']) || ($elem['number2'] != $this->_cronMinutes['rangeMax'])) {
1109 | $txt .= ' ('.$this->natlangApply('elemMin: between_X_and_Y', $this->natlangApply('ordinal: '.$elem['number1']), $this->natlangApply('ordinal: '.$elem['number2'])).')';
1110 | }
1111 |
1112 | return $txt;
1113 | }
1114 |
1115 | //
1116 | // Function: natlangElementHour
1117 | //
1118 | // Description: Converts an entry from the hour specification to natural language.
1119 | //
1120 |
1121 | final private function natlangElementHour($elem, $asBetween)
1122 | {
1123 | if (!$elem['hasInterval']) {
1124 | if ($asBetween) {
1125 | return $this->natlangApply('elemHour: between_X:00_and_Y:59', $this->natlangPad2($elem['number1']), $this->natlangPad2($elem['number1']));
1126 | } else {
1127 | return $this->natlangApply('elemHour: past_X:00', $this->natlangPad2($elem['number1']));
1128 | }
1129 | }
1130 |
1131 | if ($asBetween) {
1132 | $txt = $this->natlangApply('elemHour: in_the_60_minutes_past_'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1133 | } else {
1134 | $txt = $this->natlangApply('elemHour: past_every_consecutive_'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1135 | }
1136 |
1137 | if (($elem['number1'] != $this->_cronHours['rangeMin']) || ($elem['number2'] != $this->_cronHours['rangeMax'])) {
1138 | $txt .= ' ('.$this->natlangApply('elemHour: between_X:00_and_Y:59', $elem['number1'], $elem['number2']).')';
1139 | }
1140 |
1141 | return $txt;
1142 | }
1143 |
1144 | //
1145 | // Function: natlangElementDayOfMonth
1146 | //
1147 | // Description: Converts an entry from the day of month specification to natural language.
1148 | //
1149 |
1150 | final private function natlangElementDayOfMonth($elem)
1151 | {
1152 | if (!$elem['hasInterval']) {
1153 | return $this->natlangApply('elemDOM: the_X', $this->natlangApply('ordinal: '.$elem['number1']));
1154 | }
1155 |
1156 | $txt = $this->natlangApply('elemDOM: every_consecutive_day'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1157 | if (($elem['number1'] != $this->_cronHours['rangeMin']) || ($elem['number2'] != $this->_cronHours['rangeMax'])) {
1158 | $txt .= ' ('.$this->natlangApply('elemDOM: between_the_Xth_and_Yth', $this->natlangApply('ordinal: '.$elem['number1']), $this->natlangApply('ordinal: '.$elem['number2'])).')';
1159 | }
1160 |
1161 | return $txt;
1162 | }
1163 |
1164 | //
1165 | // Function: natlangElementDayOfMonth
1166 | //
1167 | // Description: Converts an entry from the month specification to natural language.
1168 | //
1169 |
1170 | final private function natlangElementMonth($elem)
1171 | {
1172 | if (!$elem['hasInterval']) {
1173 | return $this->natlangApply('elemMonth: every_X', $this->natlangApply('month: '.$elem['number1']));
1174 | }
1175 |
1176 | $txt = $this->natlangApply('elemMonth: every_consecutive_month'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1177 | if (($elem['number1'] != $this->_cronMonths['rangeMin']) || ($elem['number2'] != $this->_cronMonths['rangeMax'])) {
1178 | $txt .= ' ('.$this->natlangApply('elemMonth: between_X_and_Y', $this->natlangApply('month: '.$elem['number1']), $this->natlangApply('month: '.$elem['number2'])).')';
1179 | }
1180 |
1181 | return $txt;
1182 | }
1183 |
1184 | //
1185 | // Function: natlangElementYear
1186 | //
1187 | // Description: Converts an entry from the year specification to natural language.
1188 | //
1189 |
1190 | final private function natlangElementYear($elem)
1191 | {
1192 | if (!$elem['hasInterval']) {
1193 | return $elem['number1'];
1194 | }
1195 |
1196 | $txt = $this->natlangApply('elemYear: every_consecutive_year'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1197 | if (($elem['number1'] != $this->_cronMonths['rangeMin']) || ($elem['number2'] != $this->_cronMonths['rangeMax'])) {
1198 | $txt .= ' ('.$this->natlangApply('elemYear: from_X_through_Y', $elem['number1'], $elem['number2']).')';
1199 | }
1200 |
1201 | return $txt;
1202 | }
1203 |
1204 | //
1205 | // Function: asNaturalLanguage
1206 | //
1207 | // Description: Returns the current cron specification in natural language.
1208 | //
1209 | // Parameters: None
1210 | //
1211 | // Result: A string containing a natural language text.
1212 | //
1213 |
1214 | final public function asNaturalLanguage()
1215 | {
1216 | $switchForceDateExplaination = false;
1217 | $switchDaysOfWeekAreExcluding = true;
1218 |
1219 | // Generate Time String
1220 |
1221 | $txtMinutes = [];
1222 | $txtMinutes[0] = $this->natlangApply('elemMin: every_minute');
1223 | $txtMinutes[1] = $this->natlangElementMinute($this->_cronMinutes['elements'][0]);
1224 | $txtMinutes[2] = $this->natlangRange($this->_cronMinutes, [$this, 'natlangElementMinute']);
1225 |
1226 | $txtHours = [];
1227 | $txtHours[0] = $this->natlangApply('elemHour: past_every_hour');
1228 | $txtHours[1] = [];
1229 | $txtHours[1]['between'] = $this->natlangRange($this->_cronHours, [$this, 'natlangElementHour'], true);
1230 | $txtHours[1]['past'] = $this->natlangRange($this->_cronHours, [$this, 'natlangElementHour'], false);
1231 | $txtHours[2] = [];
1232 | $txtHours[2]['between'] = $this->natlangRange($this->_cronHours, [$this, 'natlangElementHour'], true);
1233 | $txtHours[2]['past'] = $this->natlangRange($this->_cronHours, [$this, 'natlangElementHour'], false);
1234 |
1235 | $classMinutes = $this->getClass($this->_cronMinutes);
1236 | $classHours = $this->getClass($this->_cronHours);
1237 |
1238 | switch ($classMinutes.$classHours) {
1239 |
1240 | // Special case: Unspecified date + Unspecified month
1241 | //
1242 | // Rule: The language for unspecified fields is omitted if a more detailed field has already been explained.
1243 | //
1244 | // The minutes field always yields an explaination, at the very least in the form of 'every minute'. This rule states that if the
1245 | // hour is not specified, it can be omitted because 'every minute' is already sufficiently clear.
1246 | //
1247 |
1248 | case '00':
1249 | $txtTime = $txtMinutes[0];
1250 | break;
1251 |
1252 | // Special case: Fixed minutes and fixed hours
1253 | //
1254 | // The default writing would be something like 'every 20 minutes past 04:00', but the more common phrasing would be: At 04:20.
1255 | //
1256 | // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1257 | //
1258 |
1259 | case '11':
1260 | $txtTime = $this->natlangApply('elemMin: at_X:Y', $this->natlangPad2($this->_cronHours['elements'][0]['number1']), $this->natlangPad2($this->_cronMinutes['elements'][0]['number1']));
1261 | $switchForceDateExplaination = true;
1262 | break;
1263 |
1264 | // Special case: Between :00 and :59
1265 | //
1266 | // If hours are specified, but minutes are not, then the minutes string will yield something like 'every minute'. We must the
1267 | // differentiate the hour specification because the minutes specification does not relate to all minutes past the hour, but only to
1268 | // those minutes between :00 and :59
1269 | //
1270 | // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1271 | //
1272 |
1273 | case '01':
1274 | case '02':
1275 | $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours]['between'];
1276 | $switchForceDateExplaination = true;
1277 | break;
1278 |
1279 | // Special case: Past the hour
1280 | //
1281 | // If minutes are specified and hours are specified, then the specification of minutes is always limited to a maximum of 60 minutes
1282 | // and always applies to the minutes 'past the hour'.
1283 | //
1284 | // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1285 | //
1286 |
1287 | case '12':
1288 | case '22':
1289 | case '21':
1290 | $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours]['past'];
1291 | $switchForceDateExplaination = true;
1292 | break;
1293 |
1294 | default:
1295 | $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours];
1296 | break;
1297 | }
1298 |
1299 | // Generate Date String
1300 |
1301 | $txtDaysOfMonth = [];
1302 | $txtDaysOfMonth[0] = '';
1303 | $txtDaysOfMonth[1] = $this->natlangApply('elemDOM: on_the_X', $this->natlangApply('ordinal: '.$this->_cronDaysOfMonth['elements'][0]['number1']));
1304 | $txtDaysOfMonth[2] = $this->natlangApply('elemDOM: on_X', $this->natlangRange($this->_cronDaysOfMonth, [$this, 'natlangElementDayOfMonth']));
1305 |
1306 | $txtMonths = [];
1307 | $txtMonths[0] = $this->natlangApply('elemMonth: of_every_month');
1308 | $txtMonths[1] = $this->natlangApply('elemMonth: during_every_X', $this->natlangApply('month: '.$this->_cronMonths['elements'][0]['number1']));
1309 | $txtMonths[2] = $this->natlangApply('elemMonth: during_X', $this->natlangRange($this->_cronMonths, [$this, 'natlangElementMonth']));
1310 |
1311 | $classDaysOfMonth = $this->getClass($this->_cronDaysOfMonth);
1312 | $classMonths = $this->getClass($this->_cronMonths);
1313 |
1314 | if ($classDaysOfMonth == '0') {
1315 | $switchDaysOfWeekAreExcluding = false;
1316 | }
1317 |
1318 | switch ($classDaysOfMonth.$classMonths) {
1319 |
1320 | // Special case: Unspecified date + Unspecified month
1321 | //
1322 | // Rule: The language for unspecified fields is omitted if a more detailed field has already been explained.
1323 | //
1324 | // The time fields always yield an explaination, at the very least in the form of 'every minute'. This rule states that if the date
1325 | // is not specified, it can be omitted because 'every minute' is already sufficiently clear.
1326 | //
1327 | // There are some time specifications that do not contain an 'every' reference, but reference a specific time of day. In those cases
1328 | // the date explaination is enforced.
1329 | //
1330 |
1331 | case '00':
1332 | $txtDate = '';
1333 | break;
1334 |
1335 | default:
1336 | $txtDate = ' '.$txtDaysOfMonth[$classDaysOfMonth].' '.$txtMonths[$classMonths];
1337 | break;
1338 | }
1339 |
1340 | // Generate Year String
1341 |
1342 | if ($this->_cronYears) {
1343 | $txtYears = [];
1344 | $txtYears[0] = '';
1345 | $txtYears[1] = ' '.$this->natlangApply('elemYear: in_X', $this->_cronYears['elements'][0]['number1']);
1346 | $txtYears[2] = ' '.$this->natlangApply('elemYear: in_X', $this->natlangRange($this->_cronYears, [$this, 'natlangElementYear']));
1347 |
1348 | $classYears = $this->getClass($this->_cronYears);
1349 | $txtYear = $txtYears[$classYears];
1350 | }
1351 |
1352 | // Generate DaysOfWeek String
1353 |
1354 | $collectDays = 0;
1355 | foreach ($this->_cronDaysOfWeek['elements'] as $elem) {
1356 | if ($elem['hasInterval']) {
1357 | for ($x = $elem['number1']; $x <= $elem['number2']; $x += $elem['interval']) {
1358 | $collectDays |= pow(2, $x);
1359 | }
1360 | } else {
1361 | $collectDays |= pow(2, $elem['number1']);
1362 | }
1363 | }
1364 | if ($collectDays == 127) { // * all days
1365 | if (!$switchDaysOfWeekAreExcluding) {
1366 | $txtDays = ' '.$this->natlangApply('elemDOM: on_every_day');
1367 | } else {
1368 | $txtDays = '';
1369 | }
1370 | } else {
1371 | $arrDays = [];
1372 | for ($x = 0; $x <= 6; $x++) {
1373 | if ($collectDays & pow(2, $x)) {
1374 | $arrDays[] = $x;
1375 | }
1376 | }
1377 | $txtDays = '';
1378 | for ($index = 0; $index < count($arrDays); $index++) {
1379 | $txtDays .= ($index == 0 ? '' : ($index == (count($arrDays) - 1) ? ' '.$this->natlangApply($switchDaysOfWeekAreExcluding ? 'separator_or' : 'separator_and').' ' : ', ')).$this->natlangApply('day: '.$arrDays[$index].'_plural');
1380 | }
1381 | if ($switchDaysOfWeekAreExcluding) {
1382 | $txtDays = ' '.$this->natlangApply('elemDOW: but_only_on_X', $txtDays);
1383 | } else {
1384 | $txtDays = ' '.$this->natlangApply('elemDOW: on_X', $txtDays);
1385 | }
1386 | }
1387 |
1388 | $txtResult = ucfirst($txtTime).$txtDate.$txtDays;
1389 |
1390 | if (isset($txtYear)) {
1391 | if ($switchDaysOfWeekAreExcluding) {
1392 | $txtResult = ucfirst($txtTime).$txtDate.$txtYear.$txtDays;
1393 | } else {
1394 | $txtResult = ucfirst($txtTime).$txtDate.$txtDays.$txtYear;
1395 | }
1396 | }
1397 |
1398 | return $txtResult.'.';
1399 | }
1400 | }
1401 |
--------------------------------------------------------------------------------
/src/Scheduling.php:
--------------------------------------------------------------------------------
1 | make('Illuminate\Contracts\Console\Kernel');
25 |
26 | return app()->make('Illuminate\Console\Scheduling\Schedule')->events();
27 | }
28 |
29 | /**
30 | * Get all formatted tasks.
31 | *
32 | * @throws \Exception
33 | *
34 | * @return array
35 | */
36 | public function getTasks()
37 | {
38 | $tasks = [];
39 |
40 | foreach ($this->getKernelEvents() as $event) {
41 | $tasks[] = [
42 | 'task' => $this->formatTask($event),
43 | 'expression' => $event->expression,
44 | 'nextRunDate' => $event->nextRunDate()->format('Y-m-d H:i:s'),
45 | 'description' => $event->description,
46 | 'readable' => CronSchedule::fromCronString($event->expression)->asNaturalLanguage(),
47 | ];
48 | }
49 |
50 | return $tasks;
51 | }
52 |
53 | /**
54 | * Format a giving task.
55 | *
56 | * @param $event
57 | *
58 | * @return array
59 | */
60 | protected function formatTask($event)
61 | {
62 | if ($event instanceof CallbackEvent) {
63 | return [
64 | 'type' => 'closure',
65 | 'name' => 'Closure',
66 | ];
67 | }
68 |
69 | if (Str::contains($event->command, '\'artisan\'')) {
70 | $exploded = explode(' ', $event->command);
71 |
72 | return [
73 | 'type' => 'artisan',
74 | 'name' => 'artisan '.implode(' ', array_slice($exploded, 2)),
75 | ];
76 | }
77 |
78 | if (PHP_OS_FAMILY === 'Windows' && Str::contains($event->command, '"artisan"')) {
79 | $exploded = explode(' ', $event->command);
80 |
81 | return [
82 | 'type' => 'artisan',
83 | 'name' => 'artisan '.implode(' ', array_slice($exploded, 2)),
84 | ];
85 | }
86 |
87 | return [
88 | 'type' => 'command',
89 | 'name' => $event->command,
90 | ];
91 | }
92 |
93 | /**
94 | * Run specific task.
95 | *
96 | * @param int $id
97 | *
98 | * @return string
99 | */
100 | public function runTask($id)
101 | {
102 | set_time_limit(0);
103 |
104 | /** @var \Illuminate\Console\Scheduling\Event $event */
105 | $event = $this->getKernelEvents()[$id - 1];
106 |
107 | if (PHP_OS_FAMILY === 'Windows') {
108 | $event->command = Str::of($event->command)->replace('php-cgi.exe', 'php.exe');
109 | }
110 |
111 | $event->sendOutputTo($this->getOutputTo());
112 |
113 | $event->run(app());
114 |
115 | return $this->readOutput();
116 | }
117 |
118 | /**
119 | * @return string
120 | */
121 | protected function getOutputTo()
122 | {
123 | if (!$this->sendOutputTo) {
124 | $this->sendOutputTo = storage_path('app/task-schedule.output');
125 | }
126 |
127 | return $this->sendOutputTo;
128 | }
129 |
130 | /**
131 | * Read output info from output file.
132 | *
133 | * @return string
134 | */
135 | protected function readOutput()
136 | {
137 | return file_get_contents($this->getOutputTo());
138 | }
139 |
140 | /**
141 | * Bootstrap this package.
142 | *
143 | * @return void
144 | */
145 | public static function boot()
146 | {
147 | static::registerRoutes();
148 |
149 | Admin::extend('scheduling', __CLASS__);
150 | }
151 |
152 | /**
153 | * Register routes for laravel-admin.
154 | *
155 | * @return void
156 | */
157 | protected static function registerRoutes()
158 | {
159 | parent::routes(function ($router) {
160 | /* @var \Illuminate\Routing\Router $router */
161 | $router->get('scheduling', 'Encore\Admin\Scheduling\SchedulingController@index')->name('scheduling-index');
162 | $router->post('scheduling/run', 'Encore\Admin\Scheduling\SchedulingController@runEvent')->name('scheduling-run');
163 | });
164 | }
165 |
166 | /**
167 | * {@inheritdoc}
168 | */
169 | public static function import()
170 | {
171 | parent::createMenu('Scheduling', 'scheduling', 'fa-clock-o');
172 |
173 | parent::createPermission('Scheduling', 'ext.scheduling', 'scheduling*');
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/SchedulingController.php:
--------------------------------------------------------------------------------
1 | header('Task scheduling');
20 |
21 | $scheduling = new Scheduling();
22 |
23 | $content->body(view('laravel-admin-scheduling::index', [
24 | 'events' => $scheduling->getTasks(),
25 | ]));
26 | });
27 | }
28 |
29 | /**
30 | * @param Request $request
31 | *
32 | * @return array
33 | */
34 | public function runEvent(Request $request)
35 | {
36 | $scheduling = new Scheduling();
37 |
38 | try {
39 | $output = $scheduling->runTask($request->get('id'));
40 |
41 | return [
42 | 'status' => true,
43 | 'message' => 'success',
44 | 'data' => $output,
45 | ];
46 | } catch (\Exception $e) {
47 | return [
48 | 'status' => false,
49 | 'message' => 'failed',
50 | 'data' => $e->getMessage(),
51 | ];
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/SchedulingServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../resources/views', 'laravel-admin-scheduling');
15 |
16 | Scheduling::boot();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------