Merge branch 'MDL-78457' of https://github.com/paulholden/moodle
[moodle.git] / lib / bennu / iCalendar_components.php
blob912e818b50dae803af350eb7dc6833121f934fe3
1 <?php
3 /**
4 * BENNU - PHP iCalendar library
5 * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
7 * Released under the LGPL.
9 * See http://bennu.sourceforge.net/ for more information and downloads.
11 * @author Ioannis Papaioannou
12 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15 class iCalendar_component {
16 var $name = NULL;
17 var $properties = NULL;
18 var $components = NULL;
19 var $valid_properties = NULL;
20 var $valid_components = NULL;
21 /**
22 * Added to hold errors from last run of unserialize
23 * @var $parser_errors array
25 var $parser_errors = NULL;
27 function __construct() {
28 // Initialize the components array
29 if(empty($this->components)) {
30 $this->components = array();
31 foreach($this->valid_components as $name) {
32 $this->components[$name] = array();
37 function get_name() {
38 return $this->name;
41 function add_property($name, $value = NULL, $parameters = NULL) {
43 // Uppercase first of all
44 $name = strtoupper($name);
46 // Are we trying to add a valid property?
47 $xname = false;
48 if(!isset($this->valid_properties[$name])) {
49 // If not, is it an x-name as per RFC 2445?
50 if(!rfc2445_is_xname($name)) {
51 return false;
53 // Since this is an xname, all components are supposed to allow this property
54 $xname = true;
57 // Create a property object of the correct class
58 if($xname) {
59 $property = new iCalendar_property_x;
60 $property->set_name($name);
62 else {
63 $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $name));
64 $property = new $classname;
67 // If $value is NULL, then this property must define a default value.
68 if($value === NULL) {
69 $value = $property->default_value();
70 if($value === NULL) {
71 return false;
75 // Set this property's parent component to ourselves, because some
76 // properties behave differently according to what component they apply to.
77 $property->set_parent_component($this->name);
79 // Set parameters before value; this helps with some properties which
80 // accept a VALUE parameter, and thus change their default value type.
82 // The parameters must be valid according to property specifications
83 if(!empty($parameters)) {
84 foreach($parameters as $paramname => $paramvalue) {
85 if(!$property->set_parameter($paramname, $paramvalue)) {
86 return false;
90 // Some parameters interact among themselves (e.g. ENCODING and VALUE)
91 // so make sure that after the dust settles, these invariants hold true
92 if(!$property->invariant_holds()) {
93 return false;
97 // $value MUST be valid according to the property data type
98 if(!$property->set_value($value)) {
99 return false;
102 // Check if the property already exists, and is limited to one occurrance,
103 // DON'T overwrite the value - this can be done explicity with set_value() instead.
104 if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE && isset($this->properties[$name])) {
105 return false;
107 else {
108 // Otherwise add it to the instance array for this property
109 $this->properties[$name][] = $property;
112 // Finally: after all these, does the component invariant hold?
113 if(!$this->invariant_holds()) {
114 // If not, completely undo the property addition
115 array_pop($this->properties[$name]);
116 if(empty($this->properties[$name])) {
117 unset($this->properties[$name]);
119 return false;
122 return true;
126 function add_component($component) {
128 // With the detailed interface, you can add only components with this function
129 if(!is_object($component) || !is_subclass_of($component, 'iCalendar_component')) {
130 return false;
133 $name = $component->get_name();
135 // Only valid components as specified by this component are allowed
136 if(!in_array($name, $this->valid_components)) {
137 return false;
140 // Add it
141 $this->components[$name][] = $component;
143 return true;
146 function get_property_list($name) {
149 function invariant_holds() {
150 return true;
153 function is_valid() {
154 // If we have any child components, check that they are all valid
155 if(!empty($this->components)) {
156 foreach($this->components as $component => $instances) {
157 foreach($instances as $number => $instance) {
158 if(!$instance->is_valid()) {
159 return false;
165 // Finally, check the valid property list for any mandatory properties
166 // that have not been set and do not have a default value
167 foreach($this->valid_properties as $property => $propdata) {
168 if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
169 $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $property));
170 $object = new $classname;
171 if($object->default_value() === NULL) {
172 return false;
174 unset($object);
178 return true;
181 function serialize() {
182 // Check for validity of the object
183 if(!$this->is_valid()) {
184 return false;
187 // Maybe the object is valid, but there are some required properties that
188 // have not been given explicit values. In that case, set them to defaults.
189 foreach($this->valid_properties as $property => $propdata) {
190 if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
191 $this->add_property($property);
195 // Start tag
196 $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF;
198 // List of properties
199 if(!empty($this->properties)) {
200 foreach($this->properties as $name => $properties) {
201 foreach($properties as $property) {
202 $string .= $property->serialize();
207 // List of components
208 if(!empty($this->components)) {
209 foreach($this->components as $name => $components) {
210 foreach($components as $component) {
211 $string .= $component->serialize();
216 // End tag
217 $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF;
219 return $string;
223 * unserialize()
225 * I needed a way to convert an iCalendar component back to a Bennu object so I could
226 * easily access and modify it after it had been stored; if this functionality is already
227 * present somewhere in the library, I apologize for adding it here unnecessarily; however,
228 * I couldn't find it so I added it myself.
229 * @param string $string the iCalendar object to load in to this iCalendar_component
230 * @return bool true if the file parsed with no errors. False if there were errors.
233 function unserialize($string) {
234 $string = rfc2445_unfold($string); // Unfold any long lines
235 $lines = preg_split("<".RFC2445_CRLF."|\n|\r>", $string, 0, PREG_SPLIT_NO_EMPTY); // Create an array of lines.
237 $components = array(); // Initialise a stack of components
238 $this->clear_errors();
239 foreach ($lines as $key => $line) {
240 // ignore empty lines
241 if (trim($line) == '') {
242 continue;
245 // Divide the line up into label, parameters and data fields.
246 if (!preg_match('#^(?P<label>[-[:alnum:]]+)(?P<params>(?:;(?:(?:[-[:alnum:]]+)=(?:[^[:cntrl:]";:,]+|"[^[:cntrl:]"]+")))*):(?P<data>.*)$#', $line, $match)) {
247 $this->parser_error('Invalid line: '.$key.', ignoring');
248 continue;
251 // parse parameters
252 $params = array();
253 if (preg_match_all('#;(?P<param>[-[:alnum:]]+)=(?P<value>[^[:cntrl:]";:,]+|"[^[:cntrl:]"]+")#', $match['params'], $pmatch)) {
254 $params = array_combine($pmatch['param'], $pmatch['value']);
256 $label = $match['label'];
257 $data = $match['data'];
258 unset($match, $pmatch);
260 if ($label == 'BEGIN') {
261 // This is the start of a component.
262 $current_component = array_pop($components); // Get the current component off the stack so we can check its valid components
263 if ($current_component == null) { // If there's nothing on the stack
264 $current_component = $this; // use the iCalendar
266 if (in_array($data, $current_component->valid_components)) { // Check that the new component is a valid subcomponent of the current one
267 if($current_component != $this) {
268 array_push($components, $current_component); // We're done with the current component, put it back on the stack.
270 if(strpos($data, 'V') === 0) {
271 $data = substr($data, 1);
273 $cname = 'iCalendar_' . strtolower($data);
274 $new_component = new $cname;
275 array_push($components, $new_component); // Push a new component onto the stack
276 } else {
277 if($current_component != $this) {
278 array_push($components, $current_component);
279 $this->parser_error('Invalid component type on line '.$key);
282 unset($current_component, $new_component);
283 } else if ($label == 'END') {
284 // It's the END of a component.
285 $component = array_pop($components); // Pop the top component off the stack - we're now done with it
286 $parent_component = array_pop($components); // Pop the component's conatining component off the stack so we can add this component to it.
287 if($parent_component == null) {
288 $parent_component = $this; // If there's no components on the stack, use the iCalendar object
290 if ($component !== null) {
291 if ($parent_component->add_component($component) === false) {
292 $this->parser_error("Failed to add component on line $key");
295 if ($parent_component != $this) { // If we're not using the iCalendar
296 array_push($components, $parent_component); // Put the component back on the stack
298 unset($parent_component, $component);
299 } else {
301 $component = array_pop($components); // Get the component off the stack so we can add properties to it
302 if ($component == null) { // If there's nothing on the stack
303 $component = $this; // use the iCalendar
306 $cleanedparams = [];
307 // Some parameter values are wrapped by DQUOTE character.
308 // We need to go through and get the actual value inside the quoted string.
309 foreach ($params as $param => $value) {
310 if (preg_match('#"(?P<actualvalue>[^"]*?)"#', $value, $matches)) {
311 $cleanedparams[$param] = $matches['actualvalue'];
312 } else {
313 $cleanedparams[$param] = $value;
316 $params = $cleanedparams;
318 if ($component->add_property($label, $data, $params) === false) {
319 $this->parser_error("Failed to add property '$label' on line $key");
322 if($component != $this) { // If we're not using the iCalendar
323 array_push($components, $component); // Put the component back on the stack
325 unset($component);
332 function clear_errors() {
333 $this->parser_errors = array();
336 function parser_error($error) {
337 $this->parser_errors[] = $error;
342 class iCalendar extends iCalendar_component {
343 var $name = 'VCALENDAR';
345 function __construct() {
346 $this->valid_properties = array(
347 'CALSCALE' => RFC2445_OPTIONAL | RFC2445_ONCE,
348 'METHOD' => RFC2445_OPTIONAL | RFC2445_ONCE,
349 'PRODID' => RFC2445_REQUIRED | RFC2445_ONCE,
350 'VERSION' => RFC2445_REQUIRED | RFC2445_ONCE,
351 RFC2445_XNAME => RFC2445_OPTIONAL
354 $this->valid_components = array(
355 'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
357 parent::__construct();
362 class iCalendar_event extends iCalendar_component {
364 var $name = 'VEVENT';
365 var $properties;
367 function __construct() {
369 $this->valid_components = array('VALARM');
371 $this->valid_properties = array(
372 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
373 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
374 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
375 // Standard ambiguous here: in 4.6.1 it says that DTSTAMP in optional,
376 // while in 4.8.7.2 it says it's REQUIRED. Go with REQUIRED.
377 'DTSTAMP' => RFC2445_REQUIRED | RFC2445_ONCE,
378 // Standard ambiguous here: in 4.6.1 it says that DTSTART in optional,
379 // while in 4.8.2.4 it says it's REQUIRED. Go with REQUIRED.
380 'DTSTART' => RFC2445_REQUIRED | RFC2445_ONCE,
381 'GEO' => RFC2445_OPTIONAL | RFC2445_ONCE,
382 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
383 'LOCATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
384 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
385 'PRIORITY' => RFC2445_OPTIONAL | RFC2445_ONCE,
386 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
387 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
388 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
389 'TRANSP' => RFC2445_OPTIONAL | RFC2445_ONCE,
390 // Standard ambiguous here: in 4.6.1 it says that UID in optional,
391 // while in 4.8.4.7 it says it's REQUIRED. Go with REQUIRED.
392 'UID' => RFC2445_REQUIRED | RFC2445_ONCE,
393 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
394 'RECURRENCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
395 'DTEND' => RFC2445_OPTIONAL | RFC2445_ONCE,
396 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
397 'ATTACH' => RFC2445_OPTIONAL,
398 'ATTENDEE' => RFC2445_OPTIONAL,
399 'CATEGORIES' => RFC2445_OPTIONAL,
400 'COMMENT' => RFC2445_OPTIONAL,
401 'CONTACT' => RFC2445_OPTIONAL,
402 'EXDATE' => RFC2445_OPTIONAL,
403 'EXRULE' => RFC2445_OPTIONAL,
404 'REQUEST-STATUS' => RFC2445_OPTIONAL,
405 'RELATED-TO' => RFC2445_OPTIONAL,
406 'RESOURCES' => RFC2445_OPTIONAL,
407 'RDATE' => RFC2445_OPTIONAL,
408 'RRULE' => RFC2445_OPTIONAL,
409 RFC2445_XNAME => RFC2445_OPTIONAL
412 parent::__construct();
415 function invariant_holds() {
416 // DTEND and DURATION must not appear together
417 if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
418 return false;
422 if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
423 // DTEND must be later than DTSTART
424 // The standard is not clear on how to hande different value types though
425 // TODO: handle this correctly even if the value types are different
426 if($this->properties['DTEND'][0]->value < $this->properties['DTSTART'][0]->value) {
427 return false;
430 // DTEND and DTSTART must have the same value type
431 if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
432 return false;
436 return true;
441 class iCalendar_todo extends iCalendar_component {
442 var $name = 'VTODO';
443 var $properties;
445 function __construct() {
447 $this->valid_components = array('VALARM');
449 $this->valid_properties = array(
450 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
451 'COMPLETED' => RFC2445_OPTIONAL | RFC2445_ONCE,
452 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
453 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
454 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
455 'DTSTAP' => RFC2445_OPTIONAL | RFC2445_ONCE,
456 'GEO' => RFC2445_OPTIONAL | RFC2445_ONCE,
457 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
458 'LOCATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
459 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
460 'PERCENT' => RFC2445_OPTIONAL | RFC2445_ONCE,
461 'PRIORITY' => RFC2445_OPTIONAL | RFC2445_ONCE,
462 'RECURID' => RFC2445_OPTIONAL | RFC2445_ONCE,
463 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
464 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
465 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
466 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
467 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
468 'DUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
469 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
470 'ATTACH' => RFC2445_OPTIONAL,
471 'ATTENDEE' => RFC2445_OPTIONAL,
472 'CATEGORIES' => RFC2445_OPTIONAL,
473 'COMMENT' => RFC2445_OPTIONAL,
474 'CONTACT' => RFC2445_OPTIONAL,
475 'EXDATE' => RFC2445_OPTIONAL,
476 'EXRULE' => RFC2445_OPTIONAL,
477 'RSTATUS' => RFC2445_OPTIONAL,
478 'RELATED' => RFC2445_OPTIONAL,
479 'RESOURCES' => RFC2445_OPTIONAL,
480 'RDATE' => RFC2445_OPTIONAL,
481 'RRULE' => RFC2445_OPTIONAL,
482 RFC2445_XNAME => RFC2445_OPTIONAL
485 parent::__construct();
488 function invariant_holds() {
489 // DTEND and DURATION must not appear together
490 if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
491 return false;
495 if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
496 // DTEND must be later than DTSTART
497 // The standard is not clear on how to hande different value types though
498 // TODO: handle this correctly even if the value types are different
499 if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
500 return false;
503 // DTEND and DTSTART must have the same value type
504 if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
505 return false;
510 if(isset($this->properties['DUE']) && isset($this->properties['DTSTART'])) {
511 if($this->properties['DUE'][0]->value <= $this->properties['DTSTART'][0]->value) {
512 return false;
516 return true;
521 class iCalendar_journal extends iCalendar_component {
522 var $name = 'VJOURNAL';
523 var $properties;
525 function __construct() {
527 $this->valid_properties = array(
528 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
529 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
530 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
531 'DTSTART' => RFC2445_OPTIONAL | RFC2445_ONCE,
532 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
533 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
534 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
535 'RECURRANCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
536 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
537 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
538 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
539 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
540 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
541 'ATTACH' => RFC2445_OPTIONAL,
542 'ATTENDEE' => RFC2445_OPTIONAL,
543 'CATEGORIES' => RFC2445_OPTIONAL,
544 'COMMENT' => RFC2445_OPTIONAL,
545 'CONTACT' => RFC2445_OPTIONAL,
546 'EXDATE' => RFC2445_OPTIONAL,
547 'EXRULE' => RFC2445_OPTIONAL,
548 'RELATED-TO' => RFC2445_OPTIONAL,
549 'RDATE' => RFC2445_OPTIONAL,
550 'RRULE' => RFC2445_OPTIONAL,
551 RFC2445_XNAME => RFC2445_OPTIONAL
554 parent::__construct();
559 class iCalendar_freebusy extends iCalendar_component {
560 var $name = 'VFREEBUSY';
561 var $properties;
563 function __construct() {
564 $this->valid_components = array();
565 $this->valid_properties = array(
566 'CONTACT' => RFC2445_OPTIONAL | RFC2445_ONCE,
567 'DTSTART' => RFC2445_OPTIONAL | RFC2445_ONCE,
568 'DTEND' => RFC2445_OPTIONAL | RFC2445_ONCE,
569 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
570 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
571 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
572 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
573 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
574 // TODO: the next two are components of their own!
575 'ATTENDEE' => RFC2445_OPTIONAL,
576 'COMMENT' => RFC2445_OPTIONAL,
577 'FREEBUSY' => RFC2445_OPTIONAL,
578 'RSTATUS' => RFC2445_OPTIONAL,
579 RFC2445_XNAME => RFC2445_OPTIONAL
582 parent::__construct();
585 function invariant_holds() {
586 // DTEND and DURATION must not appear together
587 if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
588 return false;
592 if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
593 // DTEND must be later than DTSTART
594 // The standard is not clear on how to hande different value types though
595 // TODO: handle this correctly even if the value types are different
596 if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
597 return false;
600 // DTEND and DTSTART must have the same value type
601 if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
602 return false;
606 return true;
610 class iCalendar_alarm extends iCalendar_component {
611 var $name = 'VALARM';
612 var $properties;
614 function __construct() {
615 $this->valid_components = array();
616 $this->valid_properties = array(
617 'ACTION' => RFC2445_REQUIRED | RFC2445_ONCE,
618 'TRIGGER' => RFC2445_REQUIRED | RFC2445_ONCE,
619 // If one of these 2 occurs, so must the other.
620 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
621 'REPEAT' => RFC2445_OPTIONAL | RFC2445_ONCE,
622 // The following is required if action == "PROCEDURE" | "AUDIO"
623 'ATTACH' => RFC2445_OPTIONAL,
624 // The following is required if trigger == "EMAIL" | "DISPLAY"
625 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
626 // The following are required if action == "EMAIL"
627 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
628 'ATTENDEE' => RFC2445_OPTIONAL,
629 RFC2445_XNAME => RFC2445_OPTIONAL
632 parent::__construct();
635 function invariant_holds() {
636 // DTEND and DURATION must not appear together
637 if(isset($this->properties['ACTION'])) {
638 switch ($this->properties['ACTION'][0]->value) {
639 case 'AUDIO':
640 if (!isset($this->properties['ATTACH'])) {
641 return false;
643 break;
644 case 'DISPLAY':
645 if (!isset($this->properties['DESCRIPTION'])) {
646 return false;
648 break;
649 case 'EMAIL':
650 if (!isset($this->properties['DESCRIPTION']) || !isset($this->properties['SUMMARY']) || !isset($this->properties['ATTACH'])) {
651 return false;
653 break;
654 case 'PROCEDURE':
655 if (!isset($this->properties['ATTACH']) || count($this->properties['ATTACH']) > 1) {
656 return false;
658 break;
661 return true;
667 class iCalendar_timezone extends iCalendar_component {
668 var $name = 'VTIMEZONE';
669 var $properties;
671 function __construct() {
673 $this->valid_components = array('STANDARD', 'DAYLIGHT');
675 $this->valid_properties = array(
676 'TZID' => RFC2445_REQUIRED | RFC2445_ONCE,
677 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
678 'TZURL' => RFC2445_OPTIONAL | RFC2445_ONCE,
679 RFC2445_XNAME => RFC2445_OPTIONAL
682 parent::__construct();
687 class iCalendar_standard extends iCalendar_component {
688 var $name = 'STANDARD';
689 var $properties;
691 function __construct() {
692 $this->valid_components = array();
693 $this->valid_properties = array(
694 'DTSTART' => RFC2445_REQUIRED | RFC2445_ONCE,
695 'TZOFFSETTO' => RFC2445_REQUIRED | RFC2445_ONCE,
696 'TZOFFSETFROM' => RFC2445_REQUIRED | RFC2445_ONCE,
697 'COMMENT' => RFC2445_OPTIONAL,
698 'RDATE' => RFC2445_OPTIONAL,
699 'RRULE' => RFC2445_OPTIONAL,
700 'TZNAME' => RFC2445_OPTIONAL,
701 'TZURL' => RFC2445_OPTIONAL,
702 RFC2445_XNAME => RFC2445_OPTIONAL,
704 parent::__construct();
708 class iCalendar_daylight extends iCalendar_standard {
709 var $name = 'DAYLIGHT';
712 // REMINDER: DTEND must be later than DTSTART for all components which support both
713 // REMINDER: DUE must be later than DTSTART for all components which support both