MDL-30393 lib/bennu minor fixes for RFC-2445 and PHP 5.x support.
[moodle.git] / lib / bennu / iCalendar_properties.php
1 <?php
3 /**
4  *  BENNU - PHP iCalendar library
5  *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
6  *
7  *  Released under the LGPL.
8  *
9  *  See http://bennu.sourceforge.net/ for more information and downloads.
10  *
11  * @author Ioannis Papaioannou 
12  * @version $Id$
13  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
14  */
16 class iCalendar_property {
17     // Properties can have parameters, but cannot have other properties or components
19     var $parent_component = NULL;
20     var $value            = NULL;
21     var $parameters       = NULL;
22     var $valid_parameters = NULL;
24     // These are common for 95% of properties, so define them here and override as necessary
25     var $val_multi        = false;
26     var $val_default      = NULL;
28     function __construct() {
29         $this->parameters = array();
30     }
32     // If some property needs extra care with its parameters, override this
33     // IMPORTANT: the parameter name MUST BE CAPITALIZED!
34     function is_valid_parameter($parameter, $value) {
36         if(is_array($value)) {
37             if(!iCalendar_parameter::multiple_values_allowed($parameter)) {
38                 return false;
39             }
40             foreach($value as $item) {
41                 if(!iCalendar_parameter::is_valid_value($this, $parameter, $item)) {
42                     return false;
43                 }
44             }
45             return true;
46         }
48         return iCalendar_parameter::is_valid_value($this, $parameter, $value);
49     }
51     function invariant_holds() {
52         return true;
53     }
55     // If some property is very picky about its values, it should do the work itself
56     // Only data type validation is done here
57     function is_valid_value($value) {
58         if(is_array($value)) {
59             if(!$this->val_multi) {
60                 return false;
61             }
62             else {
63                 foreach($value as $oneval) {
64                     if(!rfc2445_is_valid_value($oneval, $this->val_type)) {
65                         return false;
66                     }
67                 }
68             }
69             return true;
70         }
71         return rfc2445_is_valid_value($value, $this->val_type);
72     }
74     function default_value() {
75         return $this->val_default;
76     }
78     function set_parent_component($componentname) {
79         if(class_exists('iCalendar_'.strtolower(substr($componentname, 1)))) {
80             $this->parent_component = strtoupper($componentname);
81             return true;
82         }
84         return false;
85     }
87     function set_value($value) {
88         if($this->is_valid_value($value)) {
89             // This transparently formats any value type according to the iCalendar specs
90             if(is_array($value)) {
91                 foreach($value as $key => $item) {
92                     $value[$key] = rfc2445_do_value_formatting($item, $this->val_type);
93                 }
94                 $this->value = implode(',', $value);
95             }
96             else {
97                 $this->value = rfc2445_do_value_formatting($value, $this->val_type);
98             }
99             
100             return true;
101         }
102         return false;
103     }
105     function get_value() {
106         // First of all, assume that we have multiple values
107         $valarray = explode('\\,', $this->value);
109         // Undo transparent formatting
110         $replace_function = create_function('$a', 'return rfc2445_undo_value_formatting($a, '.$this->val_type.');');
111         $valarray = array_map($replace_function, $valarray);
113         // Now, if this property cannot have multiple values, don't return as an array
114         if(!$this->val_multi) {
115             return $valarray[0];
116         }
118         // Otherwise return an array even if it has one element, for uniformity
119         return $valarray;
121     }
123     function set_parameter($name, $value) {
125         // Uppercase
126         $name = strtoupper($name);
128         // Are we trying to add a valid parameter?
129         $xname = false;
130         if(!isset($this->valid_parameters[$name])) {
131             // If not, is it an x-name as per RFC 2445?
132             if(!rfc2445_is_xname($name)) {
133                 return false;
134             }
135             // No more checks -- all components are supposed to allow x-name parameters
136             $xname = true;
137         }
139         if(!$this->is_valid_parameter($name, $value)) {
140             return false;
141         }
143         if(is_array($value)) {
144             foreach($value as $key => $element) {
145                 $value[$key] = iCalendar_parameter::do_value_formatting($name, $element);
146             }
147         }
148         else {
149             $value = iCalendar_parameter::do_value_formatting($name, $value);
150         }
152         $this->parameters[$name] = $value;
154         // Special case: if we just changed the VALUE parameter, reflect this
155         // in the object's status so that it only accepts correct type values
156         if($name == 'VALUE') {
157             // TODO: what if this invalidates an already-set value?
158             $this->val_type = constant('RFC2445_TYPE_'.str_replace('-', '_', $value));
159         }
161         return true;
163     }
165     function get_parameter($name) {
167         // Uppercase
168         $name = strtoupper($name);
170         if(isset($this->parameters[$name])) {
171             // If there are any double quotes in the value, invisibly strip them
172             if(is_array($this->parameters[$name])) {
173                 foreach($this->parameters[$name] as $key => $value) {
174                     if(substr($value, 0, 1) == '"') {
175                        $this->parameters[$name][$key] = substr($value, 1, strlen($value) - 2);
176                     }
177                 }
178                 return $this->parameters[$name];
179             }
181             else {
182                 if(substr($this->parameters[$name], 0, 1) == '"') {
183                     return substr($this->parameters[$name], 1, strlen($this->parameters[$name]) - 2);
184                 }
185             }
186         }
188         return NULL;
189     }
191     function serialize() {
192         $string = $this->name;
194         if(!empty($this->parameters)) {
195             foreach($this->parameters as $name => $value) {
196                 $string .= ';'.$name.'=';
197                 if(is_array($value)) {
198                     $string .= implode(',', $value);
199                 }
200                 else {
201                     $string .= $value;
202                 }
203             }
204         }
206         $string .= ':'.$this->value;
208         return rfc2445_fold($string) . RFC2445_CRLF;
209     }
212 // 4.7 Calendar Properties
213 // -----------------------
215 class iCalendar_property_calscale extends iCalendar_property {
217     var $name        = 'CALSCALE';
218     var $val_type    = RFC2445_TYPE_TEXT;
220     function __construct() {
221         parent::__construct();
222         $this->valid_parameters = array(
223             RFC2445_XNAME => RFC2445_OPTIONAL
224         );
225     }
227     function is_valid_value($value) {
228         // This is case-sensitive
229         return ($value === 'GREGORIAN');
230     }
233 class iCalendar_property_method extends iCalendar_property {
235     var $name        = 'METHOD';
236     var $val_type    = RFC2445_TYPE_TEXT;
238     function __construct() {
239         parent::__construct();
240         $this->valid_parameters = array(
241             RFC2445_XNAME => RFC2445_OPTIONAL
242         );
243     }
245     function is_valid_value($value) {
246         // This is case-sensitive
247         // Methods from RFC 2446
248         $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER');
249         return in_array($value, $methods);
250     }
253 class iCalendar_property_prodid extends iCalendar_property {
255     var $name        = 'PRODID';
256     var $val_type    = RFC2445_TYPE_TEXT;
257     var $val_default = NULL;
259     function __construct() {
260         parent::__construct();
261         $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN';
263         $this->valid_parameters = array(
264             RFC2445_XNAME => RFC2445_OPTIONAL
265         );
266     }
269 class iCalendar_property_version extends iCalendar_property {
271     var $name        = 'VERSION';
272     var $val_type    = RFC2445_TYPE_TEXT;
273     var $val_default = '2.0';
275     function __construct() {
276         parent::__construct();
277         $this->valid_parameters = array(
278             RFC2445_XNAME => RFC2445_OPTIONAL
279         );
280     }
282     function is_valid_value($value) {
283         return($value === '2.0' || $value === 2.0);
284     }
288 // 4.8.1 Descriptive Component Properties
289 // --------------------------------------
291 class iCalendar_property_attach extends iCalendar_property {
293     var $name        = 'ATTACH';
294     var $val_type    = RFC2445_TYPE_URI;
296     function __construct() {
297         parent::__construct();
298         $this->valid_parameters = array(
299             'FMTTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
300             'ENCODING'    => RFC2445_OPTIONAL | RFC2445_ONCE,
301             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
302             RFC2445_XNAME => RFC2445_OPTIONAL
303         );
304     }
306     function invariant_holds() {
307         if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) {
308             return false;
309         }
310         if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) {
311             return false;
312         }
314         return true;
315     }
317     function is_valid_parameter($parameter, $value) {
319         $parameter = strtoupper($parameter);
321         if(!parent::is_valid_parameter($parameter, $value)) {
322             return false;
323         }
325         if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') {
326             return false;
327         }
329         if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') {
330             return false;
331         }
333         return true;
334     }
337 class iCalendar_property_categories extends iCalendar_property {
339     var $name        = 'CATEGORIES';
340     var $val_type    = RFC2445_TYPE_TEXT;
341     var $val_multi   = true;
343     function __construct() {
344         parent::__construct();
345         $this->valid_parameters = array(
346             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
347             RFC2445_XNAME => RFC2445_OPTIONAL
348         );
349     }
352 class iCalendar_property_class extends iCalendar_property {
354     var $name        = 'CLASS';
355     var $val_type    = RFC2445_TYPE_TEXT;
356     var $val_default = 'PUBLIC';
358     function __construct() {
359         parent::__construct();
360         $this->valid_parameters = array(
361             RFC2445_XNAME => RFC2445_OPTIONAL
362         );
363     }
365     function is_valid_value($value) {
366         // If this is not an xname, it is case-sensitive
367         return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value)));
368     }
371 class iCalendar_property_comment extends iCalendar_property {
373     var $name        = 'COMMENT';
374     var $val_type    = RFC2445_TYPE_TEXT;
376     function __construct() {
377         parent::__construct();
378         $this->valid_parameters = array(
379             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
380             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
381             RFC2445_XNAME => RFC2445_OPTIONAL
382         );
383     }
386 class iCalendar_property_description extends iCalendar_property {
388     var $name        = 'DESCRIPTION';
389     var $val_type    = RFC2445_TYPE_TEXT;
391     function __construct() {
392         parent::__construct();
393         $this->valid_parameters = array(
394             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
395             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
396             RFC2445_XNAME => RFC2445_OPTIONAL
397         );
398     }
401 class iCalendar_property_geo extends iCalendar_property {
403     var $name        = 'GEO';
404     var $val_type    = RFC2445_TYPE_TEXT;
406     function __construct() {
407         parent::__construct();
408         $this->valid_parameters = array(
409             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
410             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
411             RFC2445_XNAME => RFC2445_OPTIONAL
412         );
413     }
415     function is_valid_value($value) {
416         // This MUST be two floats separated by a semicolon
417         if(!is_string($value)) {
418             return false;
419         }
421         $floats = explode(';', $value);
422         if(count($floats) != 2) {
423             return false;
424         }
426         return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT);
427     }
429     function set_value($value) {
430         // Must override this, otherwise the semicolon separating
431         // the two floats would get auto-quoted, which is illegal
432         if($this->is_valid_value($value)) {
433             $this->value = $value;
434             return true;
435         }
437         return false;
438     }
442 class iCalendar_property_location extends iCalendar_property {
444     var $name        = 'LOCATION';
445     var $val_type    = RFC2445_TYPE_TEXT;
447     function __construct() {
448         parent::__construct();
449         $this->valid_parameters = array(
450             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
451             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
452             RFC2445_XNAME => RFC2445_OPTIONAL
453         );
454     }
457 class iCalendar_property_percent_complete extends iCalendar_property {
459     var $name        = 'PERCENT-COMPLETE';
460     var $val_type    = RFC2445_TYPE_INTEGER;
462     function __construct() {
463         parent::__construct();
464         $this->valid_parameters = array(
465             RFC2445_XNAME => RFC2445_OPTIONAL
466         );
467     }
469     function is_valid_value($value) {
470         // Only integers between 0 and 100 inclusive allowed
471         if(!parent::is_valid_value($value)) {
472             return false;
473         }
474         $value = intval($value);
475         return ($value >= 0 && $value <= 100);
476     }
480 class iCalendar_property_priority extends iCalendar_property {
482     var $name        = 'PRIORITY';
483     var $val_type    = RFC2445_TYPE_INTEGER;
485     function __construct() {
486         parent::__construct();
487         $this->valid_parameters = array(
488             RFC2445_XNAME => RFC2445_OPTIONAL
489         );
490     }
492     function is_valid_value($value) {
493         // Only integers between 0 and 9 inclusive allowed        
494         if(!parent::is_valid_value($value)) {
495             return false;
496         }
498         $value = intval($value);
499         return ($value >= 0 && $value <= 9);
500     }
503 class iCalendar_property_resources extends iCalendar_property {
505     var $name        = 'RESOURCES';
506     var $val_type    = RFC2445_TYPE_TEXT;
507     var $val_multi   = true;
509     function __construct() {
510         parent::__construct();
511         $this->valid_parameters = array(
512             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
513             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
514             RFC2445_XNAME => RFC2445_OPTIONAL
515         );
516     }
519 class iCalendar_property_status extends iCalendar_property {
521     var $name        = 'STATUS';
522     var $val_type    = RFC2445_TYPE_TEXT;
524     function __construct() {
525         parent::__construct();
526         $this->valid_parameters = array(
527             RFC2445_XNAME => RFC2445_OPTIONAL
528         );
529     }
531     function is_valid_value($value) {
532         // This is case-sensitive
533         switch ($this->parent_component) {
534             case 'VEVENT':
535                 $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED');
536             break;
537             case 'VTODO':
538                 $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED');
539             break;
540             case 'VJOURNAL':
541                 $allowed = array('DRAFT', 'FINAL', 'CANCELLED');
542             break;
543         }
544         return in_array($value, $allowed);
546     }
550 class iCalendar_property_summary extends iCalendar_property {
552     var $name        = 'SUMMARY';
553     var $val_type    = RFC2445_TYPE_TEXT;
555     function __construct() {
556         parent::__construct();
557         $this->valid_parameters = array(
558             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
559             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
560             RFC2445_XNAME => RFC2445_OPTIONAL
561         );
562     }
565 // 4.8.2 Date and Time Component Properties
566 // ----------------------------------------
568 class iCalendar_property_completed extends iCalendar_property {
570     var $name        = 'COMPLETED';
571     var $val_type    = RFC2445_TYPE_DATE_TIME;
573     function __construct() {
574         parent::__construct();
575         $this->valid_parameters = array(
576             RFC2445_XNAME => RFC2445_OPTIONAL
577         );
578     }
580     function is_valid_value($value) {
581         if(!parent::is_valid_value($value)) {
582             return false;
583         }
584         // Time MUST be in UTC format
585         return(substr($value, -1) == 'Z');
586     }
589 class iCalendar_property_dtend extends iCalendar_property {
591     var $name        = 'DTEND';
592     var $val_type    = RFC2445_TYPE_DATE_TIME;
594     function __construct() {
595         parent::__construct();
596         $this->valid_parameters = array(
597             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
598             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
599             RFC2445_XNAME => RFC2445_OPTIONAL
600         );
601     }
603     function is_valid_value($value) {
604         if(!parent::is_valid_value($value)) {
605             return false;
606         }
608         // If present in a FREEBUSY component, must be in UTC format
609         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
610             return false;
611         }
613         return true;
615     }
617     function is_valid_parameter($parameter, $value) {
619         $parameter = strtoupper($parameter);
621         if(!parent::is_valid_parameter($parameter, $value)) {
622             return false;
623         }
624         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
625             return false;
626         }
628         return true;
629     }
632 class iCalendar_property_due extends iCalendar_property {
634     var $name        = 'DUE';
635     var $val_type    = RFC2445_TYPE_DATE_TIME;
637     function __construct() {
638         parent::__construct();
639         $this->valid_parameters = array(
640             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
641             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
642             RFC2445_XNAME => RFC2445_OPTIONAL
643         );
644     }
646     function is_valid_value($value) {
647         if(!parent::is_valid_value($value)) {
648             return false;
649         }
651         // If present in a FREEBUSY component, must be in UTC format
652         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
653             return false;
654         }
656         return true;
658     }
660     function is_valid_parameter($parameter, $value) {
662         $parameter = strtoupper($parameter);
664         if(!parent::is_valid_parameter($parameter, $value)) {
665             return false;
666         }
667         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
668             return false;
669         }
671         return true;
672     }
675 class iCalendar_property_dtstart extends iCalendar_property {
677     var $name        = 'DTSTART';
678     var $val_type    = RFC2445_TYPE_DATE_TIME;
680     function __construct() {
681         parent::__construct();
682         $this->valid_parameters = array(
683             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
684             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
685             RFC2445_XNAME => RFC2445_OPTIONAL
686         );
687     }
689     // TODO: unimplemented stuff when parent is a VTIMEZONE component
691     function is_valid_value($value) {
692         if(!parent::is_valid_value($value)) {
693             return false;
694         }
696         // If present in a FREEBUSY component, must be in UTC format
697         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
698             return false;
699         }
701         return true;
702     }
704     function is_valid_parameter($parameter, $value) {
706         $parameter = strtoupper($parameter);
708         if(!parent::is_valid_parameter($parameter, $value)) {
709             return false;
710         }
711         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
712             return false;
713         }
715         return true;
716     }
719 class iCalendar_property_duration extends iCalendar_property {
721     var $name        = 'DURATION';
722     var $val_type    = RFC2445_TYPE_DURATION;
724     function __construct() {
725         parent::__construct();
726         $this->valid_parameters = array(
727             RFC2445_XNAME => RFC2445_OPTIONAL
728         );
729     }
731     function is_valid_value($value) {
732         if(!parent::is_valid_value($value)) {
733             return false;
734         }
736         // Value must be positive
737         return ($value{0} != '-');
738     }
741 class iCalendar_property_freebusy extends iCalendar_property {
743     var $name        = 'FREEBUSY';
744     var $val_type    = RFC2445_TYPE_PERIOD;
745     var $val_multi   = true;
747     function __construct() {
748         parent::__construct();
749         $this->valid_parameters = array(
750             'FBTYPE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
751             RFC2445_XNAME => RFC2445_OPTIONAL
752         );
753     }
755     function is_valid_value($value) {
756         if(!parent::is_valid_value($value)) {
757             return false;
758         }
760         $pos = strpos($value, '/'); // We know there's only one / in there
761         if($value{$pos - 1} != 'Z') {
762             // Start time MUST be in UTC
763             return false;
764         }
765         if($value{$pos + 1} != 'P' && substr($value, -1) != 'Z') {
766             // If the second part is not a period, it MUST be in UTC
767             return false;
768         }
770         return true;
771     }
773     // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak)
776 class iCalendar_property_transp extends iCalendar_property {
778     var $name        = 'TRANSP';
779     var $val_type    = RFC2445_TYPE_TEXT;
780     var $val_default = 'OPAQUE';
782     function __construct() {
783         parent::__construct();
784         $this->valid_parameters = array(
785             RFC2445_XNAME => RFC2445_OPTIONAL
786         );
787     }
789     function is_valid_value($value) {
790         return ($value === 'TRANSPARENT' || $value === 'OPAQUE');
791     }
794 // TODO: 4.8.3 timezone component properties
797 // 4.8.4 Relationship Component Properties
798 // ---------------------------------------
800 class iCalendar_property_attendee extends iCalendar_property {
802     var $name        = 'ATTENDEE';
803     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
805     // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH
806     // TODO: standard has lots of detail here, make triple sure that we eventually conform
808     function __construct() {
809         parent::__construct();
810         $this->valid_parameters = array(
811             'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
812             'CN'             => RFC2445_OPTIONAL | RFC2445_ONCE,
813             'ROLE'           => RFC2445_OPTIONAL | RFC2445_ONCE,
814             'PARTSTAT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
815             'RSVP'           => RFC2445_OPTIONAL | RFC2445_ONCE,
816             'CUTYPE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
817             'MEMBER'         => RFC2445_OPTIONAL | RFC2445_ONCE,
818             'DELEGATED-TO'   => RFC2445_OPTIONAL | RFC2445_ONCE,
819             'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE,
820             'SENT-BY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
821             'DIR'            => RFC2445_OPTIONAL | RFC2445_ONCE,
822             RFC2445_XNAME    => RFC2445_OPTIONAL
823         );
824     }
826     function set_parent_component($componentname) {
827         if(!parent::set_parent_component($componentname)) {
828             return false;
829         }
831         if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') {
832             // Most parameters become invalid in this case, the full allowed set is now:
833             $this->valid_parameters = array(
834                 'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
835                 RFC2445_XNAME    => RFC2445_OPTIONAL
836             );
837         }
839         return false;
840     }
844 class iCalendar_property_contact extends iCalendar_property {
846     var $name        = 'CONTACT';
847     var $val_type    = RFC2445_TYPE_TEXT;
849     function __construct() {
850         parent::__construct();
851         $this->valid_parameters = array(
852             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
853             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
854             RFC2445_XNAME => RFC2445_OPTIONAL
855         );
856     }
859 class iCalendar_property_organizer extends iCalendar_property {
861     var $name        = 'ORGANIZER';
862     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
864     function __construct() {
865         parent::__construct();
866         $this->valid_parameters = array(
867             'CN'          => RFC2445_OPTIONAL | RFC2445_ONCE,
868             'DIR'         => RFC2445_OPTIONAL | RFC2445_ONCE,
869             'SENT-BY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
870             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
871             RFC2445_XNAME => RFC2445_OPTIONAL
872         );
873     }
875     // TODO:
876 /*    
877    Conformance: This property MUST be specified in an iCalendar object
878    that specifies a group scheduled calendar entity. This property MUST
879    be specified in an iCalendar object that specifies the publication of
880    a calendar user's busy time. This property MUST NOT be specified in
881    an iCalendar object that specifies only a time zone definition or
882    that defines calendar entities that are not group scheduled entities,
883    but are entities only on a single user's calendar.
884 */
888 class iCalendar_property_recurrence_id extends iCalendar_property {
890     // TODO: can only be specified when defining recurring components in the calendar
891 /*
892    Conformance: This property can be specified in an iCalendar object
893    containing a recurring calendar component.
895    Description: The full range of calendar components specified by a
896    recurrence set is referenced by referring to just the "UID" property
897    value corresponding to the calendar component. The "RECURRENCE-ID"
898    property allows the reference to an individual instance within the
899    recurrence set.
900 */
902     var $name        = 'RECURRENCE-ID';
903     var $val_type    = RFC2445_TYPE_DATE_TIME;
905     function __construct() {
906         parent::__construct();
907         $this->valid_parameters = array(
908             'RANGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
909             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
910             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
911             RFC2445_XNAME => RFC2445_OPTIONAL
912         );
913     }
915     function is_valid_parameter($parameter, $value) {
917         $parameter = strtoupper($parameter);
919         if(!parent::is_valid_parameter($parameter, $value)) {
920             return false;
921         }
922         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
923             return false;
924         }
926         return true;
927     }
931 class iCalendar_property_related_to extends iCalendar_property {
933     var $name        = 'RELATED-TO';
934     var $val_type    = RFC2445_TYPE_TEXT;
936     // TODO: the value of this property must reference another component's UID
938     function __construct() {
939         parent::__construct();
940         $this->valid_parameters = array(
941             'RELTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
942             RFC2445_XNAME => RFC2445_OPTIONAL
943         );
944     }
947 class iCalendar_property_url extends iCalendar_property {
949     var $name        = 'URL';
950     var $val_type    = RFC2445_TYPE_URI;
952     function __construct() {
953         parent::__construct();
954         $this->valid_parameters = array(
955             RFC2445_XNAME => RFC2445_OPTIONAL
956         );
957     }
960 class iCalendar_property_uid extends iCalendar_property {
962     var $name        = 'UID';
963     var $val_type    = RFC2445_TYPE_TEXT;
965     function __construct() {
966         parent::__construct();
967         $this->valid_parameters = array(
968             RFC2445_XNAME => RFC2445_OPTIONAL
969         );
971         // The exception to the rule: this is not a static value, so we
972         // generate it on-the-fly here. Guaranteed to be different for
973         // each instance of this property, too. Nice.
974         $this->val_default = Bennu::generate_guid();
975     }
978 // 4.8.5 Recurrence Component Properties
979 // -------------------------------------
981 class iCalendar_property_exdate extends iCalendar_property {
983     var $name        = 'EXDATE';
984     var $val_type    = RFC2445_TYPE_DATE_TIME;
985     var $val_multi   = true;
987     function __construct() {
988         parent::__construct();
989         $this->valid_parameters = array(
990             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
991             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
992             RFC2445_XNAME => RFC2445_OPTIONAL
993         );
994     }
996     function is_valid_parameter($parameter, $value) {
998         $parameter = strtoupper($parameter);
1000         if(!parent::is_valid_parameter($parameter, $value)) {
1001             return false;
1002         }
1003         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
1004             return false;
1005         }
1007         return true;
1008     }
1012 class iCalendar_property_exrule extends iCalendar_property {
1014     var $name        = 'EXRULE';
1015     var $val_type    = RFC2445_TYPE_RECUR;
1017     function __construct() {
1018         parent::__construct();
1019         $this->valid_parameters = array(
1020             RFC2445_XNAME => RFC2445_OPTIONAL
1021         );
1022     }
1025 class iCalendar_property_rdate extends iCalendar_property {
1027     var $name        = 'RDATE';
1028     var $val_type    = RFC2445_TYPE_DATE_TIME;
1029     var $val_multi   = true;
1031     function __construct() {
1032         parent::__construct();
1033         $this->valid_parameters = array(
1034             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
1035             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
1036             RFC2445_XNAME => RFC2445_OPTIONAL
1037         );
1038     }
1040     function is_valid_parameter($parameter, $value) {
1042         $parameter = strtoupper($parameter);
1044         if(!parent::is_valid_parameter($parameter, $value)) {
1045             return false;
1046         }
1047         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) {
1048             return false;
1049         }
1051         return true;
1052     }
1056 class iCalendar_property_rrule extends iCalendar_property {
1058     var $name        = 'RRULE';
1059     var $val_type    = RFC2445_TYPE_RECUR;
1061     function __construct() {
1062         parent::__construct();
1063         $this->valid_parameters = array(
1064             RFC2445_XNAME => RFC2445_OPTIONAL
1065         );
1066     }
1069 // 4.8.6 Alarm Component Properties
1070 // -------------------------------------------
1071 class iCalendar_property_action extends iCalendar_property {
1072         var $name      = 'ACTION';
1073     var $val_type   = RFC2445_TYPE_TEXT;
1074     
1075     function __construct() {
1076         parent::__construct();
1077         $this->valid_parameters = array(
1078             RFC2445_XNAME => RFC2445_OPTIONAL
1079         );
1080     }
1081     
1082     function is_valid_value($value) {
1083         if(!parent::is_valid_value($value)) {
1084             return false;
1085         }
1086         
1087         // Value must be one of the following, or an x-name.
1088         $valid_values = array('ACTION', 'DISPLAY', 'EMAIL', 'PROCEDURE');
1089         return(in_array($value, $valid_values) || rfc2445_is_xname($value));        
1090         
1091     }
1094 class iCalendar_property_repeat extends iCalendar_property {
1095     var $name      = 'REPEAT';
1096     var $val_type   = RFC2445_TYPE_INTEGER;
1097     
1098     function __construct() {
1099         parent::__construct();
1100         $this->valid_parameters = array(
1101             RFC2445_XNAME => RFC2445_OPTIONAL
1102         );
1103     }   
1106 class iCalendar_property_trigger extends iCalendar_property {
1107     var $name      = 'TRIGGER';
1108     var $val_type   = RFC2445_TYPE_TEXT;
1109     
1110     function __construct() {
1111         parent::__construct();
1112         $this->valid_parameters = array(
1113             'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1114             'RELATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
1115             RFC2445_XNAME => RFC2445_OPTIONAL
1116         );
1117     }
1118     
1119     function is_valid_value($value) {        
1120         if(!parent::is_valid_value($value)) {
1121             return false;
1122         }
1123         // Must either be DURATION or DATE_TIME type
1124         return(rfc2445_is_valid_value($value, RFC2445_TYPE_DURATION) 
1125             || rfc2445_is_valid_value($value, RFC2445_TYPE_DATE_TIME));
1126     }
1131 // 4.8.7 Change Management Component Properties
1132 // --------------------------------------------
1134 class iCalendar_property_created extends iCalendar_property {
1136     var $name        = 'CREATED';
1137     var $val_type    = RFC2445_TYPE_DATE_TIME;
1139     function __construct() {
1140         parent::__construct();
1141         $this->valid_parameters = array(
1142             RFC2445_XNAME => RFC2445_OPTIONAL
1143         );
1144     }
1146     function is_valid_value($value) {
1147         if(!parent::is_valid_value($value)) {
1148             return false;
1149         }
1150         // Time MUST be in UTC format
1151         return(substr($value, -1) == 'Z');
1152     }
1155 class iCalendar_property_dtstamp extends iCalendar_property {
1157     var $name        = 'DTSTAMP';
1158     var $val_type    = RFC2445_TYPE_DATE_TIME;
1160     function __construct() {
1161         parent::__construct();
1162         $this->valid_parameters = array(
1163             RFC2445_XNAME => RFC2445_OPTIONAL
1164         );
1165     }
1167     function is_valid_value($value) {
1168         if(!parent::is_valid_value($value)) {
1169             return false;
1170         }
1171         // Time MUST be in UTC format
1172         return(substr($value, -1) == 'Z');
1173     }
1176 class iCalendar_property_last_modified extends iCalendar_property {
1178     var $name        = 'LAST-MODIFIED';
1179     var $val_type    = RFC2445_TYPE_DATE_TIME;
1181     function __construct() {
1182         parent::__construct();
1183         $this->valid_parameters = array(
1184             RFC2445_XNAME => RFC2445_OPTIONAL
1185         );
1186     }
1188     function is_valid_value($value) {
1189         if(!parent::is_valid_value($value)) {
1190             return false;
1191         }
1192         // Time MUST be in UTC format
1193         return(substr($value, -1) == 'Z');
1194     }
1197 class iCalendar_property_sequence extends iCalendar_property {
1199     var $name        = 'SEQUENCE';
1200     var $val_type    = RFC2445_TYPE_INTEGER;
1201     var $val_default = 0;
1203     function __construct() {
1204         parent::__construct();
1205         $this->valid_parameters = array(
1206             RFC2445_XNAME => RFC2445_OPTIONAL
1207         );
1208     }
1210     function is_valid_value($value) {
1211         if(!parent::is_valid_value($value)) {
1212             return false;
1213         }
1214         $value = intval($value);
1215         return ($value >= 0);
1216     }
1219 // 4.8.8 Miscellaneous Component Properties
1220 // ----------------------------------------
1222 class iCalendar_property_x extends iCalendar_property {
1224     var $name        = RFC2445_XNAME;
1225     var $val_type    = NULL;
1227     function __construct() {
1228         parent::__construct();
1229         $this->valid_parameters = array(
1230             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1231             RFC2445_XNAME => RFC2445_OPTIONAL
1232         );
1233     }
1235     function set_name($name) {
1237         $name = strtoupper($name);
1239         if(rfc2445_is_xname($name)) {
1240             $this->name = $name;
1241             return true;
1242         }
1244         return false;
1245     }
1248 class iCalendar_property_request_status extends iCalendar_property {
1250     // IMPORTANT NOTE: This property value includes TEXT fields
1251     // separated by semicolons. Unfortunately, auto-value-formatting
1252     // cannot be used in this case. As an exception, the value passed
1253     // to this property MUST be already escaped.
1255     var $name        = 'REQUEST-STATUS';
1256     var $val_type    = RFC2445_TYPE_TEXT;
1258     function __construct() {
1259         parent::__construct();
1260         $this->valid_parameters = array(
1261             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1262             RFC2445_XNAME => RFC2445_OPTIONAL
1263         );
1264     }
1266     function is_valid_value($value) {
1267         if(!is_string($value) || empty($value)) {
1268             return false;
1269         }
1271         $len   = strlen($value);
1272         $parts = array();
1273         $from  = 0;
1274         $escch = false;
1276         for($i = 0; $i < $len; ++$i) {
1277             if($value{$i} == ';' && !$escch) {
1278                 // Token completed
1279                 $parts[] = substr($value, $from, $i - $from);
1280                 $from = $i + 1;
1281                 continue;
1282             }
1283             $escch = ($value{$i} == '\\');
1284         }
1285         // Add one last token with the remaining text; if the value
1286         // ended with a ';' it was illegal, so check that this token
1287         // is not the empty string.
1288         $parts[] = substr($value, $from);
1290         $count = count($parts);
1292         // May have 2 or 3 tokens (last one is optional)
1293         if($count != 2 && $count != 3) {
1294             return false;
1295         }
1297         // REMEMBER: if ANY part is empty, we have an illegal value
1299         // First token must be hierarchical numeric status (3 levels max)
1300         if(strlen($parts[0]) == 0) {
1301             return false;
1302         }
1304         if($parts[0]{0} < '1' || $parts[0]{0} > '4') {
1305             return false;
1306         }
1308         $len = strlen($parts[0]);
1310         // Max 3 levels, and can't end with a period
1311         if($len > 5 || $parts[0]{$len - 1} == '.') {
1312             return false;
1313         }
1315         for($i = 1; $i < $len; ++$i) {
1316             if(($i & 1) == 1 && $parts[0]{$i} != '.') {
1317                 // Even-indexed chars must be periods
1318                 return false;
1319             }
1320             else if(($i & 1) == 0 && ($parts[0]{$i} < '0' || $parts[0]{$i} > '9')) {
1321                 // Odd-indexed chars must be numbers
1322                 return false;
1323             }
1324         }
1326         // Second and third tokens must be TEXT, and already escaped, so
1327         // they are not allowed to have UNESCAPED semicolons, commas, slashes,
1328         // or any newlines at all
1330         for($i = 1; $i < $count; ++$i) {
1331             if(strpos($parts[$i], "\n") !== false) {
1332                 return false;
1333             }
1335             $len = strlen($parts[$i]);
1336             if($len == 0) {
1337                 // Cannot be empty
1338                 return false;
1339             }
1341             $parts[$i] .= '#'; // This guard token saves some conditionals in the loop
1343             for($j = 0; $j < $len; ++$j) {
1344                 $thischar = $parts[$i]{$j};
1345                 $nextchar = $parts[$i]{$j + 1};
1346                 if($thischar == '\\') {
1347                     // Next char must now be one of ";,\nN"
1348                     if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' &&
1349                        $nextchar != 'n' && $nextchar != 'N') {
1350                         return false;
1351                     }
1353                     // OK, this escaped sequence is correct, bypass next char
1354                     ++$j;
1355                     continue;
1356                 }
1357                 if($thischar == ';' || $thischar == ',' || $thischar == '\\') {
1358                     // This wasn't escaped as it should
1359                     return false;
1360                 }
1361             }
1362         }
1364         return true;
1365     }
1367     function set_value($value) {
1368         // Must override this, otherwise the value would be quoted again
1369         if($this->is_valid_value($value)) {
1370             $this->value = $value;
1371             return true;
1372         }
1374         return false;
1375     }
1379 class iCalendar_property_tzid extends iCalendar_property {
1381     var $name        = 'TZID';
1382     var $val_type    = RFC2445_TYPE_TEXT;
1384     function __construct() {
1385         parent::__construct();
1386         $this->valid_parameters = array(
1387             RFC2445_XNAME => RFC2445_OPTIONAL
1388         );
1389     }
1391     function is_valid_value($value) {
1392         if(!parent::is_valid_value($value)) {
1393             return false;
1394         } else {
1395             return true;
1396         }
1397     }
1400 class iCalendar_property_tzname extends iCalendar_property {
1402     var $name        = 'TZNAME';
1403     var $val_type    = RFC2445_TYPE_TEXT;
1405     function __construct() {
1406         parent::__construct();
1407         $this->valid_parameters = array(
1408             'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1409             RFC2445_XNAME => RFC2445_OPTIONAL
1410         );
1411     }
1413     function is_valid_value($value) {
1414         if(!parent::is_valid_value($value)) {
1415             return false;
1416         } else {
1417             return true;
1418         }
1419     }
1422 class iCalendar_property_tzoffsetfrom extends iCalendar_property {
1424     var $name        = 'TZOFFSETFROM';
1425     var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1427     function __construct() {
1428         parent::__construct();
1429         $this->valid_parameters = array(
1430             RFC2445_XNAME => RFC2445_OPTIONAL
1431         );
1432     }
1434     function is_valid_value($value) {
1435         if(!parent::is_valid_value($value)) {
1436             return false;
1437         } else {
1438             return true;
1439         }
1440     }
1443 class iCalendar_property_tzoffsetto extends iCalendar_property {
1445     var $name        = 'TZOFFSETTO';
1446     var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1448     function __construct() {
1449         parent::__construct();
1450         $this->valid_parameters = array(
1451             RFC2445_XNAME => RFC2445_OPTIONAL
1452         );
1453     }
1455     function is_valid_value($value) {
1456         if(!parent::is_valid_value($value)) {
1457             return false;
1458         } else {
1459                 return true;
1460         }
1461     }
1466 #######################
1467 /*
1468 class iCalendar_property_class extends iCalendar_property {
1470     var $name        = 'CLASS';
1471     var $val_type    = RFC2445_TYPE_TEXT;
1473     function __construct() {
1474         parent::__construct();
1475         $this->valid_parameters = array(
1476             RFC2445_XNAME => RFC2445_OPTIONAL
1477         );
1478     }
1480 */