1
|
|
|
<?php |
2
|
|
|
namespace Agavi\Validator; |
3
|
|
|
|
4
|
|
|
// +---------------------------------------------------------------------------+ |
5
|
|
|
// | This file is part of the Agavi package. | |
6
|
|
|
// | Copyright (c) 2005-2011 the Agavi Project. | |
7
|
|
|
// | | |
8
|
|
|
// | For the full copyright and license information, please view the LICENSE | |
9
|
|
|
// | file that was distributed with this source code. You can also view the | |
10
|
|
|
// | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
11
|
|
|
// | vi: set noexpandtab: | |
12
|
|
|
// | Local Variables: | |
13
|
|
|
// | indent-tabs-mode: t | |
14
|
|
|
// | End: | |
15
|
|
|
// +---------------------------------------------------------------------------+ |
16
|
|
|
use Agavi\Config\Config; |
17
|
|
|
use Agavi\Date\Calendar; |
18
|
|
|
use Agavi\Date\DateDefinitions; |
19
|
|
|
use Agavi\Date\DateFormat; |
20
|
|
|
use Agavi\Exception\AgaviException; |
21
|
|
|
use Agavi\Exception\ConfigurationException; |
22
|
|
|
use Agavi\Exception\ValidatorException; |
23
|
|
|
use Agavi\Translation\DateFormatter; |
24
|
|
|
use Agavi\Translation\Locale; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* DateTimeValidator verifies that a parameter is of a date and or time |
28
|
|
|
* format. |
29
|
|
|
* |
30
|
|
|
* Arguments: |
31
|
|
|
* This can be: |
32
|
|
|
* * a single argument which will then be parsed with the formats in the |
33
|
|
|
* 'formats' parameter. |
34
|
|
|
* * multiple arguments with the calendar constants |
35
|
|
|
* (AgaviDateDefinitions::MONTH, etc) as key and the argument field as |
36
|
|
|
* value. |
37
|
|
|
* * multiple arguments and the 'arguments_format' parameter defined. This |
38
|
|
|
* will use the string in 'arguments_format' as input string to sprintf and |
39
|
|
|
* will use the arguments in the given order as argument to sprintf. |
40
|
|
|
* |
41
|
|
|
* Parameters: |
42
|
|
|
* 'check' check date if the specified day really exists |
43
|
|
|
* 'formats' an array of arrays with these keys: |
44
|
|
|
* 'type' The type of the string in 'format'. |
45
|
|
|
* 'format' The input string dependent on the type. These types are |
46
|
|
|
* allowed: |
47
|
|
|
* format: The value is a date format string. |
48
|
|
|
* time: The value is a time specifier (full,...) or null |
49
|
|
|
* date: The value is a date specifier or null |
50
|
|
|
* datetime: The value is a date specifier or null |
51
|
|
|
* translation_domain: The value will be translated in the |
52
|
|
|
* domain given in the 'translation_domain' key. |
53
|
|
|
* unix: Always null/empty |
54
|
|
|
* unix_milliseconds: Always null/empty |
55
|
|
|
* |
56
|
|
|
* 'locale' The optional locale which will be used for this format. |
57
|
|
|
* 'translation_domain' Only applicable when the type is translation_domain |
58
|
|
|
* 'cast_to' Only useful in combination with the export parameter. |
59
|
|
|
* This can either be a string or an array. If its an string it |
60
|
|
|
* can be one of 'unix' (converts the date to a unix timestamp), |
61
|
|
|
* 'string' (converts it to a string using the default format), |
62
|
|
|
* 'calendar' (will return the AgaviCalendar object), |
63
|
|
|
* 'datetime' (case sensitive, will return a PHP DateTime |
64
|
|
|
* object, requires PHP 5.1.x with DateTime explicitly enabled |
65
|
|
|
* or >= PHP 5.2). |
66
|
|
|
* If it's an array it can have these keys: |
67
|
|
|
* 'type' The type of the format (format, time, date, datetime) |
68
|
|
|
* 'format' see in 'formats' above. |
69
|
|
|
* 'arguments_format' A string which will be used as the format string for |
70
|
|
|
* sprintf. |
71
|
|
|
* 'min' Either an string or an array. When its a string the the |
72
|
|
|
* its assumed to be in the format 'yyyy-MM-dd[ HH:mm:ss[.S]]'. |
73
|
|
|
* When its an array it will take the minimum value from a |
74
|
|
|
* request field. These indizes apply: |
75
|
|
|
* 'format' A custom format string which should be used when the field |
76
|
|
|
* is an string. |
77
|
|
|
* 'field' The name of the field to use as minimum value (could be a |
78
|
|
|
* previous exported calendar object). Do NOT use unvalidated |
79
|
|
|
* fields here. Lax parsing will be used. |
80
|
|
|
* This value is inclusive. |
81
|
|
|
* 'max' The same as min except that the max is exclusive. |
82
|
|
|
* |
83
|
|
|
* @package agavi |
84
|
|
|
* @subpackage validator |
85
|
|
|
* |
86
|
|
|
* @author Dominik del Bondio <[email protected]> |
87
|
|
|
* @copyright Authors |
88
|
|
|
* @copyright The Agavi Project |
89
|
|
|
* |
90
|
|
|
* @since 0.11.0 |
91
|
|
|
* |
92
|
|
|
* @version $Id$ |
93
|
|
|
*/ |
94
|
|
|
class DateTimeValidator extends Validator |
95
|
|
|
{ |
96
|
|
|
/** |
97
|
|
|
* Validates the input. |
98
|
|
|
* |
99
|
|
|
* @return bool True if the input was a valid date. |
100
|
|
|
* |
101
|
|
|
* @author Dominik del Bondio <[email protected]> |
102
|
|
|
* @since 0.11.0 |
103
|
|
|
*/ |
104
|
|
|
protected function validate() |
105
|
|
|
{ |
106
|
|
|
if (!Config::get('core.use_translation')) { |
107
|
|
|
throw new ConfigurationException('The datetime validator can only be used with use_translation on'); |
108
|
|
|
} |
109
|
|
|
$tm = $this->getContext()->getTranslationManager(); |
110
|
|
|
$cal = null; |
111
|
|
|
|
112
|
|
|
$check = $this->getParameter('check', true); |
113
|
|
|
$locale = $this->hasParameter('locale') ? $tm->getLocale($this->getParameter('locale')) : $tm->getCurrentLocale(); |
114
|
|
|
|
115
|
|
|
if ($this->hasMultipleArguments() && !$this->getParameter('arguments_format')) { |
116
|
|
|
$cal = $tm->createCalendar(); |
117
|
|
|
$cal->clear(); |
118
|
|
|
$cal->setLenient(!$check); |
119
|
|
|
foreach ($this->getArguments() as $calField => $field) { |
120
|
|
|
$param = $this->getData($field); |
121
|
|
|
if (defined($calField)) { |
122
|
|
|
$calField = constant($calField); |
123
|
|
|
} elseif (!is_numeric($calField)) { |
124
|
|
|
throw new ValidatorException('Unknown argument name "' . $calField . '" for argument "' . $field . '" supplied. This needs to be one of the constants defined in AgaviDateDefinitions.'); |
125
|
|
|
} |
126
|
|
|
if (!is_scalar($param)) { |
127
|
|
|
// everything which is non scalar is ignored, since it couldn't be handled anyways |
128
|
|
|
continue; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
if ($calField == DateDefinitions::MONTH) { |
132
|
|
|
$param -= 1; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$cal->set($calField, (float) $param); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
try { |
139
|
|
|
$cal->getTime(); |
140
|
|
|
} catch (AgaviException $e) { |
141
|
|
|
$this->throwError('check'); |
142
|
|
|
return false; |
143
|
|
|
} |
144
|
|
|
} else { |
145
|
|
|
if ($argFormat = $this->getParameter('arguments_format')) { |
146
|
|
|
$values = array(); |
147
|
|
|
foreach ($this->getArguments() as $field) { |
148
|
|
|
$values[] = $this->getData($field); |
149
|
|
|
} |
150
|
|
|
$param = vsprintf($argFormat, $values); |
151
|
|
|
} else { |
152
|
|
|
$param = $this->getData($this->getArgument()); |
153
|
|
|
if (!is_scalar($param)) { |
154
|
|
|
$this->throwError(); |
155
|
|
|
return false; |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$matchedFormat = false; |
160
|
|
|
foreach ((array)$this->getParameter('formats', array()) as $key => $item) { |
161
|
|
|
if (!is_array($item)) { |
162
|
|
|
$item = array((is_int($key) ? 'format' : $key) => $item); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
$itemLocale = empty($item['locale']) ? $locale : $tm->getLocale($item['locale']); |
166
|
|
|
$type = empty($item['type']) ? 'format' : $item['type']; |
167
|
|
|
|
168
|
|
|
if ($type == 'format') { |
169
|
|
|
$formatString = $item['format']; |
170
|
|
View Code Duplication |
} elseif ($type == 'time' || $type == 'date' || $type == 'datetime') { |
|
|
|
|
171
|
|
|
$format = isset($item['format']) ? $item['format'] : null; |
172
|
|
|
$formatString = DateFormatter::resolveFormat($format, $itemLocale, $type); |
173
|
|
|
} elseif ($type == 'translation_domain') { |
174
|
|
|
$td = $item['translation_domain']; |
175
|
|
|
$formatString = $tm->_($item['format'], $td, $itemLocale); |
176
|
|
|
} elseif ($type == 'unix') { |
177
|
|
|
$matchedFormat = ($param === (string)(int)$param); |
178
|
|
|
$cal = $tm->createCalendar($itemLocale); |
179
|
|
|
$cal->setUnixTimestamp($param); |
180
|
|
View Code Duplication |
if ($matchedFormat) { |
|
|
|
|
181
|
|
|
try { |
182
|
|
|
if ($cal->getUnixTimestamp() !== (int)$param) { |
183
|
|
|
$this->throwError('check'); |
184
|
|
|
return false; |
185
|
|
|
} |
186
|
|
|
} catch (AgaviException $e) { |
187
|
|
|
$matchedFormat = false; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
} elseif ($type == 'unix_milliseconds') { |
191
|
|
|
$matchedFormat = is_numeric($param); |
192
|
|
|
$cal = $tm->createCalendar($itemLocale); |
193
|
|
|
$cal->setTime($param); |
194
|
|
View Code Duplication |
if ($matchedFormat) { |
|
|
|
|
195
|
|
|
try { |
196
|
|
|
if ($cal->getTime() !== (float)$param) { |
197
|
|
|
$this->throwError('check'); |
198
|
|
|
return false; |
199
|
|
|
} |
200
|
|
|
} catch (AgaviException $e) { |
201
|
|
|
$matchedFormat = false; |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
if (!$cal) { |
207
|
|
|
try { |
208
|
|
|
$format = new DateFormat($formatString); |
|
|
|
|
209
|
|
|
$cal = $format->parse($param, $itemLocale, $check); |
210
|
|
|
|
211
|
|
|
// no exception got thrown so the parsing was successful |
212
|
|
|
$matchedFormat = true; |
213
|
|
|
break; |
214
|
|
|
} catch (AgaviException $e) { |
215
|
|
|
// nop |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (!$matchedFormat) { |
221
|
|
|
$this->throwError('format'); |
222
|
|
|
return false; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
$cal->setLenient(true); |
227
|
|
|
$value = $cal; |
228
|
|
|
|
229
|
|
|
if ($cast = $this->getParameter('cast_to')) { |
230
|
|
|
// an array means the user wants it custom formatted |
231
|
|
|
if (is_array($cast)) { |
232
|
|
|
$type = empty($cast['type']) ? 'format' : $cast['type']; |
233
|
|
|
if ($type == 'format') { |
234
|
|
|
$formatString = $cast['format']; |
235
|
|
View Code Duplication |
} elseif ($type == 'time' || $type == 'date' || $type == 'datetime') { |
|
|
|
|
236
|
|
|
$format = isset($cast['format']) ? $cast['format'] : null; |
237
|
|
|
$formatString = DateFormatter::resolveFormat($format, $locale, $type); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$format = new DateFormat($formatString); |
241
|
|
|
$value = $format->format($cal, $cal->getType(), $locale); |
242
|
|
|
} else { |
243
|
|
|
$cast = strtolower($cast); |
244
|
|
|
if ($cast == 'unix') { |
245
|
|
|
$value = $cal->getUnixTimestamp(); |
246
|
|
|
} elseif ($cast == 'string') { |
247
|
|
|
$value = $tm->_d($cal); |
248
|
|
|
} elseif ($cast == 'datetime') { |
249
|
|
|
$value = $cal->getNativeDateTime(); |
250
|
|
|
} else { |
251
|
|
|
$value = $cal; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
$defaultParseFormat = new DateFormat('yyyy-MM-dd HH:mm:ss.S'); |
257
|
|
|
|
258
|
|
|
if ($this->hasParameter('min')) { |
259
|
|
|
$min = $this->getMinOrMaxValue('min', $defaultParseFormat, $locale); |
260
|
|
|
|
261
|
|
|
$isAfterEqual = $cal->after($min) || $cal->equals($min); |
262
|
|
|
if (!$isAfterEqual) { |
263
|
|
|
$this->throwError('min'); |
264
|
|
|
return false; |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
if ($this->hasParameter('max')) { |
269
|
|
|
$max = $this->getMinOrMaxValue('max', $defaultParseFormat, $locale); |
270
|
|
|
|
271
|
|
|
$isBefore = $cal->before($max); |
272
|
|
|
if (!$isBefore) { |
273
|
|
|
$this->throwError('max'); |
274
|
|
|
return false; |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
if ($this->hasParameter('export')) { |
279
|
|
|
$export = $this->getParameter('export'); |
280
|
|
|
if (is_string($export)) { |
281
|
|
|
$this->export($value); |
282
|
|
|
} elseif (is_array($export)) { |
283
|
|
|
foreach ($export as $calField => $field) { |
284
|
|
|
if (defined($calField)) { |
285
|
|
|
$this->export($cal->get(constant($calField)), $field); |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return true; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Returns the calendar object for a max or min definition. |
296
|
|
|
* |
297
|
|
|
* @param string $minMax 'min' or 'max' |
298
|
|
|
* @param DateFormat $defaultParseFormat The default format when parsing strings. |
299
|
|
|
* @param Locale $locale The locale to use. |
300
|
|
|
* |
301
|
|
|
* @return Calendar The calendar object storing the date. |
302
|
|
|
* |
303
|
|
|
* @author Dominik del Bondio <[email protected]> |
304
|
|
|
* @since 0.11.0 |
305
|
|
|
*/ |
306
|
|
|
protected function getMinOrMaxValue($minMax, $defaultParseFormat, $locale) |
307
|
|
|
{ |
308
|
|
|
$format = $defaultParseFormat; |
309
|
|
|
|
310
|
|
|
$minMax = $this->getParameter($minMax); |
311
|
|
|
if (is_array($minMax)) { |
312
|
|
|
$minMaxValue = $this->validationParameters->getParameter($minMax['field']); |
313
|
|
|
if (!$minMaxValue instanceof Calendar) { |
314
|
|
|
if (isset($minMax['format'])) { |
315
|
|
|
$format = new DateFormat($minMax['format']); |
316
|
|
|
} |
317
|
|
|
$result = $format->parse($minMaxValue, $locale, false); |
318
|
|
|
} else { |
319
|
|
|
$result = $minMaxValue; |
320
|
|
|
} |
321
|
|
|
} elseif (strpos($minMax, '.') === false) { |
322
|
|
|
// a strtotime compatible string does not contain a dot, so all strings with dots are assumed to be |
323
|
|
|
// strings matching the calendar format and all others are handled with strtotime |
324
|
|
|
$tz = $locale->getLocaleTimeZone(); |
325
|
|
|
if ($tz) { |
326
|
|
|
// set the timezone for the strtotime call to be the same as for creating the calendar |
327
|
|
|
$oldDefaultTimezone = date_default_timezone_get(); |
328
|
|
|
date_default_timezone_set($tz); |
329
|
|
|
} |
330
|
|
|
// create the calendar in the requested locale/timezone |
331
|
|
|
$result = $this->getContext()->getTranslationManager()->createCalendar($locale); |
332
|
|
|
$result->setUnixTimestamp(strtotime($minMax)); |
333
|
|
|
if ($tz) { |
334
|
|
|
// reset the php timezone |
335
|
|
|
date_default_timezone_set($oldDefaultTimezone); |
|
|
|
|
336
|
|
|
} |
337
|
|
|
} else { |
338
|
|
|
$result = $format->parse($minMax, $locale, false); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
return $result; |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.