Fixed bug #13191: Scheduler fails with fatal error if using intervals in cron command
[Packages/TYPO3.CMS.git] / typo3 / sysext / scheduler / class.tx_scheduler_croncmd.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2009 Markus Friedrich (markus.friedrich@dkd.de)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25
26 /**
27 * This class provides calulations for the cron command format
28 *
29 * @author Markus Friedrich <markus.friedrich@dkd.de>
30 * @package TYPO3
31 * @subpackage tx_scheduler
32 *
33 * $Id$
34 */
35 class tx_scheduler_CronCmd {
36
37 /**
38 * Sections of the cron command
39 *
40 * field allowed values
41 * ----- --------------
42 * minute 0-59
43 * hour 0-23
44 * day of month 1-31
45 * month 1-12 (or names, see below)
46 * day of week 0-7 (0 or 7 is Sun, or use names)
47 *
48 * @var array $cmd_sections
49 */
50 public $cmd_sections;
51
52 /**
53 * Valid values for each part
54 *
55 * @var array $valid_values
56 */
57 public $valid_values;
58
59 /**
60 * Array containing the values to build the new execution date
61 *
62 * 0 => minute
63 * 1 => hour
64 * 2 => day
65 * 3 => month
66 * 4 => year
67 *
68 * @var array $values
69 */
70 public $values;
71
72 /**
73 * Constructor
74 *
75 * @param string $cmd: the cron command
76 * @return void
77 */
78 public function __construct($cmd) {
79 // Explode cmd in sections
80 $this->cmd_sections = t3lib_div::trimExplode(' ', $cmd);
81
82 // Initialize the values with the starting time
83 // This takes care that the calculated time is always in the future
84 $tstamp = strtotime('+1 minute');
85 $this->values = array(
86 // Minute
87 intval(date('i', $tstamp)),
88 // Hour
89 date('G', $tstamp),
90 // Day
91 date('j', $tstamp),
92 // Month
93 date('n', $tstamp),
94 // Year
95 date('Y', $tstamp)
96 );
97
98 // Set valid values
99 $this->valid_values = array(
100 $this->getList($this->cmd_sections[0], 0, 59),
101 $this->getList($this->cmd_sections[1], 0, 23),
102 $this->getDayList($this->values[3], $this->values[4]),
103 $this->getList($this->cmd_sections[3], 1, 12),
104 $this->getList('*', date('Y', $tstamp), intval(date('Y', $tstamp))+1)
105 );
106 }
107
108 /**
109 * Calulates the next execution
110 *
111 * @param integer $level: number of the current level, e.g. 2 is the day level
112 * @return void
113 */
114 public function calculateNextValue($level) {
115 if (isset($this->values[$level])) {
116 $current_value = &$this->values[$level];
117 $next_level = $level + 1;
118
119 if (in_array($current_value, $this->valid_values[$level])) {
120 $this->calculateNextValue($next_level);
121 } else {
122 $next_value = $this->getNextValue($this->values[$level], $this->valid_values[$level]);
123 if ($next_value === false) {
124 // Set this value and prior values to the start value
125 for ($i = $level; $i >= 0; $i--) {
126 $this->values[$i] = $this->valid_values[$i][0];
127
128 // Update day list if month was changed
129 if ($i == 3) {
130 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
131 }
132 }
133
134 // Calculate next value for the next value
135 for ($i = $next_level; $i <= count($this->values); $i++) {
136 if (isset($this->values[$i])) {
137 $increased_value = $this->getNextValue($this->values[$i], $this->valid_values[$i]);
138
139 if ($increased_value !== false) {
140 $this->values[$i] = $increased_value;
141
142 // Update day list if month was changed
143 if ($i == 3) {
144 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
145
146 // Check if day had already a valid start value, if not set a new one
147 if (!$this->values[2] || !in_array($this->values[2], $this->valid_values[2])) {
148 $this->values[2] = $this->valid_values[2][0];
149 }
150 }
151
152 break;
153 } else {
154 $this->values[$i] = $this->valid_values[$i][0];
155
156 // Update day list if month was changed
157 if ($i == 3) {
158 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]+1);
159 }
160 }
161 }
162 }
163
164 $this->calculateNextValue($next_level);
165 } else {
166 if ($level == 3) {
167 // Update day list if month was changed
168 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
169 }
170
171 $current_value = $next_value;
172 $this->calculateNextValue($next_level);
173 }
174 }
175 }
176 }
177
178 /**
179 * Builds a list of days for a certain month
180 *
181 * @param integer $currentMonth: number of a month
182 * @param integer $currentYear: a year
183 * @return array list of days
184 */
185 protected function getDayList($currentMonth, $currentYear) {
186 // Create a dummy timestamp at 6:00 of the first day of the current month and year
187 // to get the number of days in the month using date()
188 $dummyTimestamp = mktime(6, 0, 0, $currentMonth, 1, $currentYear);
189 $max_days = date('t', $dummyTimestamp);
190 $validDays = $this->getList($this->cmd_sections[2], 1, $max_days);
191
192 // Consider special field 'day of week'
193 if ($this->cmd_sections[2] != '*' && (strpos($this->cmd_sections[4], '*') === false && preg_match('/[1-7]{1}/', $this->cmd_sections[4]) !== false)) {
194 // Get list
195 for ($i = 1; $i <= $max_days; $i++) {
196 if (strftime('%u', mktime(0, 0, 0, $currentMonth, $i, $currentYear)) == $this->cmd_sections[4]) {
197 if (!in_array($i, $validDays)) {
198 $validDays[] = $i;
199 }
200 }
201 }
202 }
203 sort($validDays);
204
205 return $validDays;
206 }
207
208 /**
209 * Builds a list of possible values from a cron command
210 *
211 * @param string $definition: the command e.g. 2-8, *, 0-59/20
212 * @param integer $min: minimum allowed value
213 * @param integer $max: maximum allowed value
214 * @return array list with possible values
215 */
216 protected function getList($definition, $min, $max) {
217 $list = array();
218
219 if ($definition == '*') {
220 // Get list for the asterix
221 for ($tmp = $min; $tmp <= $max; $tmp++) {
222 $list[] = $tmp;
223 }
224 } else if (strpos($definition, '/') !== false) {
225 // Get list for step values
226
227 // Extract list part
228 $defList = substr($definition, 0, strpos($definition, '/'));
229 $stepDef = substr($definition, strpos($definition, '/') + 1);
230 $tmpList = $this->getList($defList, $min, $max);
231
232 for ($i=0; $i<count($tmpList); $i++) {
233 if ($i % $stepDef == 0) {
234 $list[] = $tmpList[$i];
235 }
236 }
237 } else if (strpos($definition, ',') !== false) {
238 // Get list for list definitions
239
240 $defList = explode(',', $definition);
241 foreach ($defList as $listItem) {
242 $tmpList = $this->getList($listItem, $min, $max);
243 $list = array_merge($list, $tmpList);
244 }
245 } else if (strpos($definition, '-') !== false) {
246 // Get list for range definitions
247
248 // Get list definition parts
249 list($def_min, $def_max) = t3lib_div::trimExplode('-', $definition);
250
251 if ($def_min < $min) {
252 $def_min = $min;
253 }
254
255 if ($def_max > $max) {
256 $def_max = $max;
257 }
258
259 $list = $this->getList('*', $def_min, $def_max);
260 } else if (is_numeric($definition) && $definition >= $min && $definition <= $max) {
261 // Get list for single values
262 $list[] = intval($definition);
263 }
264
265 // Sort the list and return it
266 sort($list);
267 return $list;
268 }
269
270 /**
271 * Returns the first value that is higher than the current value
272 * from a list of possible values
273 *
274 * @param mixed $currentValue: the value to be searched in the list
275 * @param array $listArray: the list of values
276 * @return mixed The value from the list right after the current value
277 */
278 public function getNextValue($currentValue, array $listArray) {
279 $next_value = false;
280
281 $numValues = count($listArray);
282 for ($i = 0; $i < $numValues; $i++) {
283 if ($listArray[$i] > $currentValue) {
284 $next_value = $listArray[$i];
285 break;
286 }
287 }
288
289 return $next_value;
290 }
291
292 /**
293 * Returns the timestamp for the value parts in $this->values
294 *
295 * @return integer unix timestamp
296 */
297 public function getTstamp() {
298 return mktime($this->values[1], $this->values[0], 0, $this->values[3], $this->values[2], $this->values[4]);
299 }
300 }
301
302 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php']) {
303 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php']);
304 }
305
306 ?>