1 <?php
2 require_once 'Zend/Date.php';
3
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
48 class DateField extends TextField {
49
50 51 52
53 protected $config = array(
54 'showcalendar' => false,
55 'dmyfields' => false,
56 'dmyseparator' => ' <span class="separator">/</span> ',
57 'dateformat' => null,
58 'datavalueformat' => 'yyyy-MM-dd',
59 'min' => null,
60 'max' => null
61 );
62
63 64 65
66 protected $locale = null;
67
68 69 70 71 72
73 protected $valueObj = null;
74
75 function __construct($name, $title = null, $value = null, $form = null, $rightTitle = null) {
76 if(!$this->locale) {
77 $this->locale = i18n::get_locale();
78 }
79
80 if(!$this->getConfig('dateformat')) {
81 $this->setConfig('dateformat', Zend_Locale_Format::getDateFormat($this->locale));
82 }
83 if ($this->useHTML5() && !$this->getConfig('dmyfields')) {
84 $this->setConfig('dateformat', 'yyyy-MM-dd');
85 }
86
87 parent::__construct($name, $title, $value, $form, $rightTitle);
88 }
89
90 function setMin($value) {
91 $this->setHTML5Attribute('min', $value);
92 }
93
94 function setMax($value) {
95 $this->setHTML5Attribute('max', $value);
96 }
97
98
99 function ShowCalendar() {
100 return !$this->useHTML5() && $this->getConfig('showcalendar');
101 }
102
103 function FieldHolder() {
104 return parent::FieldHolder();
105 }
106
107 function Field() {
108
109 if($this->getConfig('dmyfields')) {
110
111 $valArr = ($this->valueObj) ? $this->valueObj->toArray() : null;
112
113
114 $fieldDay = new NumericField($this->name . '[day]', false, ($valArr) ? $valArr['day'] : null);
115 $fieldDay->addExtraClass('day');
116 $fieldDay->setMaxLength(2);
117 $fieldMonth = new NumericField($this->name . '[month]', false, ($valArr) ? $valArr['month'] : null);
118 $fieldMonth->addExtraClass('month');
119 $fieldMonth->setMaxLength(2);
120 $fieldYear = new NumericField($this->name . '[year]', false, ($valArr) ? $valArr['year'] : null);
121 $fieldYear->addExtraClass('year');
122 $fieldYear->setMaxLength(4);
123
124
125 $sep = $this->getConfig('dmyseparator');
126 $format = $this->getConfig('dateformat');
127 $fields = array();
128 $fields[stripos($format, 'd')] = $fieldDay->Field();
129 $fields[stripos($format, 'm')] = $fieldMonth->Field();
130 $fields[stripos($format, 'y')] = $fieldYear->Field();
131 ksort($fields);
132 $html = implode($sep, $fields);
133 }
134
135 else {
136 if ($this->useHTML5()) {
137 $this->setHTML5Attribute('type', 'date');
138 if ($min = $this->getConfig('min'))
139 $this->setMin(date('Y-m-d', strtotime($min)));
140 if ($max = $this->getConfig('max'))
141 $this->setMax('man', date('Y-m-d', strtotime($man)));
142 }
143 $html = parent::Field();
144 }
145
146 $html = $this->FieldDriver($html);
147
148
149 if($this->ShowCalendar()) $html = sprintf('<div class="calendardate">%s</div>', $html);
150
151 return $html;
152 }
153
154 155 156 157 158 159 160 161
162 protected function FieldDriver($html) {
163
164
165
166 if($this->ShowCalendar()) {
167 Requirements::javascript(THIRDPARTY_DIR . '/prototype/prototype.js');
168 Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
169 Requirements::javascript(THIRDPARTY_DIR . "/calendar/calendar.js");
170 $lang = i18n::get_lang_from_locale($this->getLocale());
171 Requirements::javascript(THIRDPARTY_DIR . "/calendar/lang/calendar-$lang.js");
172 Requirements::javascript(THIRDPARTY_DIR . "/calendar/calendar-setup.js");
173 Requirements::css(SAPPHIRE_DIR . "/css/DateField.css");
174 Requirements::css(THIRDPARTY_DIR . "/calendar/calendar-win2k-1.css");
175
176 $html .= sprintf('<img src="sapphire/images/calendar-icon.gif" id="%s-icon" alt="Calendar icon" />', $this->id());
177 $html .= sprintf('<div class="calendarpopup" id="%s-calendar"></div>', $this->id());
178 }
179
180 return $html;
181 }
182
183 184 185 186 187
188 function setValue($val) {
189 if(empty($val)) {
190 $this->value = null;
191 $this->valueObj = null;
192 } else {
193 if($this->getConfig('dmyfields')) {
194
195 if(is_array($val) && $this->validateArrayValue($val)) {
196
197 $this->valueObj = new Zend_Date($val, null, $this->locale);
198 $this->value = $this->valueObj->toArray();
199 }
200
201 else if(!empty($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'))) {
202 $this->valueObj = new Zend_Date($val, $this->getConfig('datavalueformat'));
203 $this->value = $this->valueObj->toArray();
204 }
205 else {
206 $this->value = $val;
207 $this->valueObj = null;
208 }
209 } else {
210
211
212
213
214 if(!empty($val) && Zend_Date::isDate($val, $this->getConfig('dateformat'), $this->locale)) {
215 $this->valueObj = new Zend_Date($val, $this->getConfig('dateformat'), $this->locale);
216 $this->value = $this->valueObj->get($this->getConfig('dateformat'));
217 }
218
219 else if(!empty($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'))) {
220 $this->valueObj = new Zend_Date($val, $this->getConfig('datavalueformat'));
221 $this->value = $this->valueObj->get($this->getConfig('dateformat'));
222 }
223 else {
224 $this->value = $val;
225 $this->valueObj = null;
226 }
227 }
228 }
229 }
230
231 232 233
234 function dataValue() {
235 if($this->valueObj) {
236 return $this->valueObj->toString($this->getConfig('datavalueformat'));
237 } else {
238 return null;
239 }
240 }
241
242 function performReadonlyTransformation() {
243 $field = new DateField_Disabled($this->name, $this->title, $this->value);
244 $field->setForm($this->form);
245 $field->readonly = true;
246
247 return $field;
248 }
249
250 function jsValidation() {
251
252 if($this->getLocale() != 'en_NZ') return;
253
254 $formID = $this->form->FormName();
255
256 if(Validator::get_javascript_validator_handler() == 'none') return true;
257
258 if($this->showSeparateFields) {
259 $error = _t('DateField.VALIDATIONJS', 'Please enter a valid date format (DD/MM/YYYY).');
260 $error = 'Please enter a valid date format (DD/MM/YYYY) from dmy.';
261 $jsFunc =<<<JS
262 Behaviour.register({
263 "#$formID": {
264 validateDMYDate: function(fieldName) {
265 var day_value = \$F(_CURRENT_FORM.elements[fieldName+'[day]']);
266 var month_value = \$F(_CURRENT_FORM.elements[fieldName+'[month]']);
267 var year_value = \$F(_CURRENT_FORM.elements[fieldName+'[year]']);
268
269 // TODO NZ specific
270 var value = day_value + '/' + month_value + '/' + year_value;
271 if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/([0-9][0-9]){1,2}\$/)) {
272 validationError(_CURRENT_FORM.elements[fieldName+'[day]'],"$error","validation",false);
273
274 return false;
275 }
276
277 return true;
278 }
279 }
280 });
281 JS;
282 Requirements :: customScript($jsFunc, 'func_validateDMYDate_'.$formID);
283
284 return <<<JS
285 if(\$('$formID')){
286 if(typeof fromAnOnBlur != 'undefined'){
287 if(fromAnOnBlur.name == '$this->name')
288 \$('$formID').validateDMYDate('$this->name');
289 }else{
290 \$('$formID').validateDMYDate('$this->name');
291 }
292 }
293 JS;
294 } else {
295 $error = _t('DateField.VALIDATIONJS', 'Please enter a valid date format (DD/MM/YYYY).');
296 $jsFunc =<<<JS
297 Behaviour.register({
298 "#$formID": {
299 validateDate: function(fieldName) {
300
301 var el = _CURRENT_FORM.elements[fieldName];
302 if(el)
303 var value = \$F(el);
304
305 if(Element.hasClassName(el, 'dmydate')) {
306 // dmy triple field validation
307 var day_value = \$F(_CURRENT_FORM.elements[fieldName+'[day]']);
308 var month_value = \$F(_CURRENT_FORM.elements[fieldName+'[month]']);
309 var year_value = \$F(_CURRENT_FORM.elements[fieldName+'[year]']);
310
311 // TODO NZ specific
312 var value = day_value + '/' + month_value + '/' + year_value;
313 if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/([0-9][0-9]){1,2}\$/)) {
314 validationError(_CURRENT_FORM.elements[fieldName+'[day]'],"$error","validation",false);
315 return false;
316 }
317 } else {
318 // single field validation
319 if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/[0-90-9]{2,4}\$/)) {
320 validationError(el,"$error","validation",false);
321 return false;
322 }
323 }
324
325 return true;
326 }
327 }
328 });
329 JS;
330 Requirements :: customScript($jsFunc, 'func_validateDate_'.$formID);
331
332 return <<<JS
333 if(\$('$formID')){
334 if(typeof fromAnOnBlur != 'undefined'){
335 if(fromAnOnBlur.name == '$this->name')
336 \$('$formID').validateDate('$this->name');
337 }else{
338 \$('$formID').validateDate('$this->name');
339 }
340 }
341 JS;
342 }
343 }
344
345 346 347 348 349 350 351
352 function validateArrayValue($val) {
353 if(!is_array($val)) return false;
354
355 return (
356 array_key_exists('year', $val)
357 && Zend_Date::isDate($val['year'], 'yyyy', $this->locale)
358 && array_key_exists('month', $val)
359 && Zend_Date::isDate($val['month'], 'MM', $this->locale)
360 && array_key_exists('day', $val)
361 && Zend_Date::isDate($val['day'], 'dd', $this->locale)
362 );
363 }
364
365 366 367
368 function validate($validator) {
369 $valid = true;
370
371
372 if(empty($this->value)) return true;
373
374
375 if($this->getConfig('dmyfields')) {
376 $valid = (!$this->value || $this->validateArrayValue($this->value));
377 } else {
378 $valid = (Zend_Date::isDate($this->value, $this->getConfig('dateformat'), $this->locale));
379 }
380 if(!$valid) {
381 $validator->validationError(
382 $this->name,
383 _t('DateField.VALIDDATEFORMAT', "Please enter a valid date format (DD/MM/YYYY)."),
384 "validation",
385 false
386 );
387 return false;
388 }
389
390
391 if($min = $this->getConfig('min')) {
392
393 if(Zend_Date::isDate($min, $this->getConfig('datavalueformat'))) {
394 $minDate = new Zend_Date($min, $this->getConfig('datavalueformat'));
395 } else {
396 $minDate = new Zend_Date(strftime('%Y-%m-%d', strtotime($min)), $this->getConfig('datavalueformat'));
397 }
398 if(!$this->valueObj->isLater($minDate) && !$this->valueObj->equals($minDate)) {
399 $validator->validationError(
400 $this->name,
401 sprintf(
402 _t('DateField.VALIDDATEMINDATE', "Your date has to be newer or matching the minimum allowed date (%s)"),
403 $minDate->toString($this->getConfig('dateformat'))
404 ),
405 "validation",
406 false
407 );
408 return false;
409 }
410 }
411 if($max = $this->getConfig('max')) {
412
413 if(Zend_Date::isDate($min, $this->getConfig('datavalueformat'))) {
414 $maxDate = new Zend_Date($max, $this->getConfig('datavalueformat'));
415 } else {
416 $maxDate = new Zend_Date(strftime('%Y-%m-%d', strtotime($max)), $this->getConfig('datavalueformat'));
417 }
418 if(!$this->valueObj->isEarlier($maxDate) && !$this->valueObj->equals($maxDate)) {
419 $validator->validationError(
420 $this->name,
421 sprintf(
422 _t('DateField.VALIDDATEMAXDATE', "Your date has to be older or matching the maximum allowed date (%s)"),
423 $maxDate->toString($this->getConfig('dateformat'))
424 ),
425 "validation",
426 false
427 );
428 return false;
429 }
430 }
431
432 return true;
433 }
434
435 436 437
438 function getLocale() {
439 return $this->locale;
440 }
441
442 443 444 445 446
447 function setLocale($locale) {
448 $this->locale = $locale;
449 }
450
451 452 453 454
455 function setConfig($name, $val) {
456 switch($name) {
457 case 'min':
458 $format = $this->getConfig('datavalueformat');
459 if($val && !Zend_Date::isDate($val, $format) && !strtotime($val)) {
460 throw new InvalidArgumentException('Date "%s" is not a valid minimum date format (%s) or strtotime() argument', $val, $format);
461 }
462 break;
463 case 'max':
464 $format = $this->getConfig('datavalueformat');
465 if($val && !Zend_Date::isDate($val, $format) && !strtotime($val)) {
466 throw new InvalidArgumentException('Date "%s" is not a valid maximum date format (%s) or strtotime() argument', $val, $format);
467 }
468 break;
469 }
470
471 $this->config[$name] = $val;
472 }
473
474 475 476 477
478 function getConfig($name) {
479 return $this->config[$name];
480 }
481 }
482
483 484 485 486 487 488
489 class DateField_Disabled extends DateField {
490
491 protected $disabled = true;
492
493 function Field() {
494 if($this->valueObj) {
495 if($this->valueObj->isToday()) {
496 $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat')) . ' ('._t('DateField.TODAY','today').')');
497 } else {
498 $df = new Date($this->name);
499 $df->setValue($this->dataValue());
500 $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat')) . ', ' . $df->Ago());
501 }
502 } else {
503 $val = '<i>('._t('DateField.NOTSET', 'not set').')</i>';
504 }
505
506 return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>";
507 }
508
509 function Type() {
510 return "date_disabled readonly";
511 }
512
513 function jsValidation() {
514 return null;
515 }
516
517 function validate($validator) {
518 return true;
519 }
520 }
521
522 523 524 525 526 527 528 529 530
531 class DateField_View_JQuery {
532
533 protected $field;
534
535 536 537 538
539 static $locale_map = array(
540 'en_GB' => 'en-GB',
541 'en_US' => 'en',
542 'en_NZ' => 'en-GB',
543 'fr_CH' => 'fr-CH',
544 'pt_BR' => 'pt-BR',
545 'sr_SR' => 'sr-SR',
546 'zh_CN' => 'zh-CN',
547 'zh_HK' => 'zh-HK',
548 'zh_TW' => 'zh-TW',
549 );
550
551 552 553
554 function __construct($field) {
555 $this->field = $field;
556 }
557
558 559 560
561 function getField() {
562 return $this->field;
563 }
564
565 566 567
568 function onBeforeRender() {
569 if($this->getField()->ShowCalendar()) {
570
571 $format = self::convert_iso_to_jquery_format($this->getField()->getConfig('dateformat'));
572 $conf = array(
573 'showcalendar' => true,
574 'dateFormat' => $format
575 );
576 $this->getField()->addExtraClass(str_replace('"', '\'', Convert::raw2json($conf)));
577 }
578 }
579
580 581 582 583
584 function onAfterRender($html) {
585 if($this->getField()->ShowCalendar()) {
586 Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
587 Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
588 Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery.ui.all.css');
589 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery.ui.core.js');
590 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery.ui.datepicker.js');
591
592
593 $lang = $this->getLang();
594 if($lang != 'en') {
595
596 Requirements::javascript(
597 sprintf(
598 THIRDPARTY_DIR . '/jquery-ui/i18n/jquery.ui.datepicker-%s.js',
599
600 $lang
601 ));
602 }
603
604 Requirements::javascript(THIRDPARTY_DIR . "/jquery-metadata/jquery.metadata.js");
605 Requirements::javascript(SAPPHIRE_DIR . "/javascript/DateField.js");
606 }
607
608 return $html;
609 }
610
611 612 613 614 615 616
617 protected function getLang() {
618 $locale = $this->getField()->getLocale();
619 if($this->getField()->getConfig('jslocale')) {
620
621 $lang = $this->getField()->getConfig('jslocale');
622 } else if(array_key_exists($locale, self::$locale_map)) {
623
624 $lang = self::$locale_map[$locale];
625 } else {
626
627 $lang = i18n::get_lang_from_locale($locale);
628 }
629
630 return $lang;
631 }
632
633 634 635 636 637 638 639 640 641
642 static function convert_iso_to_jquery_format($format) {
643 $convert = array(
644 '/([^d])d([^d])/' => '$1d$2',
645 '/^d([^d])/' => 'd$1',
646 '/([^d])d$/' => '$1d',
647 '/dd/' => 'dd',
648 '/EEEE/' => 'DD',
649 '/EEE/' => 'D',
650 '/SS/' => '',
651 '/eee/' => 'd',
652 '/e/' => 'N',
653 '/D/' => '',
654 '/w/' => '',
655
656 '/([^M])M([^M])/' => '$1m$2',
657
658 '/^M([^M])/' => 'm$1',
659
660 '/([^M])M$/' => '$1m',
661
662 '/(?<!M)MMM(?!M)/' => 'M',
663
664 '/(?<!M)MM(?!M)/' => 'mm',
665
666 '/MMMM/' => 'MM',
667 '/l/' => '',
668 '/YYYY/' => 'yy',
669 '/yyyy/' => 'yy',
670 '/[^y]yy[^y]/' => 'y',
671 '/a/' => '',
672 '/B/' => '',
673 '/hh/' => '',
674 '/h/' => '',
675 '/([^H])H([^H])/' => '',
676 '/^H([^H])/' => '',
677 '/([^H])H$/' => '',
678 '/HH/' => '',
679
680 '/ss/' => '',
681 '/zzzz/' => '',
682 '/I/' => '',
683 '/ZZZZ/' => '',
684 '/Z/' => '',
685 '/z/' => '',
686 '/X/' => '',
687 '/r/' => '',
688 '/U/' => '',
689 );
690 $patterns = array_keys($convert);
691 $replacements = array_values($convert);
692
693 return preg_replace($patterns, $replacements, $format);
694 }
695 }
696 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-