bb621ce1c05f6f185602bc7e3b903b0030576c9e
[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         $this->valid_parameters = array(
222             RFC2445_XNAME => RFC2445_OPTIONAL
223         );
224     }
226     function is_valid_value($value) {
227         // This is case-sensitive
228         return ($value === 'GREGORIAN');
229     }
232 class iCalendar_property_method extends iCalendar_property {
234     var $name        = 'METHOD';
235     var $val_type    = RFC2445_TYPE_TEXT;
237     function __construct() {
238         $this->valid_parameters = array(
239             RFC2445_XNAME => RFC2445_OPTIONAL
240         );
241     }
243     function is_valid_value($value) {
244         // This is case-sensitive
245         // Methods from RFC 2446
246         $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER');
247         return in_array($value, $methods);
248     }
251 class iCalendar_property_prodid extends iCalendar_property {
253     var $name        = 'PRODID';
254     var $val_type    = RFC2445_TYPE_TEXT;
255     var $val_default = NULL;
257     function __construct() {
258         $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN';
260         $this->valid_parameters = array(
261             RFC2445_XNAME => RFC2445_OPTIONAL
262         );
263     }
266 class iCalendar_property_version extends iCalendar_property {
268     var $name        = 'VERSION';
269     var $val_type    = RFC2445_TYPE_TEXT;
270     var $val_default = '2.0';
272     function __construct() {
273         $this->valid_parameters = array(
274             RFC2445_XNAME => RFC2445_OPTIONAL
275         );
276     }
278     function is_valid_value($value) {
279         return($value === '2.0' || $value === 2.0);
280     }
284 // 4.8.1 Descriptive Component Properties
285 // --------------------------------------
287 class iCalendar_property_attach extends iCalendar_property {
289     var $name        = 'ATTACH';
290     var $val_type    = RFC2445_TYPE_URI;
292     function __construct() {
293         $this->valid_parameters = array(
294             'FMTTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
295             'ENCODING'    => RFC2445_OPTIONAL | RFC2445_ONCE,
296             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
297             RFC2445_XNAME => RFC2445_OPTIONAL
298         );
299     }
301     function invariant_holds() {
302         if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) {
303             return false;
304         }
305         if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) {
306             return false;
307         }
309         return true;
310     }
312     function is_valid_parameter($parameter, $value) {
314         $parameter = strtoupper($parameter);
316         if(!parent::is_valid_parameter($parameter, $value)) {
317             return false;
318         }
320         if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') {
321             return false;
322         }
324         if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') {
325             return false;
326         }
328         return true;
329     }
332 class iCalendar_property_categories extends iCalendar_property {
334     var $name        = 'CATEGORIES';
335     var $val_type    = RFC2445_TYPE_TEXT;
336     var $val_multi   = true;
338     function __construct() {
339         $this->valid_parameters = array(
340             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
341             RFC2445_XNAME => RFC2445_OPTIONAL
342         );
343     }
346 class iCalendar_property_class extends iCalendar_property {
348     var $name        = 'CLASS';
349     var $val_type    = RFC2445_TYPE_TEXT;
350     var $val_default = 'PUBLIC';
352     function __construct() {
353         $this->valid_parameters = array(
354             RFC2445_XNAME => RFC2445_OPTIONAL
355         );
356     }
358     function is_valid_value($value) {
359         // If this is not an xname, it is case-sensitive
360         return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value)));
361     }
364 class iCalendar_property_comment extends iCalendar_property {
366     var $name        = 'COMMENT';
367     var $val_type    = RFC2445_TYPE_TEXT;
369     function __construct() {
370         $this->valid_parameters = array(
371             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
372             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
373             RFC2445_XNAME => RFC2445_OPTIONAL
374         );
375     }
378 class iCalendar_property_description extends iCalendar_property {
380     var $name        = 'DESCRIPTION';
381     var $val_type    = RFC2445_TYPE_TEXT;
383     function __construct() {
384         $this->valid_parameters = array(
385             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
386             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
387             RFC2445_XNAME => RFC2445_OPTIONAL
388         );
389     }
392 class iCalendar_property_geo extends iCalendar_property {
394     var $name        = 'GEO';
395     var $val_type    = RFC2445_TYPE_TEXT;
397     function __construct() {
398         $this->valid_parameters = array(
399             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
400             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
401             RFC2445_XNAME => RFC2445_OPTIONAL
402         );
403     }
405     function is_valid_value($value) {
406         // This MUST be two floats separated by a semicolon
407         if(!is_string($value)) {
408             return false;
409         }
411         $floats = explode(';', $value);
412         if(count($floats) != 2) {
413             return false;
414         }
416         return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT);
417     }
419     function set_value($value) {
420         // Must override this, otherwise the semicolon separating
421         // the two floats would get auto-quoted, which is illegal
422         if($this->is_valid_value($value)) {
423             $this->value = $value;
424             return true;
425         }
427         return false;
428     }
432 class iCalendar_property_location extends iCalendar_property {
434     var $name        = 'LOCATION';
435     var $val_type    = RFC2445_TYPE_TEXT;
437     function __construct() {
438         $this->valid_parameters = array(
439             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
440             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
441             RFC2445_XNAME => RFC2445_OPTIONAL
442         );
443     }
446 class iCalendar_property_percent_complete extends iCalendar_property {
448     var $name        = 'PERCENT-COMPLETE';
449     var $val_type    = RFC2445_TYPE_INTEGER;
451     function __construct() {
452         $this->valid_parameters = array(
453             RFC2445_XNAME => RFC2445_OPTIONAL
454         );
455     }
457     function is_valid_value($value) {
458         // Only integers between 0 and 100 inclusive allowed
459         if(!parent::is_valid_value($value)) {
460             return false;
461         }
462         $value = intval($value);
463         return ($value >= 0 && $value <= 100);
464     }
468 class iCalendar_property_priority extends iCalendar_property {
470     var $name        = 'PRIORITY';
471     var $val_type    = RFC2445_TYPE_INTEGER;
473     function __construct() {
474         $this->valid_parameters = array(
475             RFC2445_XNAME => RFC2445_OPTIONAL
476         );
477     }
479     function is_valid_value($value) {
480         // Only integers between 0 and 9 inclusive allowed        
481         if(!parent::is_valid_value($value)) {
482             return false;
483         }
485         $value = intval($value);
486         return ($value >= 0 && $value <= 9);
487     }
490 class iCalendar_property_resources extends iCalendar_property {
492     var $name        = 'RESOURCES';
493     var $val_type    = RFC2445_TYPE_TEXT;
494     var $val_multi   = true;
496     function __construct() {
497         $this->valid_parameters = array(
498             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
499             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
500             RFC2445_XNAME => RFC2445_OPTIONAL
501         );
502     }
505 class iCalendar_property_status extends iCalendar_property {
507     var $name        = 'STATUS';
508     var $val_type    = RFC2445_TYPE_TEXT;
510     function __construct() {
511         $this->valid_parameters = array(
512             RFC2445_XNAME => RFC2445_OPTIONAL
513         );
514     }
516     function is_valid_value($value) {
517         // This is case-sensitive
518         switch ($this->parent_component) {
519             case 'VEVENT':
520                 $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED');
521             break;
522             case 'VTODO':
523                 $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED');
524             break;
525             case 'VJOURNAL':
526                 $allowed = array('DRAFT', 'FINAL', 'CANCELLED');
527             break;
528         }
529         return in_array($value, $allowed);
531     }
535 class iCalendar_property_summary extends iCalendar_property {
537     var $name        = 'SUMMARY';
538     var $val_type    = RFC2445_TYPE_TEXT;
540     function __construct() {
541         $this->valid_parameters = array(
542             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
543             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
544             RFC2445_XNAME => RFC2445_OPTIONAL
545         );
546     }
549 // 4.8.2 Date and Time Component Properties
550 // ----------------------------------------
552 class iCalendar_property_completed extends iCalendar_property {
554     var $name        = 'COMPLETED';
555     var $val_type    = RFC2445_TYPE_DATE_TIME;
557     function __construct() {
558         $this->valid_parameters = array(
559             RFC2445_XNAME => RFC2445_OPTIONAL
560         );
561     }
563     function is_valid_value($value) {
564         if(!parent::is_valid_value($value)) {
565             return false;
566         }
567         // Time MUST be in UTC format
568         return(substr($value, -1) == 'Z');
569     }
572 class iCalendar_property_dtend extends iCalendar_property {
574     var $name        = 'DTEND';
575     var $val_type    = RFC2445_TYPE_DATE_TIME;
577     function __construct() {
578         $this->valid_parameters = array(
579             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
580             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
581             RFC2445_XNAME => RFC2445_OPTIONAL
582         );
583     }
585     function is_valid_value($value) {
586         if(!parent::is_valid_value($value)) {
587             return false;
588         }
590         // If present in a FREEBUSY component, must be in UTC format
591         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
592             return false;
593         }
595         return true;
597     }
599     function is_valid_parameter($parameter, $value) {
601         $parameter = strtoupper($parameter);
603         if(!parent::is_valid_parameter($parameter, $value)) {
604             return false;
605         }
606         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
607             return false;
608         }
610         return true;
611     }
614 class iCalendar_property_due extends iCalendar_property {
616     var $name        = 'DUE';
617     var $val_type    = RFC2445_TYPE_DATE_TIME;
619     function __construct() {
620         $this->valid_parameters = array(
621             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
622             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
623             RFC2445_XNAME => RFC2445_OPTIONAL
624         );
625     }
627     function is_valid_value($value) {
628         if(!parent::is_valid_value($value)) {
629             return false;
630         }
632         // If present in a FREEBUSY component, must be in UTC format
633         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
634             return false;
635         }
637         return true;
639     }
641     function is_valid_parameter($parameter, $value) {
643         $parameter = strtoupper($parameter);
645         if(!parent::is_valid_parameter($parameter, $value)) {
646             return false;
647         }
648         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
649             return false;
650         }
652         return true;
653     }
656 class iCalendar_property_dtstart extends iCalendar_property {
658     var $name        = 'DTSTART';
659     var $val_type    = RFC2445_TYPE_DATE_TIME;
661     function __construct() {
662         $this->valid_parameters = array(
663             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
664             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
665             RFC2445_XNAME => RFC2445_OPTIONAL
666         );
667     }
669     // TODO: unimplemented stuff when parent is a VTIMEZONE component
671     function is_valid_value($value) {
672         if(!parent::is_valid_value($value)) {
673             return false;
674         }
676         // If present in a FREEBUSY component, must be in UTC format
677         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
678             return false;
679         }
681         return true;
682     }
684     function is_valid_parameter($parameter, $value) {
686         $parameter = strtoupper($parameter);
688         if(!parent::is_valid_parameter($parameter, $value)) {
689             return false;
690         }
691         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
692             return false;
693         }
695         return true;
696     }
699 class iCalendar_property_duration extends iCalendar_property {
701     var $name        = 'DURATION';
702     var $val_type    = RFC2445_TYPE_DURATION;
704     function __construct() {
705         $this->valid_parameters = array(
706             RFC2445_XNAME => RFC2445_OPTIONAL
707         );
708     }
710     function is_valid_value($value) {
711         if(!parent::is_valid_value($value)) {
712             return false;
713         }
715         // Value must be positive
716         return ($value{0} != '-');
717     }
720 class iCalendar_property_freebusy extends iCalendar_property {
722     var $name        = 'FREEBUSY';
723     var $val_type    = RFC2445_TYPE_PERIOD;
724     var $val_multi   = true;
726     function __construct() {
727         $this->valid_parameters = array(
728             'FBTYPE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
729             RFC2445_XNAME => RFC2445_OPTIONAL
730         );
731     }
733     function is_valid_value($value) {
734         if(!parent::is_valid_value($value)) {
735             return false;
736         }
738         $pos = strpos($value, '/'); // We know there's only one / in there
739         if($value{$pos - 1} != 'Z') {
740             // Start time MUST be in UTC
741             return false;
742         }
743         if($value{$pos + 1} != 'P' && substr($value, -1) != 'Z') {
744             // If the second part is not a period, it MUST be in UTC
745             return false;
746         }
748         return true;
749     }
751     // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak)
754 class iCalendar_property_transp extends iCalendar_property {
756     var $name        = 'TRANSP';
757     var $val_type    = RFC2445_TYPE_TEXT;
758     var $val_default = 'OPAQUE';
760     function __construct() {
761         $this->valid_parameters = array(
762             RFC2445_XNAME => RFC2445_OPTIONAL
763         );
764     }
766     function is_valid_value($value) {
767         return ($value === 'TRANSPARENT' || $value === 'OPAQUE');
768     }
771 // TODO: 4.8.3 timezone component properties
774 // 4.8.4 Relationship Component Properties
775 // ---------------------------------------
777 class iCalendar_property_attendee extends iCalendar_property {
779     var $name        = 'ATTENDEE';
780     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
782     // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH
783     // TODO: standard has lots of detail here, make triple sure that we eventually conform
785     function __construct() {
786         $this->valid_parameters = array(
787             'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
788             'CN'             => RFC2445_OPTIONAL | RFC2445_ONCE,
789             'ROLE'           => RFC2445_OPTIONAL | RFC2445_ONCE,
790             'PARTSTAT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
791             'RSVP'           => RFC2445_OPTIONAL | RFC2445_ONCE,
792             'CUTYPE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
793             'MEMBER'         => RFC2445_OPTIONAL | RFC2445_ONCE,
794             'DELEGATED-TO'   => RFC2445_OPTIONAL | RFC2445_ONCE,
795             'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE,
796             'SENT-BY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
797             'DIR'            => RFC2445_OPTIONAL | RFC2445_ONCE,
798             RFC2445_XNAME    => RFC2445_OPTIONAL
799         );
800     }
802     function set_parent_component($componentname) {
803         if(!parent::set_parent_component($componentname)) {
804             return false;
805         }
807         if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') {
808             // Most parameters become invalid in this case, the full allowed set is now:
809             $this->valid_parameters = array(
810                 'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
811                 RFC2445_XNAME    => RFC2445_OPTIONAL
812             );
813         }
815         return false;
816     }
820 class iCalendar_property_contact extends iCalendar_property {
822     var $name        = 'CONTACT';
823     var $val_type    = RFC2445_TYPE_TEXT;
825     function __construct() {
826         $this->valid_parameters = array(
827             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
828             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
829             RFC2445_XNAME => RFC2445_OPTIONAL
830         );
831     }
834 class iCalendar_property_organizer extends iCalendar_property {
836     var $name        = 'ORGANIZER';
837     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
839     function __construct() {
840         $this->valid_parameters = array(
841             'CN'          => RFC2445_OPTIONAL | RFC2445_ONCE,
842             'DIR'         => RFC2445_OPTIONAL | RFC2445_ONCE,
843             'SENT-BY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
844             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
845             RFC2445_XNAME => RFC2445_OPTIONAL
846         );
847     }
849     // TODO:
850 /*    
851    Conformance: This property MUST be specified in an iCalendar object
852    that specifies a group scheduled calendar entity. This property MUST
853    be specified in an iCalendar object that specifies the publication of
854    a calendar user's busy time. This property MUST NOT be specified in
855    an iCalendar object that specifies only a time zone definition or
856    that defines calendar entities that are not group scheduled entities,
857    but are entities only on a single user's calendar.
858 */
862 class iCalendar_property_recurrence_id extends iCalendar_property {
864     // TODO: can only be specified when defining recurring components in the calendar
865 /*
866    Conformance: This property can be specified in an iCalendar object
867    containing a recurring calendar component.
869    Description: The full range of calendar components specified by a
870    recurrence set is referenced by referring to just the "UID" property
871    value corresponding to the calendar component. The "RECURRENCE-ID"
872    property allows the reference to an individual instance within the
873    recurrence set.
874 */
876     var $name        = 'RECURRENCE-ID';
877     var $val_type    = RFC2445_TYPE_DATE_TIME;
879     function __construct() {
880         $this->valid_parameters = array(
881             'RANGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
882             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
883             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
884             RFC2445_XNAME => RFC2445_OPTIONAL
885         );
886     }
888     function is_valid_parameter($parameter, $value) {
890         $parameter = strtoupper($parameter);
892         if(!parent::is_valid_parameter($parameter, $value)) {
893             return false;
894         }
895         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
896             return false;
897         }
899         return true;
900     }
904 class iCalendar_property_related_to extends iCalendar_property {
906     var $name        = 'RELATED-TO';
907     var $val_type    = RFC2445_TYPE_TEXT;
909     // TODO: the value of this property must reference another component's UID
911     function __construct() {
912         $this->valid_parameters = array(
913             'RELTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
914             RFC2445_XNAME => RFC2445_OPTIONAL
915         );
916     }
919 class iCalendar_property_url extends iCalendar_property {
921     var $name        = 'URL';
922     var $val_type    = RFC2445_TYPE_URI;
924     function __construct() {
925         $this->valid_parameters = array(
926             RFC2445_XNAME => RFC2445_OPTIONAL
927         );
928     }
931 class iCalendar_property_uid extends iCalendar_property {
933     var $name        = 'UID';
934     var $val_type    = RFC2445_TYPE_TEXT;
936     function __construct() {
937         $this->valid_parameters = array(
938             RFC2445_XNAME => RFC2445_OPTIONAL
939         );
941         // The exception to the rule: this is not a static value, so we
942         // generate it on-the-fly here. Guaranteed to be different for
943         // each instance of this property, too. Nice.
944         $this->val_default = Bennu::generate_guid();
945     }
948 // 4.8.5 Recurrence Component Properties
949 // -------------------------------------
951 class iCalendar_property_exdate extends iCalendar_property {
953     var $name        = 'EXDATE';
954     var $val_type    = RFC2445_TYPE_DATE_TIME;
955     var $val_multi   = true;
957     function __construct() {
958         $this->valid_parameters = array(
959             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
960             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
961             RFC2445_XNAME => RFC2445_OPTIONAL
962         );
963     }
965     function is_valid_parameter($parameter, $value) {
967         $parameter = strtoupper($parameter);
969         if(!parent::is_valid_parameter($parameter, $value)) {
970             return false;
971         }
972         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
973             return false;
974         }
976         return true;
977     }
981 class iCalendar_property_exrule extends iCalendar_property {
983     var $name        = 'EXRULE';
984     var $val_type    = RFC2445_TYPE_RECUR;
986     function __construct() {
987         $this->valid_parameters = array(
988             RFC2445_XNAME => RFC2445_OPTIONAL
989         );
990     }
993 class iCalendar_property_rdate extends iCalendar_property {
995     var $name        = 'RDATE';
996     var $val_type    = RFC2445_TYPE_DATE_TIME;
997     var $val_multi   = true;
999     function __construct() {
1000         $this->valid_parameters = array(
1001             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
1002             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
1003             RFC2445_XNAME => RFC2445_OPTIONAL
1004         );
1005     }
1007     function is_valid_parameter($parameter, $value) {
1009         $parameter = strtoupper($parameter);
1011         if(!parent::is_valid_parameter($parameter, $value)) {
1012             return false;
1013         }
1014         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) {
1015             return false;
1016         }
1018         return true;
1019     }
1023 class iCalendar_property_rrule extends iCalendar_property {
1025     var $name        = 'RRULE';
1026     var $val_type    = RFC2445_TYPE_RECUR;
1028     function __construct() {
1029         $this->valid_parameters = array(
1030             RFC2445_XNAME => RFC2445_OPTIONAL
1031         );
1032     }
1035 // 4.8.6 Alarm Component Properties
1036 // -------------------------------------------
1037 class iCalendar_property_action extends iCalendar_property {
1038         var $name      = 'ACTION';
1039     var $val_type   = RFC2445_TYPE_TEXT;
1040     
1041     function __construct() {
1042         $this->valid_parameters = array(
1043             RFC2445_XNAME => RFC2445_OPTIONAL
1044         );
1045     }
1046     
1047     function is_valid_value($value) {
1048         if(!parent::is_valid_value($value)) {
1049             return false;
1050         }
1051         
1052         // Value must be one of the following, or an x-name.
1053         $valid_values = array('ACTION', 'DISPLAY', 'EMAIL', 'PROCEDURE');
1054         return(in_array($value, $valid_values) || rfc2445_is_xname($value));        
1055         
1056     }
1059 class iCalendar_property_repeat extends iCalendar_property {
1060     var $name      = 'REPEAT';
1061     var $val_type   = RFC2445_TYPE_INTEGER;
1062     
1063     function __construct() {
1064         $this->valid_parameters = array(
1065             RFC2445_XNAME => RFC2445_OPTIONAL
1066         );
1067     }   
1070 class iCalendar_property_trigger extends iCalendar_property {
1071     var $name      = 'TRIGGER';
1072     var $val_type   = RFC2445_TYPE_TEXT;
1073     
1074     function __construct() {
1075         $this->valid_parameters = array(
1076             'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1077             'RELATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
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         // Must either be DURATION or DATE_TIME type
1087         return(rfc2445_is_valid_value($value, RFC2445_TYPE_DURATION) 
1088             || rfc2445_is_valid_value($value, RFC2445_TYPE_DATE_TIME));
1089     }
1094 // 4.8.7 Change Management Component Properties
1095 // --------------------------------------------
1097 class iCalendar_property_created extends iCalendar_property {
1099     var $name        = 'CREATED';
1100     var $val_type    = RFC2445_TYPE_DATE_TIME;
1102     function __construct() {
1103         $this->valid_parameters = array(
1104             RFC2445_XNAME => RFC2445_OPTIONAL
1105         );
1106     }
1108     function is_valid_value($value) {
1109         if(!parent::is_valid_value($value)) {
1110             return false;
1111         }
1112         // Time MUST be in UTC format
1113         return(substr($value, -1) == 'Z');
1114     }
1117 class iCalendar_property_dtstamp extends iCalendar_property {
1119     var $name        = 'DTSTAMP';
1120     var $val_type    = RFC2445_TYPE_DATE_TIME;
1122     function __construct() {
1123         $this->valid_parameters = array(
1124             RFC2445_XNAME => RFC2445_OPTIONAL
1125         );
1126     }
1128     function is_valid_value($value) {
1129         if(!parent::is_valid_value($value)) {
1130             return false;
1131         }
1132         // Time MUST be in UTC format
1133         return(substr($value, -1) == 'Z');
1134     }
1137 class iCalendar_property_last_modified extends iCalendar_property {
1139     var $name        = 'LAST-MODIFIED';
1140     var $val_type    = RFC2445_TYPE_DATE_TIME;
1142     function __construct() {
1143         $this->valid_parameters = array(
1144             RFC2445_XNAME => RFC2445_OPTIONAL
1145         );
1146     }
1148     function is_valid_value($value) {
1149         if(!parent::is_valid_value($value)) {
1150             return false;
1151         }
1152         // Time MUST be in UTC format
1153         return(substr($value, -1) == 'Z');
1154     }
1157 class iCalendar_property_sequence extends iCalendar_property {
1159     var $name        = 'SEQUENCE';
1160     var $val_type    = RFC2445_TYPE_INTEGER;
1161     var $val_default = 0;
1163     function __construct() {
1164         $this->valid_parameters = array(
1165             RFC2445_XNAME => RFC2445_OPTIONAL
1166         );
1167     }
1169     function is_valid_value($value) {
1170         if(!parent::is_valid_value($value)) {
1171             return false;
1172         }
1173         $value = intval($value);
1174         return ($value >= 0);
1175     }
1178 // 4.8.8 Miscellaneous Component Properties
1179 // ----------------------------------------
1181 class iCalendar_property_x extends iCalendar_property {
1183     var $name        = RFC2445_XNAME;
1184     var $val_type    = NULL;
1186     function __construct() {
1187         $this->valid_parameters = array(
1188             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1189             RFC2445_XNAME => RFC2445_OPTIONAL
1190         );
1191     }
1193     function set_name($name) {
1195         $name = strtoupper($name);
1197         if(rfc2445_is_xname($name)) {
1198             $this->name = $name;
1199             return true;
1200         }
1202         return false;
1203     }
1206 class iCalendar_property_request_status extends iCalendar_property {
1208     // IMPORTANT NOTE: This property value includes TEXT fields
1209     // separated by semicolons. Unfortunately, auto-value-formatting
1210     // cannot be used in this case. As an exception, the value passed
1211     // to this property MUST be already escaped.
1213     var $name        = 'REQUEST-STATUS';
1214     var $val_type    = RFC2445_TYPE_TEXT;
1216     function __construct() {
1217         $this->valid_parameters = array(
1218             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1219             RFC2445_XNAME => RFC2445_OPTIONAL
1220         );
1221     }
1223     function is_valid_value($value) {
1224         if(!is_string($value) || empty($value)) {
1225             return false;
1226         }
1228         $len   = strlen($value);
1229         $parts = array();
1230         $from  = 0;
1231         $escch = false;
1233         for($i = 0; $i < $len; ++$i) {
1234             if($value{$i} == ';' && !$escch) {
1235                 // Token completed
1236                 $parts[] = substr($value, $from, $i - $from);
1237                 $from = $i + 1;
1238                 continue;
1239             }
1240             $escch = ($value{$i} == '\\');
1241         }
1242         // Add one last token with the remaining text; if the value
1243         // ended with a ';' it was illegal, so check that this token
1244         // is not the empty string.
1245         $parts[] = substr($value, $from);
1247         $count = count($parts);
1249         // May have 2 or 3 tokens (last one is optional)
1250         if($count != 2 && $count != 3) {
1251             return false;
1252         }
1254         // REMEMBER: if ANY part is empty, we have an illegal value
1256         // First token must be hierarchical numeric status (3 levels max)
1257         if(strlen($parts[0]) == 0) {
1258             return false;
1259         }
1261         if($parts[0]{0} < '1' || $parts[0]{0} > '4') {
1262             return false;
1263         }
1265         $len = strlen($parts[0]);
1267         // Max 3 levels, and can't end with a period
1268         if($len > 5 || $parts[0]{$len - 1} == '.') {
1269             return false;
1270         }
1272         for($i = 1; $i < $len; ++$i) {
1273             if(($i & 1) == 1 && $parts[0]{$i} != '.') {
1274                 // Even-indexed chars must be periods
1275                 return false;
1276             }
1277             else if(($i & 1) == 0 && ($parts[0]{$i} < '0' || $parts[0]{$i} > '9')) {
1278                 // Odd-indexed chars must be numbers
1279                 return false;
1280             }
1281         }
1283         // Second and third tokens must be TEXT, and already escaped, so
1284         // they are not allowed to have UNESCAPED semicolons, commas, slashes,
1285         // or any newlines at all
1287         for($i = 1; $i < $count; ++$i) {
1288             if(strpos($parts[$i], "\n") !== false) {
1289                 return false;
1290             }
1292             $len = strlen($parts[$i]);
1293             if($len == 0) {
1294                 // Cannot be empty
1295                 return false;
1296             }
1298             $parts[$i] .= '#'; // This guard token saves some conditionals in the loop
1300             for($j = 0; $j < $len; ++$j) {
1301                 $thischar = $parts[$i]{$j};
1302                 $nextchar = $parts[$i]{$j + 1};
1303                 if($thischar == '\\') {
1304                     // Next char must now be one of ";,\nN"
1305                     if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' &&
1306                        $nextchar != 'n' && $nextchar != 'N') {
1307                         return false;
1308                     }
1310                     // OK, this escaped sequence is correct, bypass next char
1311                     ++$j;
1312                     continue;
1313                 }
1314                 if($thischar == ';' || $thischar == ',' || $thischar == '\\') {
1315                     // This wasn't escaped as it should
1316                     return false;
1317                 }
1318             }
1319         }
1321         return true;
1322     }
1324     function set_value($value) {
1325         // Must override this, otherwise the value would be quoted again
1326         if($this->is_valid_value($value)) {
1327             $this->value = $value;
1328             return true;
1329         }
1331         return false;
1332     }
1336 class iCalendar_property_tzid extends iCalendar_property {
1338     var $name        = 'TZID';
1339     var $val_type    = RFC2445_TYPE_TEXT;
1341     function __construct() {
1342         $this->valid_parameters = array(
1343             RFC2445_XNAME => RFC2445_OPTIONAL
1344         );
1345     }
1347     function is_valid_value($value) {
1348         if(!parent::is_valid_value($value)) {
1349             return false;
1350         } else {
1351             return true;
1352         }
1353     }
1356 class iCalendar_property_tzname extends iCalendar_property {
1358     var $name        = 'TZNAME';
1359     var $val_type    = RFC2445_TYPE_TEXT;
1361     function __construct() {
1362         $this->valid_parameters = array(
1363             'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1364             RFC2445_XNAME => RFC2445_OPTIONAL
1365         );
1366     }
1368     function is_valid_value($value) {
1369         if(!parent::is_valid_value($value)) {
1370             return false;
1371         } else {
1372             return true;
1373         }
1374     }
1377 class iCalendar_property_tzoffsetfrom extends iCalendar_property {
1379     var $name        = 'TZOFFSETFROM';
1380     var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1382     function __construct() {
1383         $this->valid_parameters = array(
1384             RFC2445_XNAME => RFC2445_OPTIONAL
1385         );
1386     }
1388     function is_valid_value($value) {
1389         if(!parent::is_valid_value($value)) {
1390             return false;
1391         } else {
1392             return true;
1393         }
1394     }
1397 class iCalendar_property_tzoffsetto extends iCalendar_property {
1399     var $name        = 'TZOFFSETTO';
1400     var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1402     function __construct() {
1403         $this->valid_parameters = array(
1404             RFC2445_XNAME => RFC2445_OPTIONAL
1405         );
1406     }
1408     function is_valid_value($value) {
1409         if(!parent::is_valid_value($value)) {
1410             return false;
1411         } else {
1412                 return true;
1413         }
1414     }
1419 #######################
1420 /*
1421 class iCalendar_property_class extends iCalendar_property {
1423     var $name        = 'CLASS';
1424     var $val_type    = RFC2445_TYPE_TEXT;
1426     function __construct() {
1427         $this->valid_parameters = array(
1428             RFC2445_XNAME => RFC2445_OPTIONAL
1429         );
1430     }
1432 */