Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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                  if ($component->add_property($label, $data, $params) === false) {
 307                      $this->parser_error("Failed to add property '$label' on line $key");
 308                  }
 309  
 310                  if($component != $this) { // If we're not using the iCalendar
 311                      array_push($components, $component); // Put the component back on the stack
 312                  }
 313                  unset($component);
 314              }
 315  
 316          }
 317          
 318      }
 319  
 320      function clear_errors() {
 321          $this->parser_errors = array();
 322      }
 323  
 324      function parser_error($error) {
 325          $this->parser_errors[] = $error;
 326      }
 327  
 328  }
 329  
 330  class iCalendar extends iCalendar_component {
 331      var $name = 'VCALENDAR';
 332  
 333      function __construct() {
 334          $this->valid_properties = array(
 335              'CALSCALE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 336              'METHOD'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 337              'PRODID'      => RFC2445_REQUIRED | RFC2445_ONCE,
 338              'VERSION'     => RFC2445_REQUIRED | RFC2445_ONCE,
 339              RFC2445_XNAME => RFC2445_OPTIONAL 
 340          );
 341  
 342          $this->valid_components = array(
 343              'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
 344          );
 345          parent::__construct();
 346      }
 347  
 348  }
 349  
 350  class iCalendar_event extends iCalendar_component {
 351  
 352      var $name       = 'VEVENT';
 353      var $properties;
 354      
 355      function __construct() {
 356          
 357          $this->valid_components = array('VALARM');
 358  
 359          $this->valid_properties = array(
 360              'CLASS'          => RFC2445_OPTIONAL | RFC2445_ONCE,
 361              'CREATED'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 362              'DESCRIPTION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 363              // Standard ambiguous here: in 4.6.1 it says that DTSTAMP in optional,
 364              // while in 4.8.7.2 it says it's REQUIRED. Go with REQUIRED.
 365              'DTSTAMP'        => RFC2445_REQUIRED | RFC2445_ONCE,
 366              // Standard ambiguous here: in 4.6.1 it says that DTSTART in optional,
 367              // while in 4.8.2.4 it says it's REQUIRED. Go with REQUIRED.
 368              'DTSTART'        => RFC2445_REQUIRED | RFC2445_ONCE,
 369              'GEO'            => RFC2445_OPTIONAL | RFC2445_ONCE,
 370              'LAST-MODIFIED'  => RFC2445_OPTIONAL | RFC2445_ONCE,
 371              'LOCATION'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 372              'ORGANIZER'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 373              'PRIORITY'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 374              'SEQUENCE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 375              'STATUS'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 376              'SUMMARY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 377              'TRANSP'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 378              // Standard ambiguous here: in 4.6.1 it says that UID in optional,
 379              // while in 4.8.4.7 it says it's REQUIRED. Go with REQUIRED.
 380              'UID'            => RFC2445_REQUIRED | RFC2445_ONCE,
 381              'URL'            => RFC2445_OPTIONAL | RFC2445_ONCE,
 382              'RECURRENCE-ID'  => RFC2445_OPTIONAL | RFC2445_ONCE,
 383              'DTEND'          => RFC2445_OPTIONAL | RFC2445_ONCE,
 384              'DURATION'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 385              'ATTACH'         => RFC2445_OPTIONAL,
 386              'ATTENDEE'       => RFC2445_OPTIONAL,
 387              'CATEGORIES'     => RFC2445_OPTIONAL,
 388              'COMMENT'        => RFC2445_OPTIONAL,
 389              'CONTACT'        => RFC2445_OPTIONAL,
 390              'EXDATE'         => RFC2445_OPTIONAL,
 391              'EXRULE'         => RFC2445_OPTIONAL,
 392              'REQUEST-STATUS' => RFC2445_OPTIONAL,
 393              'RELATED-TO'     => RFC2445_OPTIONAL,
 394              'RESOURCES'      => RFC2445_OPTIONAL,
 395              'RDATE'          => RFC2445_OPTIONAL,
 396              'RRULE'          => RFC2445_OPTIONAL,
 397              RFC2445_XNAME    => RFC2445_OPTIONAL
 398          );
 399  
 400          parent::__construct();
 401      }
 402  
 403      function invariant_holds() {
 404          // DTEND and DURATION must not appear together
 405          if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
 406              return false;
 407          }
 408  
 409          
 410          if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
 411              // DTEND must be later than DTSTART
 412              // The standard is not clear on how to hande different value types though
 413              // TODO: handle this correctly even if the value types are different
 414              if($this->properties['DTEND'][0]->value < $this->properties['DTSTART'][0]->value) {
 415                  return false;
 416              }
 417  
 418              // DTEND and DTSTART must have the same value type
 419              if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
 420                  return false;
 421              }
 422  
 423          }
 424          return true;
 425      }
 426  
 427  }
 428  
 429  class iCalendar_todo extends iCalendar_component {
 430      var $name       = 'VTODO';
 431      var $properties;
 432  
 433      function __construct() {
 434          
 435          $this->valid_components = array('VALARM');
 436  
 437          $this->valid_properties = array(
 438              'CLASS'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 439              'COMPLETED'   => RFC2445_OPTIONAL | RFC2445_ONCE,
 440              'CREATED'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 441              'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
 442              'DTSTAMP'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 443              'DTSTAP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 444              'GEO'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 445              'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
 446              'LOCATION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 447              'ORGANIZER'   => RFC2445_OPTIONAL | RFC2445_ONCE,
 448              'PERCENT'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 449              'PRIORITY'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 450              'RECURID'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 451              'SEQUENCE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 452              'STATUS'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 453              'SUMMARY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 454              'UID'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 455              'URL'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 456              'DUE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 457              'DURATION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 458              'ATTACH'      => RFC2445_OPTIONAL,
 459              'ATTENDEE'    => RFC2445_OPTIONAL,
 460              'CATEGORIES'  => RFC2445_OPTIONAL,
 461              'COMMENT'     => RFC2445_OPTIONAL,
 462              'CONTACT'     => RFC2445_OPTIONAL,
 463              'EXDATE'      => RFC2445_OPTIONAL,
 464              'EXRULE'      => RFC2445_OPTIONAL,
 465              'RSTATUS'     => RFC2445_OPTIONAL,
 466              'RELATED'     => RFC2445_OPTIONAL,
 467              'RESOURCES'   => RFC2445_OPTIONAL,
 468              'RDATE'       => RFC2445_OPTIONAL,
 469              'RRULE'       => RFC2445_OPTIONAL,
 470              RFC2445_XNAME => RFC2445_OPTIONAL
 471          );
 472  
 473          parent::__construct();
 474      }
 475      
 476      function invariant_holds() {
 477          // DTEND and DURATION must not appear together
 478          if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
 479              return false;
 480          }
 481  
 482          
 483          if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
 484              // DTEND must be later than DTSTART
 485              // The standard is not clear on how to hande different value types though
 486              // TODO: handle this correctly even if the value types are different
 487              if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
 488                  return false;
 489              }
 490  
 491              // DTEND and DTSTART must have the same value type
 492              if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
 493                  return false;
 494              }
 495  
 496          }
 497          
 498          if(isset($this->properties['DUE']) && isset($this->properties['DTSTART'])) {
 499              if($this->properties['DUE'][0]->value <= $this->properties['DTSTART'][0]->value) {
 500                  return false;
 501              }   
 502          }
 503          
 504          return true;
 505      }
 506      
 507  }
 508  
 509  class iCalendar_journal extends iCalendar_component {
 510      var $name = 'VJOURNAL';
 511      var $properties;
 512      
 513      function __construct() {
 514      	 
 515          $this->valid_properties = array(
 516              'CLASS'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 517              'CREATED'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 518              'DESCRIPTION'   => RFC2445_OPTIONAL | RFC2445_ONCE,
 519              'DTSTART'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 520              'DTSTAMP'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 521              'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
 522              'ORGANIZER'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 523              'RECURRANCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
 524              'SEQUENCE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 525              'STATUS'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 526              'SUMMARY'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 527              'UID'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 528              'URL'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 529              'ATTACH'        => RFC2445_OPTIONAL,
 530              'ATTENDEE'      => RFC2445_OPTIONAL,
 531              'CATEGORIES'    => RFC2445_OPTIONAL,
 532              'COMMENT'       => RFC2445_OPTIONAL,
 533              'CONTACT'       => RFC2445_OPTIONAL,
 534              'EXDATE'        => RFC2445_OPTIONAL,
 535              'EXRULE'        => RFC2445_OPTIONAL,
 536              'RELATED-TO'    => RFC2445_OPTIONAL,
 537              'RDATE'         => RFC2445_OPTIONAL,
 538              'RRULE'         => RFC2445_OPTIONAL,
 539              RFC2445_XNAME   => RFC2445_OPTIONAL            
 540          );
 541          
 542           parent::__construct();
 543          
 544      }
 545  }
 546  
 547  class iCalendar_freebusy extends iCalendar_component {
 548      var $name       = 'VFREEBUSY';
 549      var $properties;
 550  
 551      function __construct() {
 552          $this->valid_components = array();
 553          $this->valid_properties = array(
 554              'CONTACT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 555              'DTSTART'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 556              'DTEND'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 557              'DURATION'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 558              'DTSTAMP'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 559              'ORGANIZER'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 560              'UID'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 561              'URL'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 562              // TODO: the next two are components of their own!
 563              'ATTENDEE'      => RFC2445_OPTIONAL,
 564              'COMMENT'       => RFC2445_OPTIONAL,
 565              'FREEBUSY'      => RFC2445_OPTIONAL,
 566              'RSTATUS'       => RFC2445_OPTIONAL,
 567              RFC2445_XNAME   => RFC2445_OPTIONAL
 568          );
 569          
 570          parent::__construct();
 571      }
 572      
 573      function invariant_holds() {
 574          // DTEND and DURATION must not appear together
 575          if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
 576              return false;
 577          }
 578  
 579          
 580          if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
 581              // DTEND must be later than DTSTART
 582              // The standard is not clear on how to hande different value types though
 583              // TODO: handle this correctly even if the value types are different
 584              if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
 585                  return false;
 586              }
 587  
 588              // DTEND and DTSTART must have the same value type
 589              if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
 590                  return false;
 591              }
 592  
 593          }
 594          return true;
 595      }
 596  }
 597  
 598  class iCalendar_alarm extends iCalendar_component {
 599      var $name       = 'VALARM';
 600      var $properties;
 601  
 602      function __construct() {
 603          $this->valid_components = array();
 604          $this->valid_properties = array(
 605              'ACTION'    => RFC2445_REQUIRED | RFC2445_ONCE,
 606              'TRIGGER'   => RFC2445_REQUIRED | RFC2445_ONCE,
 607              // If one of these 2 occurs, so must the other.
 608              'DURATION'  => RFC2445_OPTIONAL | RFC2445_ONCE,
 609              'REPEAT'    => RFC2445_OPTIONAL | RFC2445_ONCE, 
 610              // The following is required if action == "PROCEDURE" | "AUDIO"           
 611              'ATTACH'    => RFC2445_OPTIONAL,
 612              // The following is required if trigger == "EMAIL" | "DISPLAY" 
 613              'DESCRIPTION'  => RFC2445_OPTIONAL | RFC2445_ONCE,
 614              // The following are required if action == "EMAIL"
 615              'SUMMARY'   => RFC2445_OPTIONAL | RFC2445_ONCE,
 616              'ATTENDEE'  => RFC2445_OPTIONAL,
 617              RFC2445_XNAME   => RFC2445_OPTIONAL
 618          );
 619       
 620          parent::__construct();
 621      }
 622          
 623      function invariant_holds() {
 624          // DTEND and DURATION must not appear together
 625          if(isset($this->properties['ACTION'])) {
 626              switch ($this->properties['ACTION'][0]->value) {
 627              	 case 'AUDIO':
 628                      if (!isset($this->properties['ATTACH'])) {
 629                      	 return false;
 630                      }
 631                      break;
 632                  case 'DISPLAY':
 633                      if (!isset($this->properties['DESCRIPTION'])) {
 634                      	 return false;
 635                      }
 636                      break;
 637                  case 'EMAIL':
 638                      if (!isset($this->properties['DESCRIPTION']) || !isset($this->properties['SUMMARY']) || !isset($this->properties['ATTACH'])) {
 639                          return false;
 640                      }
 641                      break;
 642                  case 'PROCEDURE':
 643                      if (!isset($this->properties['ATTACH']) || count($this->properties['ATTACH']) > 1) {
 644                      	 return false;
 645                      }
 646                      break;
 647              }
 648          }
 649          return true;
 650      }
 651          
 652          
 653  }
 654  
 655  class iCalendar_timezone extends iCalendar_component {
 656      var $name       = 'VTIMEZONE';
 657      var $properties;
 658  
 659      function __construct() {
 660  
 661          $this->valid_components = array('STANDARD', 'DAYLIGHT');
 662  
 663          $this->valid_properties = array(
 664              'TZID'        => RFC2445_REQUIRED | RFC2445_ONCE,
 665              'LAST-MODIFIED'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 666              'TZURL'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 667              RFC2445_XNAME => RFC2445_OPTIONAL
 668          );
 669          
 670          parent::__construct();
 671      }
 672  
 673  }
 674  
 675  class iCalendar_standard extends iCalendar_component {
 676      var $name       = 'STANDARD';
 677      var $properties;
 678      
 679      function __construct() {
 680          $this->valid_components = array();
 681          $this->valid_properties = array(
 682              'DTSTART'   =>  RFC2445_REQUIRED | RFC2445_ONCE,
 683              'TZOFFSETTO'    =>  RFC2445_REQUIRED | RFC2445_ONCE,
 684              'TZOFFSETFROM'  =>  RFC2445_REQUIRED | RFC2445_ONCE,
 685              'COMMENT'   =>  RFC2445_OPTIONAL,
 686              'RDATE'   =>  RFC2445_OPTIONAL,
 687              'RRULE'   =>  RFC2445_OPTIONAL,
 688              'TZNAME'   =>  RFC2445_OPTIONAL,
 689              'TZURL'   =>  RFC2445_OPTIONAL,
 690              RFC2445_XNAME   =>  RFC2445_OPTIONAL,
 691          ); 
 692          parent::__construct();
 693      }
 694  }
 695  
 696  class iCalendar_daylight extends iCalendar_standard {
 697      var $name   =   'DAYLIGHT';
 698  }
 699  
 700  // REMINDER: DTEND must be later than DTSTART for all components which support both
 701  // REMINDER: DUE must be later than DTSTART for all components which support both
 702