Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   1  <?php
   2  
   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   * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  13   */
  14  
  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
  24       */
  25      var $parser_errors = NULL;
  26  
  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();
  33              }
  34          }
  35      }
  36  
  37      function get_name() {
  38          return $this->name;
  39      }
  40  
  41      function add_property($name, $value = NULL, $parameters = NULL) {
  42  
  43          // Uppercase first of all
  44          $name = strtoupper($name);
  45  
  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;
  52              }
  53              // Since this is an xname, all components are supposed to allow this property
  54              $xname = true;
  55          }
  56  
  57          // Create a property object of the correct class
  58          if($xname) {
  59              $property = new iCalendar_property_x;
  60              $property->set_name($name);
  61          }
  62          else {
  63              $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $name));
  64              $property = new $classname;
  65          }
  66  
  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;
  72              }
  73          }
  74  
  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);
  78  
  79          // Set parameters before value; this helps with some properties which
  80          // accept a VALUE parameter, and thus change their default value type.
  81  
  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;
  87                  }
  88              }
  89  
  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;
  94              }
  95          }
  96  
  97          // $value MUST be valid according to the property data type
  98          if(!$property->set_value($value)) {
  99              return false;
 100          }
 101  
 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;
 106          } 
 107  	 	 else {
 108               // Otherwise add it to the instance array for this property
 109              $this->properties[$name][] = $property;
 110          }
 111  
 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]);
 118              }
 119              return false;
 120          }
 121  
 122          return true;        
 123          
 124      }
 125  
 126      function add_component($component) {
 127  
 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;
 131          }
 132  
 133          $name = $component->get_name();
 134  
 135          // Only valid components as specified by this component are allowed
 136          if(!in_array($name, $this->valid_components)) {
 137              return false;
 138          }
 139  
 140          // Add it
 141          $this->components[$name][] = $component;
 142  
 143          return true;
 144      }
 145  
 146      function get_property_list($name) {
 147      }
 148  
 149      function invariant_holds() {
 150          return true;
 151      }
 152  
 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;
 160                      }
 161                  }
 162              }
 163          }
 164  
 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;
 173                  }
 174                  unset($object);
 175              }
 176          }
 177  
 178          return true;
 179      }
 180      
 181      function serialize() {
 182          // Check for validity of the object
 183          if(!$this->is_valid()) {
 184              return false;
 185          }
 186  
 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);
 192              }
 193          }
 194  
 195          // Start tag
 196          $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF;
 197  
 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();
 203                  }
 204              }
 205          }
 206  
 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();
 212                  }
 213              }
 214          }
 215  
 216          // End tag
 217          $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF;
 218  
 219          return $string;
 220      }
 221      
 222      /**
 223      * unserialize()
 224      *
 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.
 231      */
 232      
 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.
 236          
 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;
 243              }
 244  
 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;
 249              }
 250  
 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']);
 255              } 
 256              $label = $match['label'];
 257              $data  = $match['data'];
 258              unset($match, $pmatch);
 259  
 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
 265                  }
 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.
 269                      }
 270                      if(strpos($data, 'V') === 0) {
 271                          $data = substr($data, 1);
 272                      }
 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);
 280                      }                        
 281                  }
 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
 289                  }
 290                  if ($component !== null) {
 291                      if ($parent_component->add_component($component) === false) {
 292                          $this->parser_error("Failed to add component on line $key");
 293                      }
 294                  }
 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
 297                  }
 298                  unset($parent_component, $component);
 299              } else {
 300                  
 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
 304                  }
 305  
 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;
 314                      }
 315                  }
 316                  $params = $cleanedparams;
 317  
 318                  if ($component->add_property($label, $data, $params) === false) {
 319                      $this->parser_error("Failed to add property '$label' on line $key");
 320                  }
 321  
 322                  if($component != $this) { // If we're not using the iCalendar
 323                      array_push($components, $component); // Put the component back on the stack
 324                  }
 325                  unset($component);
 326              }
 327  
 328          }
 329          
 330      }
 331  
 332      function clear_errors() {
 333          $this->parser_errors = array();
 334      }
 335  
 336      function parser_error($error) {
 337          $this->parser_errors[] = $error;
 338      }
 339  
 340  }
 341  
 342  class iCalendar extends iCalendar_component {
 343      var $name = 'VCALENDAR';
 344  
 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 
 352          );
 353  
 354          $this->valid_components = array(
 355              'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
 356          );
 357          parent::__construct();
 358      }
 359  
 360  }
 361  
 362  class iCalendar_event extends iCalendar_component {
 363  
 364      var $name       = 'VEVENT';
 365      var $properties;
 366      
 367      function __construct() {
 368          
 369          $this->valid_components = array('VALARM');
 370  
 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
 410          );
 411  
 412          parent::__construct();
 413      }
 414  
 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;
 419          }
 420  
 421          
 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;
 428              }
 429  
 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;
 433              }
 434  
 435          }
 436          return true;
 437      }
 438  
 439  }
 440  
 441  class iCalendar_todo extends iCalendar_component {
 442      var $name       = 'VTODO';
 443      var $properties;
 444  
 445      function __construct() {
 446          
 447          $this->valid_components = array('VALARM');
 448  
 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
 483          );
 484  
 485          parent::__construct();
 486      }
 487      
 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;
 492          }
 493  
 494          
 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;
 501              }
 502  
 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;
 506              }
 507  
 508          }
 509          
 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;
 513              }   
 514          }
 515          
 516          return true;
 517      }
 518      
 519  }
 520  
 521  class iCalendar_journal extends iCalendar_component {
 522      var $name = 'VJOURNAL';
 523      var $properties;
 524      
 525      function __construct() {
 526      	 
 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            
 552          );
 553          
 554           parent::__construct();
 555          
 556      }
 557  }
 558  
 559  class iCalendar_freebusy extends iCalendar_component {
 560      var $name       = 'VFREEBUSY';
 561      var $properties;
 562  
 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
 580          );
 581          
 582          parent::__construct();
 583      }
 584      
 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;
 589          }
 590  
 591          
 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;
 598              }
 599  
 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;
 603              }
 604  
 605          }
 606          return true;
 607      }
 608  }
 609  
 610  class iCalendar_alarm extends iCalendar_component {
 611      var $name       = 'VALARM';
 612      var $properties;
 613  
 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
 630          );
 631       
 632          parent::__construct();
 633      }
 634          
 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;
 642                      }
 643                      break;
 644                  case 'DISPLAY':
 645                      if (!isset($this->properties['DESCRIPTION'])) {
 646                      	 return false;
 647                      }
 648                      break;
 649                  case 'EMAIL':
 650                      if (!isset($this->properties['DESCRIPTION']) || !isset($this->properties['SUMMARY']) || !isset($this->properties['ATTACH'])) {
 651                          return false;
 652                      }
 653                      break;
 654                  case 'PROCEDURE':
 655                      if (!isset($this->properties['ATTACH']) || count($this->properties['ATTACH']) > 1) {
 656                      	 return false;
 657                      }
 658                      break;
 659              }
 660          }
 661          return true;
 662      }
 663          
 664          
 665  }
 666  
 667  class iCalendar_timezone extends iCalendar_component {
 668      var $name       = 'VTIMEZONE';
 669      var $properties;
 670  
 671      function __construct() {
 672  
 673          $this->valid_components = array('STANDARD', 'DAYLIGHT');
 674  
 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
 680          );
 681          
 682          parent::__construct();
 683      }
 684  
 685  }
 686  
 687  class iCalendar_standard extends iCalendar_component {
 688      var $name       = 'STANDARD';
 689      var $properties;
 690      
 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,
 703          ); 
 704          parent::__construct();
 705      }
 706  }
 707  
 708  class iCalendar_daylight extends iCalendar_standard {
 709      var $name   =   'DAYLIGHT';
 710  }
 711  
 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
 714