├── .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 | [![StyleCI](https://styleci.io/repos/99676857/shield?branch=master)](https://styleci.io/repos/99676857) 5 | [![Packagist](https://img.shields.io/packagist/l/laravel-admin-ext/scheduling.svg?maxAge=2592000)](https://packagist.org/packages/laravel-admin-ext/scheduling) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/laravel-admin-ext/scheduling.svg?style=flat-square)](https://packagist.org/packages/laravel-admin-ext/scheduling) 7 | [![Pull request welcome](https://img.shields.io/badge/pr-welcome-green.svg?style=flat-square)]() 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 | ![wx20170810-101048](https://user-images.githubusercontent.com/1479100/29151552-8affc0b2-7db4-11e7-932a-a10d8a42ec50.png) 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 | 41 | 42 | 43 | 44 | 45 | 46 | @foreach($events as $index => $event) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | @endforeach 56 | 57 |
#TaskRun atNext run timeDescriptionRun
{{ $index+1 }}.{{ $event['task']['name'] }}{{ $event['expression'] }} {{ $event['readable'] }}{{ $event['nextRunDate'] }}{{ $event['description'] }}Run
58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 | 66 |

Output

67 |
68 | 69 |
70 |

71 |     
72 | 73 |
-------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------