Added feature #13192: Allow different timestamp as optional parameter in constructor...
[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 * @param integer $tstamp: optional start time
77 * @return void
78 */
79 public function __construct($cmd, $tstamp = FALSE) {
80 // Explode cmd in sections
81 $this->cmd_sections = t3lib_div::trimExplode(' ', $cmd);
82
83 // Initialize the values with the starting time
84 // This takes care that the calculated time is always in the future
85 if ($tstamp === FALSE) {
86 $tstamp = strtotime('+1 minute');
87 }
88 $this->values = array(
89 // Minute
90 intval(date('i', $tstamp)),
91 // Hour
92 date('G', $tstamp),
93 // Day
94 date('j', $tstamp),
95 // Month
96 date('n', $tstamp),
97 // Year
98 date('Y', $tstamp)
99 );
100
101 // Set valid values
102 $this->valid_values = array(
103 $this->getList($this->cmd_sections[0], 0, 59),
104 $this->getList($this->cmd_sections[1], 0, 23),
105 $this->getDayList($this->values[3], $this->values[4]),
106 $this->getList($this->cmd_sections[3], 1, 12),
107 $this->getList('*', date('Y', $tstamp), intval(date('Y', $tstamp))+1)
108 );
109 }
110
111 /**
112 * Calulates the next execution
113 *
114 * @param integer $level: number of the current level, e.g. 2 is the day level
115 * @return void
116 */
117 public function calculateNextValue($level) {
118 if (isset($this->values[$level])) {
119 $current_value = &$this->values[$level];
120 $next_level = $level + 1;
121
122 if (in_array($current_value, $this->valid_values[$level])) {
123 $this->calculateNextValue($next_level);
124 } else {
125 $next_value = $this->getNextValue($this->values[$level], $this->valid_values[$level]);
126 if ($next_value === false) {
127 // Set this value and prior values to the start value
128 for ($i = $level; $i >= 0; $i--) {
129 $this->values[$i] = $this->valid_values[$i][0];
130
131 // Update day list if month was changed
132 if ($i == 3) {
133 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
134 }
135 }
136
137 // Calculate next value for the next value
138 for ($i = $next_level; $i <= count($this->values); $i++) {
139 if (isset($this->values[$i])) {
140 $increased_value = $this->getNextValue($this->values[$i], $this->valid_values[$i]);
141
142 if ($increased_value !== false) {
143 $this->values[$i] = $increased_value;
144
145 // Update day list if month was changed
146 if ($i == 3) {
147 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
148
149 // Check if day had already a valid start value, if not set a new one
150 if (!$this->values[2] || !in_array($this->values[2], $this->valid_values[2])) {
151 $this->values[2] = $this->valid_values[2][0];
152 }
153 }
154
155 break;
156 } else {
157 $this->values[$i] = $this->valid_values[$i][0];
158
159 // Update day list if month was changed
160 if ($i == 3) {
161 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]+1);
162 }
163 }
164 }
165 }
166
167 $this->calculateNextValue($next_level);
168 } else {
169 if ($level == 3) {
170 // Update day list if month was changed
171 $this->valid_values[2] = $this->getDayList($this->values[3], $this->values[4]);
172 }
173
174 $current_value = $next_value;
175 $this->calculateNextValue($next_level);
176 }
177 }
178 }
179 }
180
181 /**
182 * Builds a list of days for a certain month
183 *
184 * @param integer $currentMonth: number of a month
185 * @param integer $currentYear: a year
186 * @return array list of days
187 */
188 protected function getDayList($currentMonth, $currentYear) {
189 // Create a dummy timestamp at 6:00 of the first day of the current month and year
190 // to get the number of days in the month using date()
191 $dummyTimestamp = mktime(6, 0, 0, $currentMonth, 1, $currentYear);
192 $max_days = date('t', $dummyTimestamp);
193 $validDays = $this->getList($this->cmd_sections[2], 1, $max_days);
194
195 // Consider special field 'day of week'
196 if ($this->cmd_sections[2] != '*' && (strpos($this->cmd_sections[4], '*') === false && preg_match('/[1-7]{1}/', $this->cmd_sections[4]) !== false)) {
197 // Get list
198 for ($i = 1; $i <= $max_days; $i++) {
199 if (strftime('%u', mktime(0, 0, 0, $currentMonth, $i, $currentYear)) == $this->cmd_sections[4]) {
200 if (!in_array($i, $validDays)) {
201 $validDays[] = $i;
202 }
203 }
204 }
205 }
206 sort($validDays);
207
208 return $validDays;
209 }
210
211 /**
212 * Builds a list of possible values from a cron command
213 *
214 * @param string $definition: the command e.g. 2-8, *, 0-59/20
215 * @param integer $min: minimum allowed value
216 * @param integer $max: maximum allowed value
217 * @return array list with possible values
218 */
219 protected function getList($definition, $min, $max) {
220 $list = array();
221
222 if ($definition == '*') {
223 // Get list for the asterix
224 for ($tmp = $min; $tmp <= $max; $tmp++) {
225 $list[] = $tmp;
226 }
227 } else if (strpos($definition, '/') !== false) {
228 // Get list for step values
229
230 // Extract list part
231 $defList = substr($definition, 0, strpos($definition, '/'));
232 $stepDef = substr($definition, strpos($definition, '/') + 1);
233 $tmpList = $this->getList($defList, $min, $max);
234
235 for ($i=0; $i<count($tmpList); $i++) {
236 if ($i % $stepDef == 0) {
237 $list[] = $tmpList[$i];
238 }
239 }
240 } else if (strpos($definition, ',') !== false) {
241 // Get list for list definitions
242
243 $defList = explode(',', $definition);
244 foreach ($defList as $listItem) {
245 $tmpList = $this->getList($listItem, $min, $max);
246 $list = array_merge($list, $tmpList);
247 }
248 } else if (strpos($definition, '-') !== false) {
249 // Get list for range definitions
250
251 // Get list definition parts
252 list($def_min, $def_max) = t3lib_div::trimExplode('-', $definition);
253
254 if ($def_min < $min) {
255 $def_min = $min;
256 }
257
258 if ($def_max > $max) {
259 $def_max = $max;
260 }
261
262 $list = $this->getList('*', $def_min, $def_max);
263 } else if (is_numeric($definition) && $definition >= $min && $definition <= $max) {
264 // Get list for single values
265 $list[] = intval($definition);
266 }
267
268 // Sort the list and return it
269 sort($list);
270 return $list;
271 }
272
273 /**
274 * Returns the first value that is higher than the current value
275 * from a list of possible values
276 *
277 * @param mixed $currentValue: the value to be searched in the list
278 * @param array $listArray: the list of values
279 * @return mixed The value from the list right after the current value
280 */
281 public function getNextValue($currentValue, array $listArray) {
282 $next_value = false;
283
284 $numValues = count($listArray);
285 for ($i = 0; $i < $numValues; $i++) {
286 if ($listArray[$i] > $currentValue) {
287 $next_value = $listArray[$i];
288 break;
289 }
290 }
291
292 return $next_value;
293 }
294
295 /**
296 * Returns the timestamp for the value parts in $this->values
297 *
298 * @return integer unix timestamp
299 */
300 public function getTstamp() {
301 return mktime($this->values[1], $this->values[0], 0, $this->values[3], $this->values[2], $this->values[4]);
302 }
303 }
304
305 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php']) {
306 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/scheduler/class.tx_scheduler_croncmd.php']);
307 }
308
309 ?>