root/trunk/patForms/Element/Date.php

Revision 321, 26.0 kB (checked in by argh, 3 years ago)

Fixed bugs #171 (Date element accepts invalid dates), #187 (french translations for storage rules) and #182 (switch element does not check for correct value)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2 /**
3  * patForms Date element
4  *
5  * Powerful date element with a very flexible date parser that enables
6  * very intuitive usage of date input uses.
7  *
8  * $Id$
9  *
10  * @access        public
11  * @package        patForms
12  * @subpackage    Element
13  * @author        Sebastian 'The Argh' Mordziol <argh@php-tools.net>
14  */
15
16 /**
17  * Stores the path to the element's folder - needed to include the subclasses
18  */
19  define( 'PATFORMS_ELEMENT_DATE_INSTALL_DIR', dirname( __FILE__ ) );
20
21 /**
22  * Notice: could not use the date set as default for the element, as it does not match
23  *         the specified date format string.
24  */
25  define( 'PATFORMS_ELEMENT_DATE_NOTICE_DATE_PARSE_ERROR', 'patForms:Element:Date:01' );
26
27 /**
28  * Notice: the date string ended unexpectedly - it is too short according to the format.
29  */
30  define( 'PATFORMS_ELEMENT_DATE_NOTICE_DATE_UNEXPECTED_END', 'patForms:Element:Date:02' );
31
32 /**
33  * Error: the specified date string is not a valid strtotime date string.
34  */
35  define( 'PATFORMS_ELEMENT_DATE_ERROR_INVALID_DATE_DEFINITION', 'patForms:Element:Date:04' );
36
37 /**
38  * Error: the PEAR_Calendar class is not installed
39  */
40  define( 'PATFORMS_ELEMENT_DATE_WARNING_CALENDAR_NOT_INSTALLED', 'patForms:Element:Date:05' );
41  
42 /**
43  * Error: the specified date string is not a valid strtotime date string.
44  */
45  define( 'PATFORMS_ELEMENT_DATE_ERROR_PEAR_DATE_NEEDED', 'patForms:Element:Date:05' );
46  @include_once 'Date.php';
47  if( !class_exists( 'Date' ) ) {
48     patErrorManager::raiseError(
49         PATFORMS_ELEMENT_DATE_ERROR_PEAR_DATE_NEEDED,
50         'Date class not found',
51         'The patForms Date element needs the PEAR::Date class to function, but it could not be found.'
52     );
53  }
54
55 /**
56  * Subelement class file, always needed.
57  */
58  require_once PATFORMS_ELEMENT_DATE_INSTALL_DIR.'/Date/Element.php';
59
60 /**
61  * patForms Date element
62  *
63  * Powerful date element with a very flexible date parser that enables
64  * very intuitive usage of date input uses.
65  *
66  * $Id$
67  *
68  * @access        public
69  * @package        patForms
70  * @subpackage    Element
71  * @author        Sebastian 'The Argh' Mordziol <argh@php-tools.net>
72  * @license        LGPL
73  */
74 class patForms_Element_Date extends patForms_Element
75 {
76    /**
77     * Stores the name of the element - this is used mainly by the patForms
78     * error management and should be set in every element class.
79     * @access    public
80     */
81     var $elementName    =    'Date';
82
83    /**
84     * set here which attributes you want to include in the element if you want to use
85     * the {@link patForms_Element::convertDefinition2Attributes()} method to automatically
86     * convert the values from your element definition into element attributes.
87     *
88     * @access    protected
89     * @see        patForms_Element::convertDefinition2Attribute()
90     */
91     var    $attributeDefinition    =    array(
92
93         'id' => array(
94             'required'        =>    false,
95             'format'        =>    'string',
96             'outputFormats'    =>    array( 'html' ),
97         ),
98         'name' => array(
99             'required'        =>    true,
100             'format'        =>    'string',
101             'outputFormats'    =>    array( 'html' ),
102             'modifiers'        =>    array( 'insertSpecials' => array() ),
103         ),
104         'type' => array(
105             'required'        =>    false,
106             'format'        =>    'string',
107             'default'        =>    'text',
108             'outputFormats'    =>    array( 'html' ),
109         ),
110         'title' => array(
111             'required'        =>    false,
112             'format'        =>    'string',
113             'outputFormats'    =>    array( 'html' ),
114             'modifiers'        =>    array( 'insertSpecials' => array() ),
115         ),
116         'description' => array(
117             'required'        =>    false,
118             'format'        =>    'string',
119             'outputFormats'    =>    array(),
120             'modifiers'        =>    array( 'insertSpecials' => array() ),
121         ),
122         'default' => array(
123             'required'        =>    false,
124             'format'        =>    'string',
125             'outputFormats'    =>    array(),
126         ),
127         'label' => array(
128             'required'        =>    false,
129             'format'        =>    'string',
130             'outputFormats'    =>    array(),
131         ),
132         'edit' => array(
133             'required'        =>    false,
134             'format'        =>    'string',
135             'default'        =>    'yes',
136             'outputFormats'    =>    array(),
137         ),
138         'display' => array(
139             'required'        =>    false,
140             'format'        =>    'string',
141             'default'        =>    'yes',
142             'outputFormats'    =>    array(),
143         ),
144         'required' => array(
145             'required'        =>    false,
146             'format'        =>    'string',
147             'default'        =>    'yes',
148             'outputFormats'    =>    array(),
149         ),
150         'value' => array(
151             'required'        =>    false,
152             'format'        =>    'string',
153             'outputFormats'    =>    array( 'html' ),
154         ),
155         'style' => array(
156             'required'        =>    false,
157             'outputFormats'    =>    array( 'html' ),
158             'format'        =>    'string',
159         ),
160         'class' => array(
161             'required'        =>    false,
162             'outputFormats'    =>    array( 'html' ),
163             'format'        =>    'string',
164         ),
165         'onchange' => array(
166             'required'        =>    false,
167             'format'        =>    'string',
168             'outputFormats'    =>    array( 'html' ),
169             'modifiers'        =>    array( 'insertSpecials' => array() ),
170         ),
171         'onclick' => array(
172             'required'        =>    false,
173             'format'        =>    'string',
174             'outputFormats'    =>    array( 'html' ),
175             'modifiers'        =>    array( 'insertSpecials' => array() ),
176         ),
177         'onfocus' => array(
178             'required'        =>    false,
179             'format'        =>    'string',
180             'outputFormats'    =>    array( 'html' ),
181             'modifiers'        =>    array( 'insertSpecials' => array() ),
182         ),
183         'onmouseover' => array(
184             'required'        =>    false,
185             'format'        =>    'string',
186             'outputFormats'    =>    array( 'html' ),
187             'modifiers'        =>    array( 'insertSpecials' => array() ),
188         ),
189         'onmouseout' => array(
190             'required'        =>    false,
191             'format'        =>    'string',
192             'outputFormats'    =>    array( 'html' ),
193             'modifiers'        =>    array( 'insertSpecials' => array() ),
194         ),
195         'onblur' => array(
196             'required'        =>    false,
197             'format'        =>    'string',
198             'outputFormats'    =>    array( 'html' ),
199             'modifiers'        =>    array( 'insertSpecials' => array() ),
200         ),
201         'accesskey' => array(
202             'required'        =>    false,
203             'format'        =>    'string',
204             'outputFormats'    =>    array( 'html' ),
205         ),
206         'position' => array(
207             'required'        =>    false,
208             'format'        =>    'int',
209             'outputFormats'    =>    array(),
210         ),
211         'tabindex' => array(
212             'required'        =>    false,
213             'format'        =>    'int',
214             'outputFormats'    =>    array( 'html' ),
215         ),
216         'max' => array(
217             'required'        =>    false,
218             'format'        =>    'datetime',
219             'outputFormats'    =>    array(),
220         ),
221         'min' => array(
222             'required'        =>    false,
223             'format'        =>    'datetime',
224             'outputFormats'    =>    array(),
225         ),
226         'format' => array(
227             'required'        =>    false,
228             'format'        =>    'datetime',
229             'outputFormats'    =>    array(),
230         ),
231         'dateformat' => array(
232             'required'        =>    false,
233             'format'        =>    'string',
234             'default'        =>    'Y.m.d H:i:s',
235             'outputFormats'    =>    array(),
236         ),
237         'presets' => array(
238             'required'        =>    false,
239             'format'        =>    'string',
240             'default'        =>    'yes',
241             'outputFormats'    =>    array(),
242         ),
243         'disabled' => array(
244             'required'        =>    false,
245             'format'        =>    'string',
246             'default'        =>    'no',
247             'outputFormats'    =>    array( 'html' ),
248         ),
249         'returnformat' => array(
250             'required'        =>    false,
251             'format'        =>    'string',
252             'default'        =>    'datestring',
253             'outputFormats'    =>    array(),
254         ),
255     );
256
257    /**
258     * define error codes and messages for each form element
259     *
260     * @access private
261     * @var    array    $validatorErrorCodes
262     */
263     var    $validatorErrorCodes  =   array(
264         'C'    =>    array(
265             1    =>    'This field is required, please complete it.',
266             2    =>    'Value must be after \'[MINDATE]\'!',
267             3    =>    'Value must be before \'[MAXDATE]\'!',
268             4    =>    'Incorrect date format',
269             5    =>    'This date does not exist'
270
271         ),
272         'de' =>    array(
273             1    =>    'Pflichtfeld. Bitte vervollständigen Sie Ihre Angabe.',
274             2    =>    'Der Wert muss nach \'[MINDATE]\' sein.',
275             3    =>    'Der Wert muss vor \'[MAXDATE]\' sein.',
276             4    =>    'Falsches Datumsformat',
277             5    =>    'Dieses Datum existiert nicht'
278         ),
279         'fr' =>    array(
280             1    =>    'Ce champ est obligatoire.',
281             2    =>    'La date doit être après le \'[MINDATE]\'.',
282             3    =>    'La date doit être avant le \'[MAXDATE]\'.',
283             4    =>    'Format de date incorrect',
284             5    =>    'Cette date n\'existe pas'
285         ),
286     );
287
288    /**
289     * the type of the element - set this to the type of element you are creating
290     * if you want to use the {@link patForms_Element::element2html()} method to
291     * create the final HTML tag for your element.
292     *
293     * @access    public
294     * @see        patForms_Element::element2html()
295     */
296     var $elementType    =    array(    "html"    =>    "input" );
297
298    /**
299     * Stores a subelement counter used for the automatic ID generation for each of
300     * the date element's sub elements.
301     *
302     * @access    private
303     * @var        int
304     */
305     var $elCount = 0;
306
307    /**
308     * Stores an index of all date tokens the date element supports, and the
309     * corresponding subelement that handles the token.
310     *
311     * @access    private
312     * @var        array
313     * @see        $tokens
314     */
315     var $tokensIndex = array(
316         'Y'    =>    'Year',
317         'y'    =>    'Year',
318         'd'    =>    'Day',
319         'j'    =>    'Day',
320         'F' =>    'Month',
321         'm'    =>    'Month',
322         'M'    =>    'Month',
323         'n'    =>    'Month',
324         'i'    =>    'Minute',
325         'a'    =>    'Meridiem',
326         'A'    =>    'Meridiem',
327         'g'    =>    'Hour',
328         'G'    =>    'Hour',
329         'h'    =>    'Hour',
330         'H'    =>    'Hour',
331         's'    =>    'Second',
332     );
333
334    /**
335     * Stores a list of date tokens for which the min and max attributes
336     * have to be set when in 'presets' mode.
337     *
338     * @access    private
339     * @var        array
340     */
341     var $minmaxTokens = array(
342         'Y',
343         'y'
344     );
345
346    /**
347     * Similar to the {@link $tokensIndex} property, only without the subelements
348     * information - only the tokens, as an indexed array so it is easier to check
349     * whether a token is available.
350     *
351     * @access    private
352     * @var        array
353     */
354     var $tokens = array();
355
356    /**
357     * Stores a list of all tokens the current date format string uses. Needed by some
358     * subelements like the Meridiem subelement to check whether the related tokens are
359     * present.
360     *
361     * @access    private
362     * @var        array
363     * @see        tokenUsed()
364     */
365     var $usedTokens = array();
366
367    /**
368     * Stores a collection of all the elements the current date format uses. Built on
369     * startup by the _init() method.
370     *
371     * @access    private
372     * @var        array
373     * @see        _init()
374     */
375     var $dateElements = array();
376
377    /**
378     * Stores all date element objects needed for the current date format string. Created
379     * automatically on startup by the _init() method.
380     *
381     * @access    private
382     * @var        array
383     * @see        _init()
384     */
385     var $elements = array();
386
387    /**
388     * Stores a list of characters contained in the date format string that will be transformed
389     * when serializing to HTML to ensure the output stays correct.
390     *
391     * @access    private
392     * @var        array
393     */
394     var $transformTable = array(
395         ' '    =>    '&#160;'
396     );
397
398    /**
399     * Stores a list of attributes that all subelements will inherit from the date element
400     *
401     * @access    private
402     * @var        array
403     */
404     var $inheritableAttributes = array(
405         'edit',
406         'display',
407         'required',
408         'style',
409         'class'
410     );
411
412    /**
413     * Stores an index of all elements for easy element name handling
414     *
415     * @access    private
416     * @var        array
417     */
418     var $elementsIndex = array();
419
420    /**
421     * Stores the current date object (the value of the element)
422     *
423     * @access    private
424     * @var        object
425     */
426     var $date = null;
427
428    /**
429     * Stores the default date object
430     *
431     * @access    private
432     * @var        object
433     */
434     var $defaultDate = null;
435
436    /**
437     * Stores the max date object
438     *
439     * @access    private
440     * @var        object
441     */
442     var $maxDate = null;
443
444    /**
445     * Stores the min date object
446     *
447     * @access    private
448     * @var        object
449     */
450     var $minDate = null;
451
452    /**
453     * Stores attributes for which there are special setter methods
454     * which need to be called. This is mainly for date attributes
455     * that need to be converted to date objects internally.
456     *
457     * @access    private
458     * @var        array
459     */
460     var $setterAttribs = array(
461         'default',
462         'max',
463         'min'
464     );
465
466    /**
467     * Runs all needed tasks that the date element needs to run - this
468     * includes parsing the date format string and generating the date
469     * subelements collection. Also sets any default values and max/min
470     * directives as required.
471     *
472     * @access    private
473     * @return    bool    $success    Always returns true. Errors occurring on parsing the default date are ignored and the default date ignored as well in that case.
474     * @see        $dateElements
475     * @see        $elements
476     */
477     function _init()
478     {
479         parent::_init();
480
481         // presets mode? check the attribute collection
482         if( $this->attributes['presets'] == 'yes' ) {
483             $this->checkMinMaxAttribs();
484         }
485
486         $this->tokens = array_keys( $this->tokensIndex );
487
488         // now parse the format string to create the
489         // elements collection. This fills the dateElements
490         // property as well as the elements property.
491         $cnt = strlen( $this->attributes["dateformat"] );
492         for( $i=0; $i < $cnt; $i++ )
493         {
494             $char = $this->attributes["dateformat"][$i];
495
496             if( in_array( $char, $this->tokens ) ){
497
498                 $elementID = $this->tokensIndex[$char];
499
500                 $this->elements[$elementID] =& $this->createElement( $elementID );
501                 $this->elements[$elementID]->setToken( $char );
502
503                 $element = array(
504                     'type'        =>    'token',
505                     'elementID'    =>    $elementID,
506                 );
507
508                 if( !in_array( $char, $this->usedTokens ) ) {
509                     array_push( $this->usedTokens, $char );
510                 }
511             } else {
512                 $element = array(
513                     'type'         => 'cdata',
514                     'content'    =>    $char
515                 );
516             }
517
518             array_push( $this->dateElements, $element );
519         }
520
521         $this->elementsIndex = array_keys( $this->elements );
522
523         // inherit any inheritable attributes
524         foreach( $this->inheritableAttributes as $attribute ) {
525             if( !isset( $this->attributes[$attribute] ) ) {
526                 continue;
527             }
528             $this->inheritAttribute( $attribute, $this->attributes[$attribute] );
529         }
530
531         // special attributes which have their own setter methods
532         foreach( $this->setterAttribs as $attributeName ) {
533             if( isset( $this->attributes[$attributeName] ) ) {
534                 $setterMethod = 'set'.ucfirst( $attributeName );
535                 $this->$setterMethod( $this->attributes[$attributeName] );
536             }
537         }
538
539         // submitted state
540         foreach( $this->elementsIndex as $elementID ) {
541             $this->elements[$elementID]->setSubmitted( $this->submitted );
542         }
543
544         foreach( $this->elementsIndex as $elementID ) {
545             $this->elements[$elementID]->init();
546         }
547
548         return true;
549     }
550
551    /**
552     * Wrapper for the main element function, with added functionality to
553     * inherit the submitted state to all subelements.
554     *
555     * @access    public
556     * @param    bool    $state    True if it has been submitted, false otherwise (default).
557     */
558     function setSubmitted( $state )
559     {
560         $this->submitted = $state;
561
562         foreach( $this->elementsIndex as $elementID ) {
563             $this->elements[$elementID]->setSubmitted( $this->submitted );
564         }
565     }
566
567    /**
568     * set the element's namespace
569     *
570     * @static
571     * @access    public
572     * @param    string        namespace
573     * @return    null
574     */
575     function setNamespace($namespace)
576     {
577         foreach($this->elementsIndex as $elementID) {
578             $this->elements[$elementID]->setNamespace($namespace);
579         }
580     }
581
582    /**
583     * Some date tokens require the min and max attributes to be set when
584     * in presets mode, as these dates are used to create the span with
585     * which the date selectors are filled. This method checks if all is