Issues (30)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/CronSchedule.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Mistletoe;
3
4
// from https://gist.github.com/m4tthumphrey/8236756#file-cronschedule-php
5
/*
6
 * Plugin:        StreamlineFoundation
7
 *
8
 * Class:        Schedule
9
 *
10
 * Description:    Provides scheduling mechanics including creating a schedule, testing if a specific moment is part of the schedule, moving back
11
 *                and forth between scheduled moments in time and translating the created schedule back to a human readable form.
12
 *
13
 * Usage:        ::fromCronString() creates a new Schedule class and requires a string in the cron ('* * * * *', $language) format.
14
 *
15
 *                ->next(<datetime>) returns the first scheduled datetime after <datetime> in array format.
16
 *                ->nextAsString(<datetime>) does the same with an ISO string as the result.
17
 *                ->nextAsTime(<datetime>) does the same with a UNIX timestamp as the result.
18
 *
19
 *                ->previous(<datetime>) returns the first scheduled datetime before <datetime> in array format.
20
 *                ->previousAsString(<datetime>) does the same with an ISO string as the result.
21
 *                ->previousAsTime(<datetime>) 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, <datetime> 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 ([email protected], [email protected])
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 <http://www.gnu.org/licenses/>.
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            = array();
44
    private    $_hours                = array();
45
    private    $_daysOfMonth        = array();
46
    private    $_months            = array();
47
    private    $_daysOfWeek        = array();
48
    private    $_years                = array();
49
50
    // The original cron specification in compiled form.
51
    private $_cronMinutes        = array();
52
    private $_cronHours            = array();
53
    private $_cronDaysOfMonth    = array();
54
    private $_cronMonths        = array();
55
    private $_cronDaysOfWeek    = array();
56
    private $_cronYears            = array();
57
58
    // The language table
59
    private $_lang                = FALSE;
60
61
62
    /**
63
     * 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
64
     * must assume vulnerability.
65
     */
66
    protected $RANGE_YEARS_MIN    = 1970;    // Must match date range supported by date(). See also: http://en.wikipedia.org/wiki/Year_2038_problem
67
    protected $RANGE_YEARS_MAX    = 2037;    // Must match date range supported by date(). See also: http://en.wikipedia.org/wiki/Year_2038_problem
68
69
70
    /**
71
     * Function:    __construct
72
     *
73
     * Description:    Performs only base initialization, including language initialization.
74
     *
75
     * Parameters:    $language            The languagecode of the chosen language.
76
     */
77
    public function __construct($language = 'en')
78
    {
79
        $this->initLang($language);
80
    }
81
82
    //
83
    // Function:    fromCronString
84
    //
85
    // Description:    Creates a new Schedule object based on a Cron specification.
86
    //
87
    // Parameters:    $cronSpec            A string containing a cron specification.
88
    //                $language            The language to use to create a natural language representation of the string
89
    //
90
    // Result:        A new Schedule object. An \Exception is thrown if the specification is invalid.
91
    //
92
93
    final public static function fromCronString($cronSpec = '* * * * * *', $language = 'en')
94
    {
95
96
        // Split input liberal. Single or multiple Spaces, Tabs and Newlines are all allowed as separators.
97
        if(count($elements = preg_split('/\s+/', $cronSpec)) < 5)
98
            throw new Exception('Invalid specification.');
99
100
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101
        // Named ranges in cron entries
102
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
103
        $arrMonths        = array('JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6, 'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' => 10, 'NOV' => 11, 'DEC' => 12);
104
        $arrDaysOfWeek    = array('SUN' => 0, 'MON' => 1, 'TUE' => 2, 'WED' => 3, 'THU' => 4, 'FRI' => 5, 'SAT' => 6);
105
106
107
        // Translate the cron specification into arrays that hold specifications of the actual dates
108
        $newCron = new CronSchedule($language);
109
        $newCron->_cronMinutes        = $newCron->cronInterpret($elements[0],                               0,                           59, array(),            'minutes');
110
        $newCron->_cronHours        = $newCron->cronInterpret($elements[1],                               0,                           23, array(),            'hours');
111
        $newCron->_cronDaysOfMonth    = $newCron->cronInterpret($elements[2],                            1,                           31, array(),            'daysOfMonth');
112
        $newCron->_cronMonths        = $newCron->cronInterpret($elements[3],                            1,                           12, $arrMonths,        'months');
113
        $newCron->_cronDaysOfWeek    = $newCron->cronInterpret($elements[4],                            0,                            6, $arrDaysOfWeek,    'daysOfWeek');
114
115
116
        $newCron->_minutes            = $newCron->cronCreateItems($newCron->_cronMinutes);
117
        $newCron->_hours            = $newCron->cronCreateItems($newCron->_cronHours);
118
        $newCron->_daysOfMonth        = $newCron->cronCreateItems($newCron->_cronDaysOfMonth);
119
        $newCron->_months            = $newCron->cronCreateItems($newCron->_cronMonths);
120
        $newCron->_daysOfWeek        = $newCron->cronCreateItems($newCron->_cronDaysOfWeek);
121
122
        if (isset($elements[5])) {
123
            $newCron->_cronYears        = $newCron->cronInterpret($elements[5], $newCron->RANGE_YEARS_MIN, $newCron->RANGE_YEARS_MAX, array(),            'years');
124
            $newCron->_years            = $newCron->cronCreateItems($newCron->_cronYears);
125
        }
126
127
        return $newCron;
128
    }
129
130
131
    /*
132
     * Function:    cronInterpret
133
     *
134
     * Description:    Interprets a single field from a cron specification. Throws an \Exception if the specification is in some way invalid.
135
     *
136
     * Parameters:    $specification        The actual text from the spefication, such as 12-38/3
137
     *                $rangeMin            The lowest value for specification.
138
     *                $rangeMax            The highest value for specification
139
     *                $namesItems            A key/value pair where value is a value between $rangeMin and $rangeMax and key is the name for that value.
140
     *                $errorName            The name of the category to use in case of an error.
141
     *
142
     * Result:        An array with entries, each of which is an array with the following fields:
143
     *                'number1'            The first number of the range or the number specified
144
     *                'number2'            The second number of the range if a range is specified
145
     *                'hasInterval'        TRUE if a range is specified. FALSE otherwise
146
     *                'interval'            The interval if a range is specified.
147
     */
148
149
    /**
150
     * @param integer $rangeMin
151
     * @param integer $rangeMax
152
     * @param string $errorName
153
     */
154
    final private function cronInterpret($specification, $rangeMin, $rangeMax, $namedItems, $errorName)
155
    {
156
157
        if((!is_string($specification)) && (!(is_int($specification))))
158
            throw new Exception('Invalid specification.');
159
160
161
        // Multiple values, separated by comma
162
        $specs = array();
163
        $specs['rangeMin'] = $rangeMin;
164
        $specs['rangeMax'] = $rangeMax;
165
        $specs['elements'] = array();
166
        $arrSegments = explode(',', $specification);
167
        foreach($arrSegments as $segment)
168
        {
169
            $hasRange        = (($posRange        = strpos($segment, '-')) !== FALSE);
170
            $hasInterval    = (($posIncrement    = strpos($segment, '/')) !== FALSE);
171
172
            /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
173
            // Check: Increment without range is invalid
174
175
            //if(!$hasRange && $hasInterval)                                                throw new \Exception("Invalid Range ($errorName).");
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
177
178
            // Check: Increment must be final specification
179
180
            if($hasRange && $hasInterval)
181
                if($posIncrement < $posRange)                                            throw new \Exception("Invalid order ($errorName).");
182
183
184
            // GetSegments
185
186
            $segmentNumber1        = $segment;
187
            $segmentNumber2        = '';
188
            $segmentIncrement    = '';
189
            $intIncrement        = 1;
190
            if($hasInterval)
191
            {
192
                $segmentNumber1 = substr($segment, 0, $posIncrement);
193
                $segmentIncrement = substr($segment, $posIncrement + 1);
194
            }
195
196
            if($hasRange)
197
            {
198
                $segmentNumber2 = substr($segmentNumber1, $posRange + 1);
199
                $segmentNumber1 = substr($segmentNumber1, 0, $posRange);
200
            }
201
202
203
            // Get and validate first value in range
204
205
            if($segmentNumber1 == '*')
206
            {
207
                $intNumber1 = $rangeMin;
208
                $intNumber2 = $rangeMax;
209
                $hasRange    = TRUE;
210
            }
211
            else
212
            {
213
                if(array_key_exists(strtoupper($segmentNumber1), $namedItems)) $segmentNumber1 = $namedItems[strtoupper($segmentNumber1)];
214
                if(((string) ($intNumber1 = (int) $segmentNumber1)) != $segmentNumber1)        throw new \Exception("Invalid symbol ($errorName).");
215
                if(($intNumber1 < $rangeMin) || ($intNumber1 > $rangeMax))                    throw new \Exception("Out of bounds ($errorName).");
216
217
218
                // Get and validate second value in range
219
220
                if($hasRange)
221
                {
222
                    if(array_key_exists(strtoupper($segmentNumber2), $namedItems)) $segmentNumber2 = $namedItems[strtoupper($segmentNumber2)];
223
                    if(((string) ($intNumber2 = (int) $segmentNumber2)) != $segmentNumber2)    throw new \Exception("Invalid symbol ($errorName).");
224
                    if(($intNumber2 < $rangeMin) || ($intNumber2 > $rangeMax))                throw new \Exception("Out of bounds ($errorName).");
225
                    if($intNumber1 > $intNumber2)                                            throw new \Exception("Invalid range ($errorName).");
226
                }
227
            }
228
229
230
            // Get and validate increment
231
232
            if($hasInterval)
233
            {
234
                if(($intIncrement = (int) $segmentIncrement) != $segmentIncrement)        throw new \Exception("Invalid symbol ($errorName).");
235
                if($intIncrement < 1)                                                    throw new \Exception("Out of bounds ($errorName).");
236
            }
237
238
239
            // Apply range and increment
240
241
            $elem = array();
242
            $elem['number1'] = $intNumber1;
243
            $elem['hasInterval'] = $hasRange;
244
            if($hasRange)
245
            {
246
                $elem['number2']    = $intNumber2;
247
                $elem['interval']    = $intIncrement;
248
            }
249
            $specs['elements'][] = $elem;
250
        }
251
252
        return $specs;
253
    }
254
255
256
    //
257
    // Function:    cronCreateItems
258
    //
259
    // Description:    Uses the interpreted cron specification of a single item from a cron specification to create an array with keys that match the
260
    //                selected items.
261
    //
262
    // Parameters:    $cronInterpreted    The interpreted specification
263
    //
264
    // Result:        An array where each key identifies a matching entry. E.g. the cron specification */10 for minutes will yield an array
265
    //                [0] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
266
    //                [10] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
267
    //                [20] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
268
    //                [30] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
269
    //                [40] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270
    //                [50] => 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
271
    //
272
273
    final private function cronCreateItems($cronInterpreted)
274
    {
275
        $items = array();
276
277
        foreach($cronInterpreted['elements'] as $elem)
278
        {
279
            if(!$elem['hasInterval'])
280
                $items[$elem['number1']] = TRUE;
281
            else
282
                for($number = $elem['number1']; $number <= $elem['number2']; $number += $elem['interval'])
283
                    $items[$number] = TRUE;
284
        }
285
        ksort($items);
286
        return $items;
287
    }
288
289
290
    //
291
    // Function:    dtFromParameters
292
    //
293
    // Description:    Transforms a flexible parameter passing of a datetime specification into an internally used array.
294
    //
295
    // Parameters:    $time                If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
296
    //                                    If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
297
    //                                    If a string, interpreted as unix time.
298
    //                                    If omitted or specified FALSE, defaults to the current time.
299
    //
300
    // Result:        An array with indices 0-4 holding the actual interpreted values for $minute, $hour, $day, $month and $year.
301
    //
302
303
    final private function dtFromParameters($time = FALSE)
304
    {
305
        if($time === FALSE)
306
        {
307
            $arrTime = getDate();
308
            return array($arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']);
309
        }
310
        elseif(is_array($time))
311
            return $time;
312
        elseif(is_string($time))
313
        {
314
            $arrTime = getDate(strtotime($time));
315
            return array($arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']);
316
        }elseif(is_int($time))
317
        {
318
            $arrTime = getDate($time);
319
            return array($arrTime['minutes'], $arrTime['hours'], $arrTime['mday'], $arrTime['mon'], $arrTime['year']);
320
        }
321
    }
322
323
    final private function dtAsString($arrDt)
324
    {
325
        if($arrDt === FALSE)
326
            return FALSE;
327
        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';
328
    }
329
330
331
    //
332
    // Function:    match
333
    //
334
    // Description:    Returns TRUE if the specified date and time corresponds to a scheduled point in time. FALSE otherwise.
335
    //
336
    // Parameters:    $time                If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
337
    //                                    If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
338
    //                                    If a string, interpreted as unix time.
339
    //                                    If omitted or specified FALSE, defaults to the current time.
340
    //
341
    // Result:        TRUE if the schedule matches the specified datetime. FALSE otherwise.
342
    //
343
344
    final public function match($time = FALSE)
345
    {
346
347
        // Convert parameters to array datetime
348
349
        $arrDT = $this->dtFromParameters($time);
350
351
352
        // Verify match
353
354
355
        // Years
356
        if(!array_key_exists($arrDT[4], $this->_years)) return FALSE;
357
        // Day of week
358
        if(!array_key_exists(date('w', strtotime($arrDT[4].'-'.$arrDT[3].'-'.$arrDT[2])), $this->_daysOfWeek)) return FALSE;
359
        // Month
360
        if(!array_key_exists($arrDT[3], $this->_months)) return FALSE;
361
        // Day of month
362
        if(!array_key_exists($arrDT[2], $this->_daysOfMonth)) return FALSE;
363
        // Hours
364
        if(!array_key_exists($arrDT[1], $this->_hours)) return FALSE;
365
        // Minutes
366
        if(!array_key_exists($arrDT[0], $this->_minutes)) return FALSE;
367
368
        return TRUE;
369
    }
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.
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
400
            // Verify the current date is in range. If not, move into range and consider this the next position
401
402
            if(!array_key_exists($arrDT[4], $this->_years))
403
            {
404
                if(($arrDT[4] = $this->getEarliestItem($this->_years, $arrDT[4], FALSE)) === FALSE)
405
                    return FALSE;
406
                $arrDT[3] = $this->getEarliestItem($this->_months);
407
                $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth);
408
                $arrDT[1] = $this->getEarliestItem($this->_hours);
409
                $arrDT[0] = $this->getEarliestItem($this->_minutes);
410
                break;
411
            } elseif(!array_key_exists($arrDT[3], $this->_months))
412
            {
413
                $arrDT[3] = $this->getEarliestItem($this->_months, $arrDT[3]);
414
                $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth);
415
                $arrDT[1] = $this->getEarliestItem($this->_hours);
416
                $arrDT[0] = $this->getEarliestItem($this->_minutes);
417
                break;
418
            } elseif(!array_key_exists($arrDT[2], $this->_daysOfMonth))
419
            {
420
                $arrDT[2] = $this->getEarliestItem($this->_daysOfMonth, $arrDT[2]);
421
                $arrDT[1] = $this->getEarliestItem($this->_hours);
422
                $arrDT[0] = $this->getEarliestItem($this->_minutes);
423
                break;
424
            } elseif(!array_key_exists($arrDT[1], $this->_hours))
425
            {
426
                $arrDT[1] = $this->getEarliestItem($this->_hours, $arrDT[1]);
427
                $arrDT[0] = $this->getEarliestItem($this->_minutes);
428
                break;
429
            } elseif(!array_key_exists($arrDT[1], $this->_hours))
430
            {
431
                $arrDT[0] = $this->getEarliestItem($this->_minutes, $arrDT[0]);
432
                break;
433
            }
434
435
436
            // Advance minute, hour, date, month and year while overflowing.
437
438
            $daysInThisMonth = date('t', strtotime($arrDT[4].'-'.$arrDT[3]));
439
            if($this->advanceItem($this->_minutes, 0, 59, $arrDT[0]))
440
                if($this->advanceItem($this->_hours, 0, 23, $arrDT[1]))
441
                    if($this->advanceItem($this->_daysOfMonth, 0, $daysInThisMonth, $arrDT[2]))
442
                        if($this->advanceItem($this->_months, 1, 12, $arrDT[3]))
443
                            if($this->advanceItem($this->_years, $this->RANGE_YEARS_MIN, $this->RANGE_YEARS_MAX, $arrDT[4]))
444
                                return FALSE;
445
            break;
446
        }
447
448
449
        // If Datetime now points to a day that is schedule then return.
450
451
        $dayOfWeek = date('w', strtotime($this->dtAsString($arrDT)));
452
        if(array_key_exists($dayOfWeek, $this->_daysOfWeek))
453
            return $arrDT;
454
455
456
        // Otherwise move to next scheduled date
457
458
        return $this->next($arrDT);
459
    }
460
461
    final public function nextAsString($time = FALSE)
462
    {
463
        return $this->dtAsString($this->next($time));
464
    }
465
466
    final public function nextAsTime($time = FALSE)
467
    {
468
        return strtotime($this->dtAsString($this->next($time)));
469
    }
470
471
472
    //
473
    // Function:    advanceItem
474
    //
475
    // Description:    Advances the current item to the next one (the next minute, the next hour, etc.).
476
    //
477
    // Parameters:    $arrItems            A reference to the collection in which to advance.
478
    //                $rangeMin            The lowest possible value for $current.
479
    //                $rangeMax            The highest possible value for $current
480
    //                $current            The index that is being incremented.
481
    //
482
    // Result:        FALSE if current did not overflow (reset back to the earliest possible value). TRUE if it did.
483
    //
484
485
    /**
486
     * @param integer $rangeMin
487
     */
488
    final private function advanceItem($arrItems, $rangeMin, $rangeMax, & $current)
489
    {
490
491
        // Advance pointer
492
493
        $current++;
494
495
496
        // If still before start, move to earliest
497
498
        if($current < $rangeMin)
499
            $current = $this->getEarliestItem($arrItems);
500
501
502
        // Parse items until found or overflow
503
504
        for(;$current <= $rangeMax; $current++)
505
            if(array_key_exists($current, $arrItems))
506
                return FALSE; // We did not overflow
507
508
509
        // Or overflow
510
511
        $current = $this->getEarliestItem($arrItems);
512
        return TRUE;
513
    }
514
515
516
    //
517
    // Function:    getEarliestItem
518
    //
519
    // Description:    Retrieves the earliest item in a collection, e.g. the earliest minute or the earliest month.
520
    //
521
    // Parameters:    $arrItems            A reference to the collection in which to search.
522
    //                $afterItem            The highest index that is to be skipped.
523
    //
524
525
    final private function getEarliestItem($arrItems, $afterItem = FALSE, $allowOverflow = TRUE)
526
    {
527
528
        // If no filter is specified, return the earliest listed item.
529
530
        if($afterItem === FALSE)
531
        {
532
            reset($arrItems);
533
            return key($arrItems);
534
        }
535
536
537
        // Or parse until we passed $afterItem
538
539
        foreach($arrItems as $key => $value)
540
            if($key > $afterItem)
541
                return $key;
542
543
544
        // If still nothing found, we may have exhausted our options.
545
546
        if(!$allowOverflow)
547
            return FALSE;
548
        reset($arrItems);
549
        return key($arrItems);
550
    }
551
552
553
    //
554
    // Function:    previous
555
    //
556
    // Description:    Acquires the first scheduled datetime before the provided one.
557
    //
558
    // Parameters:    $time                If a string interpreted as a datetime string in the YYYY-MM-DD HH:II format and other parameters ignored.
559
    //                                    If an array $minute, $hour, $day, $month and $year are passed as keys 0-4 and other parameters ignored.
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
560
    //                                    If a string, interpreted as unix time.
561
    //                                    If omitted or specified FALSE, defaults to the current time.
562
    //
563
    // Result:        An array with the following keys:
564
    //                0                    Previous scheduled minute
565
    //                1                    Previous scheduled hour
566
    //                2                    Previous scheduled date
567
    //                3                    Previous scheduled month
568
    //                4                    Previous scheduled year
569
    //
570
571
    final public function previous($time = FALSE)
572
    {
573
574
        // Convert parameters to array datetime
575
576
        $arrDT = $this->dtFromParameters($time);
577
578
        while(1)
579
        {
580
581
            // Verify the current date is in range. If not, move into range and consider this the previous position
582
583
            if(!array_key_exists($arrDT[4], $this->_years))
584
            {
585
                if(($arrDT[4] = $this->getLatestItem($this->_years, $arrDT[4], FALSE)) === FALSE)
586
                    return FALSE;
587
                $arrDT[3] = $this->getLatestItem($this->_months);
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[3], $this->_months))
593
            {
594
                $arrDT[3] = $this->getLatestItem($this->_months, $arrDT[3]);
595
                $arrDT[2] = $this->getLatestItem($this->_daysOfMonth);
596
                $arrDT[1] = $this->getLatestItem($this->_hours);
597
                $arrDT[0] = $this->getLatestItem($this->_minutes);
598
                break;
599
            } elseif(!array_key_exists($arrDT[2], $this->_daysOfMonth))
600
            {
601
                $arrDT[2] = $this->getLatestItem($this->_daysOfMonth, $arrDT[2]);
602
                $arrDT[1] = $this->getLatestItem($this->_hours);
603
                $arrDT[0] = $this->getLatestItem($this->_minutes);
604
                break;
605
            } elseif(!array_key_exists($arrDT[1], $this->_hours))
606
            {
607
                $arrDT[1] = $this->getLatestItem($this->_hours, $arrDT[1]);
608
                $arrDT[0] = $this->getLatestItem($this->_minutes);
609
                break;
610
            } elseif(!array_key_exists($arrDT[1], $this->_hours))
611
            {
612
                $arrDT[0] = $this->getLatestItem($this->_minutes, $arrDT[0]);
613
                break;
614
            }
615
616
617
            // Recede minute, hour, date, month and year while overflowing.
618
619
            $daysInPreviousMonth = date('t', strtotime('-1 month', strtotime($arrDT[4].'-'.$arrDT[3])));
620
            if($this->recedeItem($this->_minutes, 0, 59, $arrDT[0]))
621
                if($this->recedeItem($this->_hours, 0, 23, $arrDT[1]))
622
                    if($this->recedeItem($this->_daysOfMonth, 0, $daysInPreviousMonth, $arrDT[2]))
623
                        if($this->recedeItem($this->_months, 1, 12, $arrDT[3]))
624
                            if($this->recedeItem($this->_years, $this->RANGE_YEARS_MIN, $this->RANGE_YEARS_MAX, $arrDT[4]))
625
                                return FALSE;
626
            break;
627
        }
628
629
630
        // If Datetime now points to a day that is schedule then return.
631
632
        $dayOfWeek = date('w', strtotime($this->dtAsString($arrDT)));
633
        if(array_key_exists($dayOfWeek, $this->_daysOfWeek))
634
            return $arrDT;
635
636
637
        // Otherwise move to next scheduled date
638
639
        return $this->previous($arrDT);
640
    }
641
642
    final public function previousAsString($time = FALSE)
643
    {
644
        return $this->dtAsString($this->previous($time));
645
    }
646
647
    final public function previousAsTime($time = FALSE)
648
    {
649
        return strtotime($this->dtAsString($this->previous($time)));
650
    }
651
652
653
    //
654
    // Function:    recedeItem
655
    //
656
    // Description:    Recedes the current item to the previous one (the previous minute, the previous hour, etc.).
657
    //
658
    // Parameters:    $arrItems            A reference to the collection in which to recede.
659
    //                $rangeMin            The lowest possible value for $current.
660
    //                $rangeMax            The highest possible value for $current
661
    //                $current            The index that is being decremented.
662
    //
663
    // Result:        FALSE if current did not overflow (reset back to the highest possible value). TRUE if it did.
664
    //
665
666
    /**
667
     * @param integer $rangeMin
668
     */
669
    final private function recedeItem($arrItems, $rangeMin, $rangeMax, & $current)
670
    {
671
672
        // Recede pointer
673
674
        $current--;
675
676
677
        // If still above highest, move to highest
678
679
        if($current > $rangeMax)
680
            $current = $this->getLatestItem($arrItems, $rangeMax + 1);
681
682
683
        // Parse items until found or overflow
684
685
        for(;$current >= $rangeMin; $current--)
686
            if(array_key_exists($current, $arrItems))
687
                return FALSE; // We did not overflow
688
689
690
        // Or overflow
691
692
        $current = $this->getLatestItem($arrItems, $rangeMax + 1);
693
        return TRUE;
694
    }
695
696
697
    //
698
    // Function:    getLatestItem
699
    //
700
    // Description:    Retrieves the latest item in a collection, e.g. the latest minute or the latest month.
701
    //
702
    // Parameters:    $arrItems            A reference to the collection in which to search.
703
    //                $beforeItem            The lowest index that is to be skipped.
704
    //
705
706
    final private function getLatestItem($arrItems, $beforeItem = FALSE, $allowOverflow = TRUE)
707
    {
708
709
        // If no filter is specified, return the latestlisted item.
710
711
        if($beforeItem === FALSE)
712
        {
713
            end($arrItems);
714
            return key($arrItems);
715
        }
716
717
718
        // Or parse until we passed $beforeItem
719
720
        end($arrItems);
721
        do
722
        {
723
            if(($key = key($arrItems)) < $beforeItem)
724
                return $key;
725
        } while(prev($arrItems));
726
727
728
        // If still nothing found, we may have exhausted our options.
729
730
        if(!$allowOverflow)
731
            return FALSE;
732
        end($arrItems);
733
        return key($arrItems);
734
    }
735
736
737
738
    //
739
    // Function:
740
    //
741
    // Description:
742
    //
743
    // Parameters:
744
    //
745
    // Result:
746
    //
747
748
    final private function getClass($spec)
749
    {
750
        if(!$this->classIsSpecified($spec))        return '0';
751
        if($this->classIsSingleFixed($spec))    return '1';
752
        return '2';
753
    }
754
755
756
    //
757
    // Function:
758
    //
759
    // Description:    Returns TRUE if the Cron Specification is specified. FALSE otherwise. This is true if the specification has more than one entry
760
    //                or is anything than the entire approved range ("*").
761
    //
762
    // Parameters:
763
    //
764
    // Result:
765
    //
766
767
    final private function classIsSpecified($spec)
768
    {
769
        if($spec['elements'][0]['hasInterval'] == FALSE)            return TRUE;
770
        if($spec['elements'][0]['number1'] != $spec['rangeMin'])    return TRUE;
771
        if($spec['elements'][0]['number2'] != $spec['rangeMax'])    return TRUE;
772
        if($spec['elements'][0]['interval'] != 1)                    return TRUE;
773
        return FALSE;
774
    }
775
776
777
    //
778
    // Function:
779
    //
780
    // Description:    Returns TRUE if the Cron Specification is specified as a single value. FALSE otherwise. This is true only if there is only
781
    //                one entry and the entry is only a single number (e.g. "10")
782
    //
783
    // Parameters:
784
    //
785
    // Result:
786
    //
787
788
    final private function classIsSingleFixed($spec)
789
    {
790
        return (count($spec['elements']) == 1) && (!$spec['elements'][0]['hasInterval']);
791
    }
792
793
    final private function initLang($language = 'en')
794
    {
795
        switch($language)
796
        {
797
            case 'en':
798
                $this->_lang['elemMin: at_the_hour']                            = 'at the hour';
799
                $this->_lang['elemMin: after_the_hour_every_X_minute']            = 'every minute';
800
                $this->_lang['elemMin: after_the_hour_every_X_minute_plural']    = 'every @1 minutes';
801
                $this->_lang['elemMin: every_consecutive_minute']                = 'every consecutive minute';
802
                $this->_lang['elemMin: every_consecutive_minute_plural']        = 'every consecutive @1 minutes';
803
                $this->_lang['elemMin: every_minute']                            = 'every minute';
804
                $this->_lang['elemMin: between_X_and_Y']                        = 'from the @1 to the @2';
805
                $this->_lang['elemMin: at_X:Y']                                    = 'At @1:@2';
806
                $this->_lang['elemHour: past_X:00']                                = 'past @1:00';
807
                $this->_lang['elemHour: between_X:00_and_Y:59']                    = 'between @1:00 and @2:59';
808
                $this->_lang['elemHour: in_the_60_minutes_past_']                = 'in the 60 minutes past every consecutive hour';
809
                $this->_lang['elemHour: in_the_60_minutes_past__plural']        = 'in the 60 minutes past every consecutive @1 hours';
810
                $this->_lang['elemHour: past_every_consecutive_']                = 'past every consecutive hour';
811
                $this->_lang['elemHour: past_every_consecutive__plural']        = 'past every consecutive @1 hours';
812
                $this->_lang['elemHour: past_every_hour']                        = 'past every hour';
813
                $this->_lang['elemDOM: the_X']                                    = 'the @1';
814
                $this->_lang['elemDOM: every_consecutive_day']                    = 'every consecutive day';
815
                $this->_lang['elemDOM: every_consecutive_day_plural']            = 'every consecutive @1 days';
816
                $this->_lang['elemDOM: on_every_day']                            = 'on every day';
817
                $this->_lang['elemDOM: between_the_Xth_and_Yth']                = 'between the @1 and the @2';
818
                $this->_lang['elemDOM: on_the_X']                                = 'on the @1';
819
                $this->_lang['elemDOM: on_X']                                    = 'on @1';
820
                $this->_lang['elemMonth: every_X']                                = 'every @1';
821
                $this->_lang['elemMonth: every_consecutive_month']                = 'every consecutive month';
822
                $this->_lang['elemMonth: every_consecutive_month_plural']        = 'every consecutive @1 months';
823
                $this->_lang['elemMonth: between_X_and_Y']                        = 'from @1 to @2';
824
                $this->_lang['elemMonth: of_every_month']                        = 'of every month';
825
                $this->_lang['elemMonth: during_every_X']                        = 'during every @1';
826
                $this->_lang['elemMonth: during_X']                                = 'during @1';
827
                $this->_lang['elemYear: in_X']                                    = 'in @1';
828
                $this->_lang['elemYear: every_consecutive_year']                = 'every consecutive year';
829
                $this->_lang['elemYear: every_consecutive_year_plural']            = 'every consecutive @1 years';
830
                $this->_lang['elemYear: from_X_through_Y']                        = 'from @1 through @2';
831
                $this->_lang['elemDOW: on_every_day']                            = 'on every day';
832
                $this->_lang['elemDOW: on_X']                                    = 'on @1';
833
                $this->_lang['elemDOW: but_only_on_X']                            = 'but only if the event takes place on @1';
834
                $this->_lang['separator_and']                                    = 'and';
835
                $this->_lang['separator_or']                                    = 'or';
836
                $this->_lang['day: 0_plural']                                    = 'Sundays';
837
                $this->_lang['day: 1_plural']                                    = 'Mondays';
838
                $this->_lang['day: 2_plural']                                    = 'Tuesdays';
839
                $this->_lang['day: 3_plural']                                    = 'Wednesdays';
840
                $this->_lang['day: 4_plural']                                    = 'Thursdays';
841
                $this->_lang['day: 5_plural']                                    = 'Fridays';
842
                $this->_lang['day: 6_plural']                                    = 'Saturdays';
843
                $this->_lang['month: 1']                                        = 'January';
844
                $this->_lang['month: 2']                                        = 'February';
845
                $this->_lang['month: 3']                                        = 'March';
846
                $this->_lang['month: 4']                                        = 'April';
847
                $this->_lang['month: 5']                                        = 'May';
848
                $this->_lang['month: 6']                                        = 'June';
849
                $this->_lang['month: 7']                                        = 'July';
850
                $this->_lang['month: 8']                                        = 'Augustus';
851
                $this->_lang['month: 9']                                        = 'September';
852
                $this->_lang['month: 10']                                        = 'October';
853
                $this->_lang['month: 11']                                        = 'November';
854
                $this->_lang['month: 12']                                        = 'December';
855
                $this->_lang['ordinal: 1']                                        = '1st';
856
                $this->_lang['ordinal: 2']                                        = '2nd';
857
                $this->_lang['ordinal: 3']                                        = '3rd';
858
                $this->_lang['ordinal: 4']                                        = '4th';
859
                $this->_lang['ordinal: 5']                                        = '5th';
860
                $this->_lang['ordinal: 6']                                        = '6th';
861
                $this->_lang['ordinal: 7']                                        = '7th';
862
                $this->_lang['ordinal: 8']                                        = '8th';
863
                $this->_lang['ordinal: 9']                                        = '9th';
864
                $this->_lang['ordinal: 10']                                        = '10th';
865
                $this->_lang['ordinal: 11']                                        = '11th';
866
                $this->_lang['ordinal: 12']                                        = '12th';
867
                $this->_lang['ordinal: 13']                                        = '13th';
868
                $this->_lang['ordinal: 14']                                        = '14th';
869
                $this->_lang['ordinal: 15']                                        = '15th';
870
                $this->_lang['ordinal: 16']                                        = '16th';
871
                $this->_lang['ordinal: 17']                                        = '17th';
872
                $this->_lang['ordinal: 18']                                        = '18th';
873
                $this->_lang['ordinal: 19']                                        = '19th';
874
                $this->_lang['ordinal: 20']                                        = '20th';
875
                $this->_lang['ordinal: 21']                                        = '21st';
876
                $this->_lang['ordinal: 22']                                        = '22nd';
877
                $this->_lang['ordinal: 23']                                        = '23rd';
878
                $this->_lang['ordinal: 24']                                        = '24th';
879
                $this->_lang['ordinal: 25']                                        = '25th';
880
                $this->_lang['ordinal: 26']                                        = '26th';
881
                $this->_lang['ordinal: 27']                                        = '27th';
882
                $this->_lang['ordinal: 28']                                        = '28th';
883
                $this->_lang['ordinal: 29']                                        = '29th';
884
                $this->_lang['ordinal: 30']                                        = '30th';
885
                $this->_lang['ordinal: 31']                                        = '31st';
886
                $this->_lang['ordinal: 32']                                        = '32nd';
887
                $this->_lang['ordinal: 33']                                        = '33rd';
888
                $this->_lang['ordinal: 34']                                        = '34th';
889
                $this->_lang['ordinal: 35']                                        = '35th';
890
                $this->_lang['ordinal: 36']                                        = '36th';
891
                $this->_lang['ordinal: 37']                                        = '37th';
892
                $this->_lang['ordinal: 38']                                        = '38th';
893
                $this->_lang['ordinal: 39']                                        = '39th';
894
                $this->_lang['ordinal: 40']                                        = '40th';
895
                $this->_lang['ordinal: 41']                                        = '41st';
896
                $this->_lang['ordinal: 42']                                        = '42nd';
897
                $this->_lang['ordinal: 43']                                        = '43rd';
898
                $this->_lang['ordinal: 44']                                        = '44th';
899
                $this->_lang['ordinal: 45']                                        = '45th';
900
                $this->_lang['ordinal: 46']                                        = '46th';
901
                $this->_lang['ordinal: 47']                                        = '47th';
902
                $this->_lang['ordinal: 48']                                        = '48th';
903
                $this->_lang['ordinal: 49']                                        = '49th';
904
                $this->_lang['ordinal: 50']                                        = '50th';
905
                $this->_lang['ordinal: 51']                                        = '51st';
906
                $this->_lang['ordinal: 52']                                        = '52nd';
907
                $this->_lang['ordinal: 53']                                        = '53rd';
908
                $this->_lang['ordinal: 54']                                        = '54th';
909
                $this->_lang['ordinal: 55']                                        = '55th';
910
                $this->_lang['ordinal: 56']                                        = '56th';
911
                $this->_lang['ordinal: 57']                                        = '57th';
912
                $this->_lang['ordinal: 58']                                        = '58th';
913
                $this->_lang['ordinal: 59']                                        = '59th';
914
                break;
915
916
            case 'nl':
917
                $this->_lang['elemMin: at_the_hour']                            = 'op het hele uur';
918
                $this->_lang['elemMin: after_the_hour_every_X_minute']            = 'elke minuut';
919
                $this->_lang['elemMin: after_the_hour_every_X_minute_plural']    = 'elke @1 minuten';
920
                $this->_lang['elemMin: every_consecutive_minute']                = 'elke opeenvolgende minuut';
921
                $this->_lang['elemMin: every_consecutive_minute_plural']        = 'elke opeenvolgende @1 minuten';
922
                $this->_lang['elemMin: every_minute']                            = 'elke minuut';
923
                $this->_lang['elemMin: between_X_and_Y']                        = 'van de @1 tot en met de @2';
924
                $this->_lang['elemMin: at_X:Y']                                    = 'Om @1:@2';
925
                $this->_lang['elemHour: past_X:00']                                = 'na @1:00';
926
                $this->_lang['elemHour: between_X:00_and_Y:59']                    = 'tussen @1:00 en @2:59';
927
                $this->_lang['elemHour: in_the_60_minutes_past_']                = 'in de 60 minuten na elk opeenvolgend uur';
928
                $this->_lang['elemHour: in_the_60_minutes_past__plural']        = 'in de 60 minuten na elke opeenvolgende @1 uren';
929
                $this->_lang['elemHour: past_every_consecutive_']                = 'na elk opeenvolgend uur';
930
                $this->_lang['elemHour: past_every_consecutive__plural']        = 'na elke opeenvolgende @1 uren';
931
                $this->_lang['elemHour: past_every_hour']                        = 'na elk uur';
932
                $this->_lang['elemDOM: the_X']                                    = 'de @1';
933
                $this->_lang['elemDOM: every_consecutive_day']                    = 'elke opeenvolgende dag';
934
                $this->_lang['elemDOM: every_consecutive_day_plural']            = 'elke opeenvolgende @1 dagen';
935
                $this->_lang['elemDOM: on_every_day']                            = 'op elke dag';
936
                $this->_lang['elemDOM: between_the_Xth_and_Yth']                = 'tussen de @1 en de @2';
937
                $this->_lang['elemDOM: on_the_X']                                = 'op de @1';
938
                $this->_lang['elemDOM: on_X']                                    = 'op @1';
939
                $this->_lang['elemMonth: every_X']                                = 'elke @1';
940
                $this->_lang['elemMonth: every_consecutive_month']                = 'elke opeenvolgende maand';
941
                $this->_lang['elemMonth: every_consecutive_month_plural']        = 'elke opeenvolgende @1 maanden';
942
                $this->_lang['elemMonth: between_X_and_Y']                        = 'van @1 tot @2';
943
                $this->_lang['elemMonth: of_every_month']                        = 'van elke maand';
944
                $this->_lang['elemMonth: during_every_X']                        = 'tijdens elke @1';
945
                $this->_lang['elemMonth: during_X']                                = 'tijdens @1';
946
                $this->_lang['elemYear: in_X']                                    = 'in @1';
947
                $this->_lang['elemYear: every_consecutive_year']                = 'elk opeenvolgend jaar';
948
                $this->_lang['elemYear: every_consecutive_year_plural']            = 'elke opeenvolgende @1 jaren';
949
                $this->_lang['elemYear: from_X_through_Y']                        = 'van @1 tot en met @2';
950
                $this->_lang['elemDOW: on_every_day']                            = 'op elke dag';
951
                $this->_lang['elemDOW: on_X']                                    = 'op @1';
952
                $this->_lang['elemDOW: but_only_on_X']                            = 'maar alleen als het plaatsvindt op @1';
953
                $this->_lang['separator_and']                                    = 'en';
954
                $this->_lang['separator_of']                                    = 'of';
955
                $this->_lang['day: 0_plural']                                    = 'zondagen';
956
                $this->_lang['day: 1_plural']                                    = 'maandagen';
957
                $this->_lang['day: 2_plural']                                    = 'dinsdagen';
958
                $this->_lang['day: 3_plural']                                    = 'woensdagen';
959
                $this->_lang['day: 4_plural']                                    = 'donderdagen';
960
                $this->_lang['day: 5_plural']                                    = 'vrijdagen';
961
                $this->_lang['day: 6_plural']                                    = 'zaterdagen';
962
                $this->_lang['month: 1']                                        = 'januari';
963
                $this->_lang['month: 2']                                        = 'februari';
964
                $this->_lang['month: 3']                                        = 'maart';
965
                $this->_lang['month: 4']                                        = 'april';
966
                $this->_lang['month: 5']                                        = 'mei';
967
                $this->_lang['month: 6']                                        = 'juni';
968
                $this->_lang['month: 7']                                        = 'juli';
969
                $this->_lang['month: 8']                                        = 'augustus';
970
                $this->_lang['month: 9']                                        = 'september';
971
                $this->_lang['month: 10']                                        = 'october';
972
                $this->_lang['month: 11']                                        = 'november';
973
                $this->_lang['month: 12']                                        = 'december';
974
                $this->_lang['ordinal: 1']                                        = '1e';
975
                $this->_lang['ordinal: 2']                                        = '2e';
976
                $this->_lang['ordinal: 3']                                        = '3e';
977
                $this->_lang['ordinal: 4']                                        = '4e';
978
                $this->_lang['ordinal: 5']                                        = '5e';
979
                $this->_lang['ordinal: 6']                                        = '6e';
980
                $this->_lang['ordinal: 7']                                        = '7e';
981
                $this->_lang['ordinal: 8']                                        = '8e';
982
                $this->_lang['ordinal: 9']                                        = '9e';
983
                $this->_lang['ordinal: 10']                                        = '10e';
984
                $this->_lang['ordinal: 11']                                        = '11e';
985
                $this->_lang['ordinal: 12']                                        = '12e';
986
                $this->_lang['ordinal: 13']                                        = '13e';
987
                $this->_lang['ordinal: 14']                                        = '14e';
988
                $this->_lang['ordinal: 15']                                        = '15e';
989
                $this->_lang['ordinal: 16']                                        = '16e';
990
                $this->_lang['ordinal: 17']                                        = '17e';
991
                $this->_lang['ordinal: 18']                                        = '18e';
992
                $this->_lang['ordinal: 19']                                        = '19e';
993
                $this->_lang['ordinal: 20']                                        = '20e';
994
                $this->_lang['ordinal: 21']                                        = '21e';
995
                $this->_lang['ordinal: 22']                                        = '22e';
996
                $this->_lang['ordinal: 23']                                        = '23e';
997
                $this->_lang['ordinal: 24']                                        = '24e';
998
                $this->_lang['ordinal: 25']                                        = '25e';
999
                $this->_lang['ordinal: 26']                                        = '26e';
1000
                $this->_lang['ordinal: 27']                                        = '27e';
1001
                $this->_lang['ordinal: 28']                                        = '28e';
1002
                $this->_lang['ordinal: 29']                                        = '29e';
1003
                $this->_lang['ordinal: 30']                                        = '30e';
1004
                $this->_lang['ordinal: 31']                                        = '31e';
1005
                $this->_lang['ordinal: 32']                                        = '32e';
1006
                $this->_lang['ordinal: 33']                                        = '33e';
1007
                $this->_lang['ordinal: 34']                                        = '34e';
1008
                $this->_lang['ordinal: 35']                                        = '35e';
1009
                $this->_lang['ordinal: 36']                                        = '36e';
1010
                $this->_lang['ordinal: 37']                                        = '37e';
1011
                $this->_lang['ordinal: 38']                                        = '38e';
1012
                $this->_lang['ordinal: 39']                                        = '39e';
1013
                $this->_lang['ordinal: 40']                                        = '40e';
1014
                $this->_lang['ordinal: 41']                                        = '41e';
1015
                $this->_lang['ordinal: 42']                                        = '42e';
1016
                $this->_lang['ordinal: 43']                                        = '43e';
1017
                $this->_lang['ordinal: 44']                                        = '44e';
1018
                $this->_lang['ordinal: 45']                                        = '45e';
1019
                $this->_lang['ordinal: 46']                                        = '46e';
1020
                $this->_lang['ordinal: 47']                                        = '47e';
1021
                $this->_lang['ordinal: 48']                                        = '48e';
1022
                $this->_lang['ordinal: 49']                                        = '49e';
1023
                $this->_lang['ordinal: 50']                                        = '50e';
1024
                $this->_lang['ordinal: 51']                                        = '51e';
1025
                $this->_lang['ordinal: 52']                                        = '52e';
1026
                $this->_lang['ordinal: 53']                                        = '53e';
1027
                $this->_lang['ordinal: 54']                                        = '54e';
1028
                $this->_lang['ordinal: 55']                                        = '55e';
1029
                $this->_lang['ordinal: 56']                                        = '56e';
1030
                $this->_lang['ordinal: 57']                                        = '57e';
1031
                $this->_lang['ordinal: 58']                                        = '58e';
1032
                $this->_lang['ordinal: 59']                                        = '59e';
1033
                break;
1034
        }
1035
    }
1036
1037
    final private function natlangPad2($number)
1038
    {
1039
        return (strlen($number) == 1 ? '0' : '').$number;
1040
    }
1041
1042
    /**
1043
     * @param string $id
1044
     *
1045
     * @return boolean|string
1046
     */
1047
    final private function natlangApply($id, $p1 = FALSE, $p2 = FALSE, $p3 = FALSE, $p4 = FALSE, $p5 = FALSE, $p6 = FALSE)
1048
    {
1049
        $txt = $this->_lang[$id];
1050
1051
        if($p1 !== FALSE)    $txt = str_replace('@1', $p1, $txt);
1052
        if($p2 !== FALSE)    $txt = str_replace('@2', $p2, $txt);
1053
        if($p3 !== FALSE)    $txt = str_replace('@3', $p3, $txt);
1054
        if($p4 !== FALSE)    $txt = str_replace('@4', $p4, $txt);
1055
        if($p5 !== FALSE)    $txt = str_replace('@5', $p5, $txt);
1056
        if($p6 !== FALSE)    $txt = str_replace('@6', $p6, $txt);
1057
1058
        return $txt;
1059
    }
1060
1061
1062
    //
1063
    // Function:    natlangRange
1064
    //
1065
    // Description:    Converts a range into natural language
1066
    //
1067
    // Parameters:
1068
    //
1069
    // Result:
1070
    //
1071
1072
    final private function natlangRange($spec, $entryFunction, $p1 = FALSE)
1073
    {
1074
        $arrIntervals = array();
1075
        foreach($spec['elements'] as $elem)
1076
            $arrIntervals[] = call_user_func($entryFunction, $elem, $p1);
1077
1078
        $txt = "";
1079
        for($index = 0; $index < count($arrIntervals); $index++)
1080
            $txt .= ($index == 0 ? '' : ($index == (count($arrIntervals) - 1) ? ' '.$this->natlangApply('separator_and').' ' : ', ')).$arrIntervals[$index];
1081
        return $txt;
1082
    }
1083
1084
1085
    //
1086
    // Function:    natlangElementMinute
1087
    //
1088
    // Description:    Converts an entry from the minute specification to natural language.
1089
    //
1090
1091
    final private function natlangElementMinute($elem)
1092
    {
1093
        if(!$elem['hasInterval'])
1094
        {
1095
            if($elem['number1'] == 0)    return $this->natlangApply('elemMin: at_the_hour');
1096
            else                        return $this->natlangApply('elemMin: after_the_hour_every_X_minute'.($elem['number1'] == 1 ? '' : '_plural'), $elem['number1']);
1097
        }
1098
1099
        $txt = $this->natlangApply('elemMin: every_consecutive_minute'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1100
        if(($elem['number1'] != $this->_cronMinutes['rangeMin']) || ($elem['number2'] != $this->_cronMinutes['rangeMax']))
1101
            $txt .= ' ('.$this->natlangApply('elemMin: between_X_and_Y', $this->natlangApply('ordinal: '.$elem['number1']), $this->natlangApply('ordinal: '.$elem['number2'])).')';
1102
        return $txt;
1103
    }
1104
1105
1106
    //
1107
    // Function:    natlangElementHour
1108
    //
1109
    // Description:    Converts an entry from the hour specification to natural language.
1110
    //
1111
1112
    final private function natlangElementHour($elem, $asBetween)
1113
    {
1114
        if(!$elem['hasInterval'])
1115
        {
1116
            if($asBetween)    return $this->natlangApply('elemHour: between_X:00_and_Y:59', $this->natlangPad2($elem['number1']), $this->natlangPad2($elem['number1']));
1117
            else            return $this->natlangApply('elemHour: past_X:00', $this->natlangPad2($elem['number1']));
1118
        }
1119
1120
        if($asBetween)        $txt = $this->natlangApply('elemHour: in_the_60_minutes_past_'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1121
        else                $txt = $this->natlangApply('elemHour: past_every_consecutive_'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1122
1123
        if(($elem['number1'] != $this->_cronHours['rangeMin']) || ($elem['number2'] != $this->_cronHours['rangeMax']))
1124
            $txt .= ' ('.$this->natlangApply('elemHour: between_X:00_and_Y:59', $elem['number1'], $elem['number2']).')';
1125
        return $txt;
1126
    }
1127
1128
1129
    //
1130
    // Function:    natlangElementDayOfMonth
1131
    //
1132
    // Description:    Converts an entry from the day of month specification to natural language.
1133
    //
1134
1135
    final private function natlangElementDayOfMonth($elem)
1136
    {
1137
        if(!$elem['hasInterval'])
1138
            return $this->natlangApply('elemDOM: the_X', $this->natlangApply('ordinal: '.$elem['number1']));
1139
1140
        $txt = $this->natlangApply('elemDOM: every_consecutive_day'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1141
        if(($elem['number1'] != $this->_cronHours['rangeMin']) || ($elem['number2'] != $this->_cronHours['rangeMax']))
1142
            $txt .= ' ('.$this->natlangApply('elemDOM: between_the_Xth_and_Yth', $this->natlangApply('ordinal: '.$elem['number1']), $this->natlangApply('ordinal: '.$elem['number2'])).')';
1143
        return $txt;
1144
    }
1145
1146
1147
    //
1148
    // Function:    natlangElementDayOfMonth
1149
    //
1150
    // Description:    Converts an entry from the month specification to natural language.
1151
    //
1152
1153
    final private function natlangElementMonth($elem)
1154
    {
1155
        if(!$elem['hasInterval'])
1156
            return $this->natlangApply('elemMonth: every_X', $this->natlangApply('month: '.$elem['number1']));
1157
1158
        $txt = $this->natlangApply('elemMonth: every_consecutive_month'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1159
        if(($elem['number1'] != $this->_cronMonths['rangeMin']) || ($elem['number2'] != $this->_cronMonths['rangeMax']))
1160
            $txt .= ' ('.$this->natlangApply('elemMonth: between_X_and_Y', $this->natlangApply('month: '.$elem['number1']), $this->natlangApply('month: '.$elem['number2'])).')';
1161
        return $txt;
1162
    }
1163
1164
1165
    //
1166
    // Function:    natlangElementYear
1167
    //
1168
    // Description:    Converts an entry from the year specification to natural language.
1169
    //
1170
1171
    final private function natlangElementYear($elem)
1172
    {
1173
        if(!$elem['hasInterval'])
1174
            return $elem['number1'];
1175
1176
        $txt = $this->natlangApply('elemYear: every_consecutive_year'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
1177
        if(($elem['number1'] != $this->_cronMonths['rangeMin']) || ($elem['number2'] != $this->_cronMonths['rangeMax']))
1178
            $txt .= ' ('.$this->natlangApply('elemYear: from_X_through_Y', $elem['number1'], $elem['number2']).')';
1179
        return $txt;
1180
    }
1181
1182
1183
    //
1184
    // Function:    asNaturalLanguage
1185
    //
1186
    // Description:    Returns the current cron specification in natural language.
1187
    //
1188
    // Parameters:    None
1189
    //
1190
    // Result:        A string containing a natural language text.
1191
    //
1192
1193
    final public function asNaturalLanguage()
1194
    {
1195
        $switchForceDateExplaination = FALSE;
1196
        $switchDaysOfWeekAreExcluding = TRUE;
1197
1198
1199
        // Generate Time String
1200
1201
        $txtMinutes                    = array();
1202
        $txtMinutes[0]                = $this->natlangApply('elemMin: every_minute');
1203
        $txtMinutes[1]                = $this->natlangElementMinute($this->_cronMinutes['elements'][0]);
1204
        $txtMinutes[2]                = $this->natlangRange($this->_cronMinutes, array($this, 'natlangElementMinute'));
1205
1206
        $txtHours                    = array();
1207
        $txtHours[0]                = $this->natlangApply('elemHour: past_every_hour');
1208
        $txtHours[1]                = array();
1209
        $txtHours[1]['between']        = $this->natlangRange($this->_cronHours, array($this, 'natlangElementHour'), TRUE);
1210
        $txtHours[1]['past']        = $this->natlangRange($this->_cronHours, array($this, 'natlangElementHour'), FALSE);
1211
        $txtHours[2]                = array();
1212
        $txtHours[2]['between']        = $this->natlangRange($this->_cronHours, array($this, 'natlangElementHour'), TRUE);
1213
        $txtHours[2]['past']        = $this->natlangRange($this->_cronHours, array($this, 'natlangElementHour'), FALSE);
1214
1215
        $classMinutes                = $this->getClass($this->_cronMinutes);
1216
        $classHours                    = $this->getClass($this->_cronHours);
1217
1218
        switch($classMinutes.$classHours)
1219
        {
1220
1221
            // Special case: Unspecified date + Unspecified month
1222
            //
1223
            // Rule: The language for unspecified fields is omitted if a more detailed field has already been explained.
1224
            //
1225
            // The minutes field always yields an explaination, at the very least in the form of 'every minute'. This rule states that if the
1226
            // hour is not specified, it can be omitted because 'every minute' is already sufficiently clear.
1227
            //
1228
1229
            case '00':
1230
                $txtTime = $txtMinutes[0];
1231
                break;
1232
1233
1234
            // Special case: Fixed minutes and fixed hours
1235
            //
1236
            // The default writing would be something like 'every 20 minutes past 04:00', but the more common phrasing would be: At 04:20.
1237
            //
1238
            // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1239
            //
1240
1241
            case '11':
1242
                $txtTime = $this->natlangApply('elemMin: at_X:Y', $this->natlangPad2($this->_cronHours['elements'][0]['number1']), $this->natlangPad2($this->_cronMinutes['elements'][0]['number1']));
1243
                $switchForceDateExplaination = TRUE;
1244
                break;
1245
1246
1247
            // Special case: Between :00 and :59
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1248
            //
1249
            // If hours are specified, but minutes are not, then the minutes string will yield something like 'every minute'. We must the
1250
            // differentiate the hour specification because the minutes specification does not relate to all minutes past the hour, but only to
1251
            // those minutes between :00 and :59
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1252
            //
1253
            // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1254
            //
1255
1256
            case '01':
1257
            case '02':
1258
                $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours]['between'];
1259
                $switchForceDateExplaination = TRUE;
1260
                break;
1261
1262
1263
            // Special case: Past the hour
1264
            //
1265
            // If minutes are specified and hours are specified, then the specification of minutes is always limited to a maximum of 60 minutes
1266
            // and always applies to the minutes 'past the hour'.
1267
            //
1268
            // We will switch ForceDateExplaination on, so that even a non-specified date yields an explaination (e.g. 'every day')
1269
            //
1270
1271
            case '12':
1272
            case '22':
1273
            case '21':
1274
                $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours]['past'];
1275
                $switchForceDateExplaination = TRUE;
1276
                break;
1277
1278
            default:
1279
                $txtTime = $txtMinutes[$classMinutes].' '.$txtHours[$classHours];
1280
                break;
1281
        }
1282
1283
1284
        // Generate Date String
1285
1286
        $txtDaysOfMonth        = array();
1287
        $txtDaysOfMonth[0]    = '';
1288
        $txtDaysOfMonth[1]    = $this->natlangApply('elemDOM: on_the_X', $this->natlangApply('ordinal: '.$this->_cronDaysOfMonth['elements'][0]['number1']));
1289
        $txtDaysOfMonth[2]    = $this->natlangApply('elemDOM: on_X', $this->natlangRange($this->_cronDaysOfMonth, array($this, 'natlangElementDayOfMonth')));
1290
1291
        $txtMonths            = array();
1292
        $txtMonths[0]        = $this->natlangApply('elemMonth: of_every_month');
1293
        $txtMonths[1]        = $this->natlangApply('elemMonth: during_every_X', $this->natlangApply('month: '.$this->_cronMonths['elements'][0]['number1']));
1294
        $txtMonths[2]        = $this->natlangApply('elemMonth: during_X', $this->natlangRange($this->_cronMonths, array($this, 'natlangElementMonth')));
1295
1296
        $classDaysOfMonth    = $this->getClass($this->_cronDaysOfMonth);
1297
        $classMonths        = $this->getClass($this->_cronMonths);
1298
1299
        if($classDaysOfMonth == '0')
1300
            $switchDaysOfWeekAreExcluding = FALSE;
1301
1302
        switch($classDaysOfMonth.$classMonths)
1303
        {
1304
1305
            // Special case: Unspecified date + Unspecified month
1306
            //
1307
            // Rule: The language for unspecified fields is omitted if a more detailed field has already been explained.
1308
            //
1309
            // The time fields always yield an explaination, at the very least in the form of 'every minute'. This rule states that if the date
1310
            // is not specified, it can be omitted because 'every minute' is already sufficiently clear.
1311
            //
1312
            // There are some time specifications that do not contain an 'every' reference, but reference a specific time of day. In those cases
1313
            // the date explaination is enforced.
1314
            //
1315
1316
            case '00':
1317
                $txtDate = '';
1318
                break;
1319
1320
            default:
1321
                $txtDate = ' '.$txtDaysOfMonth[$classDaysOfMonth].' '.$txtMonths[$classMonths];
1322
                break;
1323
        }
1324
1325
1326
        // Generate Year String
1327
1328
        if ($this->_cronYears) {
1329
            $txtYears            = array();
1330
            $txtYears[0]        = '';
1331
            $txtYears[1]        = ' '.$this->natlangApply('elemYear: in_X', $this->_cronYears['elements'][0]['number1']);
1332
            $txtYears[2]        = ' '.$this->natlangApply('elemYear: in_X', $this->natlangRange($this->_cronYears, array($this, 'natlangElementYear')));
1333
1334
            $classYears            = $this->getClass($this->_cronYears);
1335
            $txtYear = $txtYears[$classYears];
1336
        }
1337
1338
1339
        // Generate DaysOfWeek String
1340
1341
        $collectDays = 0;
1342
        foreach($this->_cronDaysOfWeek['elements'] as $elem)
1343
        {
1344
            if($elem['hasInterval'])
1345
                for($x = $elem['number1']; $x <= $elem['number2']; $x += $elem['interval'])
1346
                    $collectDays |= pow(2, $x);
1347
            else
1348
                $collectDays |= pow(2, $elem['number1']);
1349
        }
1350
        if($collectDays == 127)    // * all days
1351
        {
1352
            if(!$switchDaysOfWeekAreExcluding)
1353
                $txtDays = ' '.$this->natlangApply('elemDOM: on_every_day');
1354
            else
1355
                $txtDays = '';
1356
        }
1357
        else
1358
        {
1359
            $arrDays = array();
1360
            for($x = 0; $x <= 6; $x++)
1361
                if($collectDays & pow(2, $x))
1362
                    $arrDays[] = $x;
1363
            $txtDays = '';
1364
            for($index = 0; $index < count($arrDays); $index++)
1365
                $txtDays .= ($index == 0 ? '' : ($index == (count($arrDays) - 1) ? ' '.$this->natlangApply($switchDaysOfWeekAreExcluding ? 'separator_or' : 'separator_and').' ' : ', ')).$this->natlangApply('day: '.$arrDays[$index].'_plural');
1366
            if($switchDaysOfWeekAreExcluding)    $txtDays = ' '.$this->natlangApply('elemDOW: but_only_on_X', $txtDays);
1367
            else                                $txtDays = ' '.$this->natlangApply('elemDOW: on_X', $txtDays);
1368
        }
1369
1370
        $txtResult = ucfirst($txtTime).$txtDate.$txtDays;
1371
1372
        if (isset($txtYear)) {
1373
            if ($switchDaysOfWeekAreExcluding) {
1374
                $txtResult = ucfirst($txtTime).$txtDate.$txtYear.$txtDays;
1375
            } else {
1376
                $txtResult = ucfirst($txtTime).$txtDate.$txtDays.$txtYear;
1377
            }
1378
        }
1379
1380
        return $txtResult.'.';
1381
    }
1382
}