9da0dc08c713cdf8b1272d92db4a951394211eff
[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 iCalendar_property() {
29         $this->construct();
30     }
32     function construct() {
33         $this->parameters = array();
34     }
36     // If some property needs extra care with its parameters, override this
37     // IMPORTANT: the parameter name MUST BE CAPITALIZED!
38     function is_valid_parameter($parameter, $value) {
40         if(is_array($value)) {
41             if(!iCalendar_parameter::multiple_values_allowed($parameter)) {
42                 return false;
43             }
44             foreach($value as $item) {
45                 if(!iCalendar_parameter::is_valid_value($this, $parameter, $item)) {
46                     return false;
47                 }
48             }
49             return true;
50         }
52         return iCalendar_parameter::is_valid_value($this, $parameter, $value);
53     }
55     function invariant_holds() {
56         return true;
57     }
59     // If some property is very picky about its values, it should do the work itself
60     // Only data type validation is done here
61     function is_valid_value($value) {
62         if(is_array($value)) {
63             if(!$this->val_multi) {
64                 return false;
65             }
66             else {
67                 foreach($value as $oneval) {
68                     if(!rfc2445_is_valid_value($oneval, $this->val_type)) {
69                         return false;
70                     }
71                 }
72             }
73             return true;
74         }
75         return rfc2445_is_valid_value($value, $this->val_type);
76     }
78     function default_value() {
79         return $this->val_default;
80     }
82     function set_parent_component($componentname) {
83         if(class_exists('iCalendar_'.strtolower(substr($componentname, 1)))) {
84             $this->parent_component = strtoupper($componentname);
85             return true;
86         }
88         return false;
89     }
91     function set_value($value) {
92         if($this->is_valid_value($value)) {
93             // This transparently formats any value type according to the iCalendar specs
94             if(is_array($value)) {
95                 foreach($value as $key => $item) {
96                     $value[$key] = rfc2445_do_value_formatting($item, $this->val_type);
97                 }
98                 $this->value = implode(',', $value);
99             }
100             else {
101                 $this->value = rfc2445_do_value_formatting($value, $this->val_type);
102             }
103             
104             return true;
105         }
106         return false;
107     }
109     function get_value() {
110         // First of all, assume that we have multiple values
111         $valarray = explode('\\,', $this->value);
113         // Undo transparent formatting
114         $replace_function = create_function('$a', 'return rfc2445_undo_value_formatting($a, '.$this->val_type.');');
115         $valarray = array_map($replace_function, $valarray);
117         // Now, if this property cannot have multiple values, don't return as an array
118         if(!$this->val_multi) {
119             return $valarray[0];
120         }
122         // Otherwise return an array even if it has one element, for uniformity
123         return $valarray;
125     }
127     function set_parameter($name, $value) {
129         // Uppercase
130         $name = strtoupper($name);
132         // Are we trying to add a valid parameter?
133         $xname = false;
134         if(!isset($this->valid_parameters[$name])) {
135             // If not, is it an x-name as per RFC 2445?
136             if(!rfc2445_is_xname($name)) {
137                 return false;
138             }
139             // No more checks -- all components are supposed to allow x-name parameters
140             $xname = true;
141         }
143         if(!$this->is_valid_parameter($name, $value)) {
144             return false;
145         }
147         if(is_array($value)) {
148             foreach($value as $key => $element) {
149                 $value[$key] = iCalendar_parameter::do_value_formatting($name, $element);
150             }
151         }
152         else {
153             $value = iCalendar_parameter::do_value_formatting($name, $value);
154         }
156         $this->parameters[$name] = $value;
158         // Special case: if we just changed the VALUE parameter, reflect this
159         // in the object's status so that it only accepts correct type values
160         if($name == 'VALUE') {
161             // TODO: what if this invalidates an already-set value?
162             $this->val_type = constant('RFC2445_TYPE_'.str_replace('-', '_', $value));
163         }
165         return true;
167     }
169     function get_parameter($name) {
171         // Uppercase
172         $name = strtoupper($name);
174         if(isset($this->parameters[$name])) {
175             // If there are any double quotes in the value, invisibly strip them
176             if(is_array($this->parameters[$name])) {
177                 foreach($this->parameters[$name] as $key => $value) {
178                     if(substr($value, 0, 1) == '"') {
179                        $this->parameters[$name][$key] = substr($value, 1, strlen($value) - 2);
180                     }
181                 }
182                 return $this->parameters[$name];
183             }
185             else {
186                 if(substr($this->parameters[$name], 0, 1) == '"') {
187                     return substr($this->parameters[$name], 1, strlen($this->parameters[$name]) - 2);
188                 }
189             }
190         }
192         return NULL;
193     }
195     function serialize() {
196         $string = $this->name;
198         if(!empty($this->parameters)) {
199             foreach($this->parameters as $name => $value) {
200                 $string .= ';'.$name.'=';
201                 if(is_array($value)) {
202                     $string .= implode(',', $value);
203                 }
204                 else {
205                     $string .= $value;
206                 }
207             }
208         }
210         $string .= ':'.$this->value;
212         return rfc2445_fold($string) . RFC2445_CRLF;
213     }
216 // 4.7 Calendar Properties
217 // -----------------------
219 class iCalendar_property_calscale extends iCalendar_property {
221     var $name        = 'CALSCALE';
222     var $val_type    = RFC2445_TYPE_TEXT;
224     function construct() {
225         $this->valid_parameters = array(
226             RFC2445_XNAME => RFC2445_OPTIONAL
227         );
228     }
230     function is_valid_value($value) {
231         // This is case-sensitive
232         return ($value === 'GREGORIAN');
233     }
236 class iCalendar_property_method extends iCalendar_property {
238     var $name        = 'METHOD';
239     var $val_type    = RFC2445_TYPE_TEXT;
241     function construct() {
242         $this->valid_parameters = array(
243             RFC2445_XNAME => RFC2445_OPTIONAL
244         );
245     }
247     function is_valid_value($value) {
248         // This is case-sensitive
249         // Methods from RFC 2446
250         $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER');
251         return in_array($value, $methods);
252     }
255 class iCalendar_property_prodid extends iCalendar_property {
257     var $name        = 'PRODID';
258     var $val_type    = RFC2445_TYPE_TEXT;
259     var $val_default = NULL;
261     function construct() {
262         $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN';
264         $this->valid_parameters = array(
265             RFC2445_XNAME => RFC2445_OPTIONAL
266         );
267     }
270 class iCalendar_property_version extends iCalendar_property {
272     var $name        = 'VERSION';
273     var $val_type    = RFC2445_TYPE_TEXT;
274     var $val_default = '2.0';
276     function 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         $this->valid_parameters = array(
298             'FMTTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
299             'ENCODING'    => RFC2445_OPTIONAL | RFC2445_ONCE,
300             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
301             RFC2445_XNAME => RFC2445_OPTIONAL
302         );
303     }
305     function invariant_holds() {
306         if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) {
307             return false;
308         }
309         if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) {
310             return false;
311         }
313         return true;
314     }
316     function is_valid_parameter($parameter, $value) {
318         $parameter = strtoupper($parameter);
320         if(!parent::is_valid_parameter($parameter, $value)) {
321             return false;
322         }
324         if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') {
325             return false;
326         }
328         if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') {
329             return false;
330         }
332         return true;
333     }
336 class iCalendar_property_categories extends iCalendar_property {
338     var $name        = 'CATEGORIES';
339     var $val_type    = RFC2445_TYPE_TEXT;
340     var $val_multi   = true;
342     function construct() {
343         $this->valid_parameters = array(
344             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
345             RFC2445_XNAME => RFC2445_OPTIONAL
346         );
347     }
350 class iCalendar_property_class extends iCalendar_property {
352     var $name        = 'CLASS';
353     var $val_type    = RFC2445_TYPE_TEXT;
354     var $val_default = 'PUBLIC';
356     function construct() {
357         $this->valid_parameters = array(
358             RFC2445_XNAME => RFC2445_OPTIONAL
359         );
360     }
362     function is_valid_value($value) {
363         // If this is not an xname, it is case-sensitive
364         return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value)));
365     }
368 class iCalendar_property_comment extends iCalendar_property {
370     var $name        = 'COMMENT';
371     var $val_type    = RFC2445_TYPE_TEXT;
373     function construct() {
374         $this->valid_parameters = array(
375             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
376             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
377             RFC2445_XNAME => RFC2445_OPTIONAL
378         );
379     }
382 class iCalendar_property_description extends iCalendar_property {
384     var $name        = 'DESCRIPTION';
385     var $val_type    = RFC2445_TYPE_TEXT;
387     function construct() {
388         $this->valid_parameters = array(
389             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
390             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
391             RFC2445_XNAME => RFC2445_OPTIONAL
392         );
393     }
396 class iCalendar_property_geo extends iCalendar_property {
398     var $name        = 'GEO';
399     var $val_type    = RFC2445_TYPE_TEXT;
401     function construct() {
402         $this->valid_parameters = array(
403             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
404             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
405             RFC2445_XNAME => RFC2445_OPTIONAL
406         );
407     }
409     function is_valid_value($value) {
410         // This MUST be two floats separated by a semicolon
411         if(!is_string($value)) {
412             return false;
413         }
415         $floats = explode(';', $value);
416         if(count($floats) != 2) {
417             return false;
418         }
420         return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT);
421     }
423     function set_value($value) {
424         // Must override this, otherwise the semicolon separating
425         // the two floats would get auto-quoted, which is illegal
426         if($this->is_valid_value($value)) {
427             $this->value = $value;
428             return true;
429         }
431         return false;
432     }
436 class iCalendar_property_location extends iCalendar_property {
438     var $name        = 'LOCATION';
439     var $val_type    = RFC2445_TYPE_TEXT;
441     function construct() {
442         $this->valid_parameters = array(
443             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
444             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
445             RFC2445_XNAME => RFC2445_OPTIONAL
446         );
447     }
450 class iCalendar_property_percent_complete extends iCalendar_property {
452     var $name        = 'PERCENT-COMPLETE';
453     var $val_type    = RFC2445_TYPE_INTEGER;
455     function construct() {
456         $this->valid_parameters = array(
457             RFC2445_XNAME => RFC2445_OPTIONAL
458         );
459     }
461     function is_valid_value($value) {
462         // Only integers between 0 and 100 inclusive allowed
463         if(!parent::is_valid_value($value)) {
464             return false;
465         }
466         $value = intval($value);
467         return ($value >= 0 && $value <= 100);
468     }
472 class iCalendar_property_priority extends iCalendar_property {
474     var $name        = 'PRIORITY';
475     var $val_type    = RFC2445_TYPE_INTEGER;
477     function construct() {
478         $this->valid_parameters = array(
479             RFC2445_XNAME => RFC2445_OPTIONAL
480         );
481     }
483     function is_valid_value($value) {
484         // Only integers between 0 and 9 inclusive allowed        
485         if(!parent::is_valid_value($value)) {
486             return false;
487         }
489         $value = intval($value);
490         return ($value >= 0 && $value <= 9);
491     }
494 class iCalendar_property_resources extends iCalendar_property {
496     var $name        = 'RESOURCES';
497     var $val_type    = RFC2445_TYPE_TEXT;
498     var $val_multi   = true;
500     function construct() {
501         $this->valid_parameters = array(
502             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
503             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
504             RFC2445_XNAME => RFC2445_OPTIONAL
505         );
506     }
509 class iCalendar_property_status extends iCalendar_property {
511     var $name        = 'STATUS';
512     var $val_type    = RFC2445_TYPE_TEXT;
514     function construct() {
515         $this->valid_parameters = array(
516             RFC2445_XNAME => RFC2445_OPTIONAL
517         );
518     }
520     function is_valid_value($value) {
521         // This is case-sensitive
522         switch ($this->parent_component) {
523             case 'VEVENT':
524                 $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED');
525             break;
526             case 'VTODO':
527                 $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED');
528             break;
529             case 'VJOURNAL':
530                 $allowed = array('DRAFT', 'FINAL', 'CANCELLED');
531             break;
532         }
533         return in_array($value, $allowed);
535     }
539 class iCalendar_property_summary extends iCalendar_property {
541     var $name        = 'SUMMARY';
542     var $val_type    = RFC2445_TYPE_TEXT;
544     function construct() {
545         $this->valid_parameters = array(
546             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
547             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
548             RFC2445_XNAME => RFC2445_OPTIONAL
549         );
550     }
553 // 4.8.2 Date and Time Component Properties
554 // ----------------------------------------
556 class iCalendar_property_completed extends iCalendar_property {
558     var $name        = 'COMPLETED';
559     var $val_type    = RFC2445_TYPE_DATE_TIME;
561     function construct() {
562         $this->valid_parameters = array(
563             RFC2445_XNAME => RFC2445_OPTIONAL
564         );
565     }
567     function is_valid_value($value) {
568         if(!parent::is_valid_value($value)) {
569             return false;
570         }
571         // Time MUST be in UTC format
572         return(substr($value, -1) == 'Z');
573     }
576 class iCalendar_property_dtend extends iCalendar_property {
578     var $name        = 'DTEND';
579     var $val_type    = RFC2445_TYPE_DATE_TIME;
581     function construct() {
582         $this->valid_parameters = array(
583             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
584             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
585             RFC2445_XNAME => RFC2445_OPTIONAL
586         );
587     }
589     function is_valid_value($value) {
590         if(!parent::is_valid_value($value)) {
591             return false;
592         }
594         // If present in a FREEBUSY component, must be in UTC format
595         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
596             return false;
597         }
599         return true;
601     }
603     function is_valid_parameter($parameter, $value) {
605         $parameter = strtoupper($parameter);
607         if(!parent::is_valid_parameter($parameter, $value)) {
608             return false;
609         }
610         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
611             return false;
612         }
614         return true;
615     }
618 class iCalendar_property_due extends iCalendar_property {
620     var $name        = 'DUE';
621     var $val_type    = RFC2445_TYPE_DATE_TIME;
623     function construct() {
624         $this->valid_parameters = array(
625             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
626             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
627             RFC2445_XNAME => RFC2445_OPTIONAL
628         );
629     }
631     function is_valid_value($value) {
632         if(!parent::is_valid_value($value)) {
633             return false;
634         }
636         // If present in a FREEBUSY component, must be in UTC format
637         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
638             return false;
639         }
641         return true;
643     }
645     function is_valid_parameter($parameter, $value) {
647         $parameter = strtoupper($parameter);
649         if(!parent::is_valid_parameter($parameter, $value)) {
650             return false;
651         }
652         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
653             return false;
654         }
656         return true;
657     }
660 class iCalendar_property_dtstart extends iCalendar_property {
662     var $name        = 'DTSTART';
663     var $val_type    = RFC2445_TYPE_DATE_TIME;
665     function construct() {
666         $this->valid_parameters = array(
667             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
668             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
669             RFC2445_XNAME => RFC2445_OPTIONAL
670         );
671     }
673     // TODO: unimplemented stuff when parent is a VTIMEZONE component
675     function is_valid_value($value) {
676         if(!parent::is_valid_value($value)) {
677             return false;
678         }
680         // If present in a FREEBUSY component, must be in UTC format
681         if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
682             return false;
683         }
685         return true;
686     }
688     function is_valid_parameter($parameter, $value) {
690         $parameter = strtoupper($parameter);
692         if(!parent::is_valid_parameter($parameter, $value)) {
693             return false;
694         }
695         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
696             return false;
697         }
699         return true;
700     }
703 class iCalendar_property_duration extends iCalendar_property {
705     var $name        = 'DURATION';
706     var $val_type    = RFC2445_TYPE_DURATION;
708     function construct() {
709         $this->valid_parameters = array(
710             RFC2445_XNAME => RFC2445_OPTIONAL
711         );
712     }
714     function is_valid_value($value) {
715         if(!parent::is_valid_value($value)) {
716             return false;
717         }
719         // Value must be positive
720         return ($value{0} != '-');
721     }
724 class iCalendar_property_freebusy extends iCalendar_property {
726     var $name        = 'FREEBUSY';
727     var $val_type    = RFC2445_TYPE_PERIOD;
728     var $val_multi   = true;
730     function construct() {
731         $this->valid_parameters = array(
732             'FBTYPE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
733             RFC2445_XNAME => RFC2445_OPTIONAL
734         );
735     }
737     function is_valid_value($value) {
738         if(!parent::is_valid_value($value)) {
739             return false;
740         }
742         $pos = strpos($value, '/'); // We know there's only one / in there
743         if($value{$pos - 1} != 'Z') {
744             // Start time MUST be in UTC
745             return false;
746         }
747         if($value{$pos + 1} != 'P' && $substr($value, -1) != 'Z') {
748             // If the second part is not a period, it MUST be in UTC
749             return false;
750         }
752         return true;
753     }
755     // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak)
758 class iCalendar_property_transp extends iCalendar_property {
760     var $name        = 'TRANSP';
761     var $val_type    = RFC2445_TYPE_TEXT;
762     var $val_default = 'OPAQUE';
764     function construct() {
765         $this->valid_parameters = array(
766             RFC2445_XNAME => RFC2445_OPTIONAL
767         );
768     }
770     function is_valid_value($value) {
771         return ($value === 'TRANSPARENT' || $value === 'OPAQUE');
772     }
775 // TODO: 4.8.3 timezone component properties
778 // 4.8.4 Relationship Component Properties
779 // ---------------------------------------
781 class iCalendar_property_attendee extends iCalendar_property {
783     var $name        = 'ATTENDEE';
784     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
786     // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH
787     // TODO: standard has lots of detail here, make triple sure that we eventually conform
789     function construct() {
790         $this->valid_parameters = array(
791             'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
792             'CN'             => RFC2445_OPTIONAL | RFC2445_ONCE,
793             'ROLE'           => RFC2445_OPTIONAL | RFC2445_ONCE,
794             'PARTSTAT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
795             'RSVP'           => RFC2445_OPTIONAL | RFC2445_ONCE,
796             'CUTYPE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
797             'MEMBER'         => RFC2445_OPTIONAL | RFC2445_ONCE,
798             'DELEGATED-TO'   => RFC2445_OPTIONAL | RFC2445_ONCE,
799             'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE,
800             'SENT-BY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
801             'DIR'            => RFC2445_OPTIONAL | RFC2445_ONCE,
802             RFC2445_XNAME    => RFC2445_OPTIONAL
803         );
804     }
806     function set_parent_component($componentname) {
807         if(!parent::set_parent_component($componentname)) {
808             return false;
809         }
811         if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') {
812             // Most parameters become invalid in this case, the full allowed set is now:
813             $this->valid_parameters = array(
814                 'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
815                 RFC2445_XNAME    => RFC2445_OPTIONAL
816             );
817         }
819         return false;
820     }
824 class iCalendar_property_contact extends iCalendar_property {
826     var $name        = 'CONTACT';
827     var $val_type    = RFC2445_TYPE_TEXT;
829     function construct() {
830         $this->valid_parameters = array(
831             'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
832             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
833             RFC2445_XNAME => RFC2445_OPTIONAL
834         );
835     }
838 class iCalendar_property_organizer extends iCalendar_property {
840     var $name        = 'ORGANIZER';
841     var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
843     function construct() {
844         $this->valid_parameters = array(
845             'CN'          => RFC2445_OPTIONAL | RFC2445_ONCE,
846             'DIR'         => RFC2445_OPTIONAL | RFC2445_ONCE,
847             'SENT-BY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
848             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
849             RFC2445_XNAME => RFC2445_OPTIONAL
850         );
851     }
853     // TODO:
854 /*    
855    Conformance: This property MUST be specified in an iCalendar object
856    that specifies a group scheduled calendar entity. This property MUST
857    be specified in an iCalendar object that specifies the publication of
858    a calendar user's busy time. This property MUST NOT be specified in
859    an iCalendar object that specifies only a time zone definition or
860    that defines calendar entities that are not group scheduled entities,
861    but are entities only on a single user's calendar.
862 */
866 class iCalendar_property_recurrence_id extends iCalendar_property {
868     // TODO: can only be specified when defining recurring components in the calendar
869 /*
870    Conformance: This property can be specified in an iCalendar object
871    containing a recurring calendar component.
873    Description: The full range of calendar components specified by a
874    recurrence set is referenced by referring to just the "UID" property
875    value corresponding to the calendar component. The "RECURRENCE-ID"
876    property allows the reference to an individual instance within the
877    recurrence set.
878 */
880     var $name        = 'RECURRENCE-ID';
881     var $val_type    = RFC2445_TYPE_DATE_TIME;
883     function construct() {
884         $this->valid_parameters = array(
885             'RANGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
886             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
887             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
888             RFC2445_XNAME => RFC2445_OPTIONAL
889         );
890     }
892     function is_valid_parameter($parameter, $value) {
894         $parameter = strtoupper($parameter);
896         if(!parent::is_valid_parameter($parameter, $value)) {
897             return false;
898         }
899         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
900             return false;
901         }
903         return true;
904     }
908 class iCalendar_property_related_to extends iCalendar_property {
910     var $name        = 'RELATED-TO';
911     var $val_type    = RFC2445_TYPE_TEXT;
913     // TODO: the value of this property must reference another component's UID
915     function construct() {
916         $this->valid_parameters = array(
917             'RELTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
918             RFC2445_XNAME => RFC2445_OPTIONAL
919         );
920     }
923 class iCalendar_property_url extends iCalendar_property {
925     var $name        = 'URL';
926     var $val_type    = RFC2445_TYPE_URI;
928     function construct() {
929         $this->valid_parameters = array(
930             RFC2445_XNAME => RFC2445_OPTIONAL
931         );
932     }
935 class iCalendar_property_uid extends iCalendar_property {
937     var $name        = 'UID';
938     var $val_type    = RFC2445_TYPE_TEXT;
940     function construct() {
941         $this->valid_parameters = array(
942             RFC2445_XNAME => RFC2445_OPTIONAL
943         );
945         // The exception to the rule: this is not a static value, so we
946         // generate it on-the-fly here. Guaranteed to be different for
947         // each instance of this property, too. Nice.
948         $this->val_default = Bennu::generate_guid();
949     }
952 // 4.8.5 Recurrence Component Properties
953 // -------------------------------------
955 class iCalendar_property_exdate extends iCalendar_property {
957     var $name        = 'EXDATE';
958     var $val_type    = RFC2445_TYPE_DATE_TIME;
959     var $val_multi   = true;
961     function construct() {
962         $this->valid_parameters = array(
963             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
964             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
965             RFC2445_XNAME => RFC2445_OPTIONAL
966         );
967     }
969     function is_valid_parameter($parameter, $value) {
971         $parameter = strtoupper($parameter);
973         if(!parent::is_valid_parameter($parameter, $value)) {
974             return false;
975         }
976         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
977             return false;
978         }
980         return true;
981     }
985 class iCalendar_property_exrule extends iCalendar_property {
987     var $name        = 'EXRULE';
988     var $val_type    = RFC2445_TYPE_RECUR;
990     function construct() {
991         $this->valid_parameters = array(
992             RFC2445_XNAME => RFC2445_OPTIONAL
993         );
994     }
997 class iCalendar_property_rdate extends iCalendar_property {
999     var $name        = 'RDATE';
1000     var $val_type    = RFC2445_TYPE_DATE_TIME;
1001     var $val_multi   = true;
1003     function construct() {
1004         $this->valid_parameters = array(
1005             'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
1006             'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
1007             RFC2445_XNAME => RFC2445_OPTIONAL
1008         );
1009     }
1011     function is_valid_parameter($parameter, $value) {
1013         $parameter = strtoupper($parameter);
1015         if(!parent::is_valid_parameter($parameter, $value)) {
1016             return false;
1017         }
1018         if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) {
1019             return false;
1020         }
1022         return true;
1023     }
1027 class iCalendar_property_rrule extends iCalendar_property {
1029     var $name        = 'RRULE';
1030     var $val_type    = RFC2445_TYPE_RECUR;
1032     function construct() {
1033         $this->valid_parameters = array(
1034             RFC2445_XNAME => RFC2445_OPTIONAL
1035         );
1036     }
1039 // TODO: 4.8.6 Alarm Component Properties
1041 // 4.8.7 Change Management Component Properties
1042 // --------------------------------------------
1044 class iCalendar_property_created extends iCalendar_property {
1046     var $name        = 'CREATED';
1047     var $val_type    = RFC2445_TYPE_DATE_TIME;
1049     function construct() {
1050         $this->valid_parameters = array(
1051             RFC2445_XNAME => RFC2445_OPTIONAL
1052         );
1053     }
1055     function is_valid_value($value) {
1056         if(!parent::is_valid_value($value)) {
1057             return false;
1058         }
1059         // Time MUST be in UTC format
1060         return(substr($value, -1) == 'Z');
1061     }
1064 class iCalendar_property_dtstamp extends iCalendar_property {
1066     var $name        = 'DTSTAMP';
1067     var $val_type    = RFC2445_TYPE_DATE_TIME;
1069     function construct() {
1070         $this->valid_parameters = array(
1071             RFC2445_XNAME => RFC2445_OPTIONAL
1072         );
1073     }
1075     function is_valid_value($value) {
1076         if(!parent::is_valid_value($value)) {
1077             return false;
1078         }
1079         // Time MUST be in UTC format
1080         return(substr($value, -1) == 'Z');
1081     }
1084 class iCalendar_property_last_modified extends iCalendar_property {
1086     var $name        = 'LAST-MODIFIED';
1087     var $val_type    = RFC2445_TYPE_DATE_TIME;
1089     function construct() {
1090         $this->valid_parameters = array(
1091             RFC2445_XNAME => RFC2445_OPTIONAL
1092         );
1093     }
1095     function is_valid_value($value) {
1096         if(!parent::is_valid_value($value)) {
1097             return false;
1098         }
1099         // Time MUST be in UTC format
1100         return(substr($value, -1) == 'Z');
1101     }
1104 class iCalendar_property_sequence extends iCalendar_property {
1106     var $name        = 'SEQUENCE';
1107     var $val_type    = RFC2445_TYPE_INTEGER;
1108     var $val_default = 0;
1110     function construct() {
1111         $this->valid_parameters = array(
1112             RFC2445_XNAME => RFC2445_OPTIONAL
1113         );
1114     }
1116     function is_valid_value($value) {
1117         if(!parent::is_valid_value($value)) {
1118             return false;
1119         }
1120         $value = intval($value);
1121         return ($value >= 0);
1122     }
1125 // 4.8.8 Miscellaneous Component Properties
1126 // ----------------------------------------
1128 class iCalendar_property_x extends iCalendar_property {
1130     var $name        = RFC2445_XNAME;
1131     var $val_type    = NULL;
1133     function construct() {
1134         $this->valid_parameters = array(
1135             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1136             RFC2445_XNAME => RFC2445_OPTIONAL
1137         );
1138     }
1140     function set_name($name) {
1142         $name = strtoupper($name);
1144         if(rfc2445_is_xname($name)) {
1145             $this->name = $name;
1146             return true;
1147         }
1149         return false;
1150     }
1153 class iCalendar_property_request_status extends iCalendar_property {
1155     // IMPORTANT NOTE: This property value includes TEXT fields
1156     // separated by semicolons. Unfortunately, auto-value-formatting
1157     // cannot be used in this case. As an exception, the value passed
1158     // to this property MUST be already escaped.
1160     var $name        = 'REQUEST-STATUS';
1161     var $val_type    = RFC2445_TYPE_TEXT;
1163     function construct() {
1164         $this->valid_parameters = array(
1165             'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1166             RFC2445_XNAME => RFC2445_OPTIONAL
1167         );
1168     }
1170     function is_valid_value($value) {
1171         if(!is_string($value) || empty($value)) {
1172             return false;
1173         }
1175         $len   = strlen($value);
1176         $parts = array();
1177         $from  = 0;
1178         $escch = false;
1180         for($i = 0; $i < $len; ++$i) {
1181             if($value{$i} == ';' && !$escch) {
1182                 // Token completed
1183                 $parts[] = substr($value, $from, $i - $from);
1184                 $from = $i + 1;
1185                 continue;
1186             }
1187             $escch = ($value{$i} == '\\');
1188         }
1189         // Add one last token with the remaining text; if the value
1190         // ended with a ';' it was illegal, so check that this token
1191         // is not the empty string.
1192         $parts[] = substr($value, $from);
1194         $count = count($parts);
1196         // May have 2 or 3 tokens (last one is optional)
1197         if($count != 2 && $count != 3) {
1198             return false;
1199         }
1201         // REMEMBER: if ANY part is empty, we have an illegal value
1203         // First token must be hierarchical numeric status (3 levels max)
1204         if(strlen($parts[0]) == 0) {
1205             return false;
1206         }
1208         if($parts[0]{0} < '1' || $parts[0]{0} > '4') {
1209             return false;
1210         }
1212         $len = strlen($parts[0]);
1214         // Max 3 levels, and can't end with a period
1215         if($len > 5 || $parts[0]{$len - 1} == '.') {
1216             return false;
1217         }
1219         for($i = 1; $i < $len; ++$i) {
1220             if(($i & 1) == 1 && $parts[0]{$i} != '.') {
1221                 // Even-indexed chars must be periods
1222                 return false;
1223             }
1224             else if(($i & 1) == 0 && ($parts[0]{$i} < '0' || $parts[0]{$i} > '9')) {
1225                 // Odd-indexed chars must be numbers
1226                 return false;
1227             }
1228         }
1230         // Second and third tokens must be TEXT, and already escaped, so
1231         // they are not allowed to have UNESCAPED semicolons, commas, slashes,
1232         // or any newlines at all
1234         for($i = 1; $i < $count; ++$i) {
1235             if(strpos($parts[$i], "\n") !== false) {
1236                 return false;
1237             }
1239             $len = strlen($parts[$i]);
1240             if($len == 0) {
1241                 // Cannot be empty
1242                 return false;
1243             }
1245             $parts[$i] .= '#'; // This guard token saves some conditionals in the loop
1247             for($j = 0; $j < $len; ++$j) {
1248                 $thischar = $parts[$i]{$j};
1249                 $nextchar = $parts[$i]{$j + 1};
1250                 if($thischar == '\\') {
1251                     // Next char must now be one of ";,\nN"
1252                     if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' &&
1253                        $nextchar != 'n' && $nextchar != 'N') {
1254                         return false;
1255                     }
1257                     // OK, this escaped sequence is correct, bypass next char
1258                     ++$j;
1259                     continue;
1260                 }
1261                 if($thischar == ';' || $thischar == ',' || $thischar == '\\') {
1262                     // This wasn't escaped as it should
1263                     return false;
1264                 }
1265             }
1266         }
1268         return true;
1269     }
1271     function set_value($value) {
1272         // Must override this, otherwise the value would be quoted again
1273         if($this->is_valid_value($value)) {
1274             $this->value = $value;
1275             return true;
1276         }
1278         return false;
1279     }
1284 #######################
1285 /*
1286 class iCalendar_property_class extends iCalendar_property {
1288     var $name        = 'CLASS';
1289     var $val_type    = RFC2445_TYPE_TEXT;
1291     function construct() {
1292         $this->valid_parameters = array(
1293             RFC2445_XNAME => RFC2445_OPTIONAL
1294         );
1295     }
1297 */