Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 402] [Versions 39 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   * @version $Id$
  13   * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  14   */
  15  
  16  class iCalendar_property {
  17      // Properties can have parameters, but cannot have other properties or components
  18  
  19      var $parent_component = NULL;
  20      var $value            = NULL;
  21      var $parameters       = NULL;
  22      var $valid_parameters = NULL;
  23  
  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;
  27  
  28      function __construct() {
  29          $this->parameters = array();
  30      }
  31  
  32      // If some property needs extra care with its parameters, override this
  33      // IMPORTANT: the parameter name MUST BE CAPITALIZED!
  34      function is_valid_parameter($parameter, $value) {
  35  
  36          if(is_array($value)) {
  37              if(!iCalendar_parameter::multiple_values_allowed($parameter)) {
  38                  return false;
  39              }
  40              foreach($value as $item) {
  41                  if(!iCalendar_parameter::is_valid_value($this, $parameter, $item)) {
  42                      return false;
  43                  }
  44              }
  45              return true;
  46          }
  47  
  48          return iCalendar_parameter::is_valid_value($this, $parameter, $value);
  49      }
  50  
  51      function invariant_holds() {
  52          return true;
  53      }
  54  
  55      // If some property is very picky about its values, it should do the work itself
  56      // Only data type validation is done here
  57      function is_valid_value($value) {
  58          if(is_array($value)) {
  59              if(!$this->val_multi) {
  60                  return false;
  61              }
  62              else {
  63                  foreach($value as $oneval) {
  64                      if(!rfc2445_is_valid_value($oneval, $this->val_type)) {
  65                          return false;
  66                      }
  67                  }
  68              }
  69              return true;
  70          }
  71          return rfc2445_is_valid_value($value, $this->val_type);
  72      }
  73  
  74      function default_value() {
  75          return $this->val_default;
  76      }
  77  
  78      function set_parent_component($componentname) {
  79          if(class_exists('iCalendar_'.strtolower(substr($componentname, 1)))) {
  80              $this->parent_component = strtoupper($componentname);
  81              return true;
  82          }
  83  
  84          return false;
  85      }
  86  
  87      function set_value($value) {
  88          if($this->is_valid_value($value)) {
  89              // This transparently formats any value type according to the iCalendar specs
  90              if(is_array($value)) {
  91                  foreach($value as $key => $item) {
  92                      $value[$key] = rfc2445_do_value_formatting($item, $this->val_type);
  93                  }
  94                  $this->value = implode(',', $value);
  95              }
  96              else {
  97                  $this->value = rfc2445_do_value_formatting($value, $this->val_type);
  98              }
  99              
 100              return true;
 101          }
 102          return false;
 103      }
 104  
 105      function get_value() {
 106          // First of all, assume that we have multiple values
 107          $valarray = explode('\\,', $this->value);
 108  
 109          // Undo transparent formatting
 110          $valtype = $this->val_type;
 111          $replace_function = function($a) use ($valtype) {
 112              return rfc2445_undo_value_formatting($a, $valtype);
 113          };
 114          $valarray = array_map($replace_function, $valarray);
 115  
 116          // Now, if this property cannot have multiple values, don't return as an array
 117          if(!$this->val_multi) {
 118              return $valarray[0];
 119          }
 120  
 121          // Otherwise return an array even if it has one element, for uniformity
 122          return $valarray;
 123  
 124      }
 125  
 126      function set_parameter($name, $value) {
 127  
 128          // Uppercase
 129          $name = strtoupper($name);
 130  
 131          // Are we trying to add a valid parameter?
 132          $xname = false;
 133          if(!isset($this->valid_parameters[$name])) {
 134              // If not, is it an x-name as per RFC 2445?
 135              if(!rfc2445_is_xname($name)) {
 136                  return false;
 137              }
 138              // No more checks -- all components are supposed to allow x-name parameters
 139              $xname = true;
 140          }
 141  
 142          if(!$this->is_valid_parameter($name, $value)) {
 143              return false;
 144          }
 145  
 146          if(is_array($value)) {
 147              foreach($value as $key => $element) {
 148                  $value[$key] = iCalendar_parameter::do_value_formatting($name, $element);
 149              }
 150          }
 151          else {
 152              $value = iCalendar_parameter::do_value_formatting($name, $value);
 153          }
 154  
 155          $this->parameters[$name] = $value;
 156  
 157          // Special case: if we just changed the VALUE parameter, reflect this
 158          // in the object's status so that it only accepts correct type values
 159          if($name == 'VALUE') {
 160              // TODO: what if this invalidates an already-set value?
 161              $this->val_type = constant('RFC2445_TYPE_'.str_replace('-', '_', $value));
 162          }
 163  
 164          return true;
 165  
 166      }
 167  
 168      function get_parameter($name) {
 169  
 170          // Uppercase
 171          $name = strtoupper($name);
 172  
 173          if(isset($this->parameters[$name])) {
 174              // If there are any double quotes in the value, invisibly strip them
 175              if(is_array($this->parameters[$name])) {
 176                  foreach($this->parameters[$name] as $key => $value) {
 177                      if(substr($value, 0, 1) == '"') {
 178                         $this->parameters[$name][$key] = substr($value, 1, strlen($value) - 2);
 179                      }
 180                  }
 181                  return $this->parameters[$name];
 182              }
 183  
 184              else {
 185                  if(substr($this->parameters[$name], 0, 1) == '"') {
 186                      return substr($this->parameters[$name], 1, strlen($this->parameters[$name]) - 2);
 187                  }
 188              }
 189          }
 190  
 191          return NULL;
 192      }
 193  
 194      function serialize() {
 195          $string = $this->name;
 196  
 197          if(!empty($this->parameters)) {
 198              foreach($this->parameters as $name => $value) {
 199                  $string .= ';'.$name.'=';
 200                  if(is_array($value)) {
 201                      $string .= implode(',', $value);
 202                  }
 203                  else {
 204                      $string .= $value;
 205                  }
 206              }
 207          }
 208  
 209          $string .= ':'.$this->value;
 210  
 211          return rfc2445_fold($string) . RFC2445_CRLF;
 212      }
 213  }
 214  
 215  // 4.7 Calendar Properties
 216  // -----------------------
 217  
 218  class iCalendar_property_calscale extends iCalendar_property {
 219  
 220      var $name        = 'CALSCALE';
 221      var $val_type    = RFC2445_TYPE_TEXT;
 222  
 223      function __construct() {
 224          parent::__construct();
 225          $this->valid_parameters = array(
 226              RFC2445_XNAME => RFC2445_OPTIONAL
 227          );
 228      }
 229  
 230      function is_valid_value($value) {
 231          // This is case-sensitive
 232          return ($value === 'GREGORIAN');
 233      }
 234  }
 235  
 236  class iCalendar_property_method extends iCalendar_property {
 237  
 238      var $name        = 'METHOD';
 239      var $val_type    = RFC2445_TYPE_TEXT;
 240  
 241      function __construct() {
 242          parent::__construct();
 243          $this->valid_parameters = array(
 244              RFC2445_XNAME => RFC2445_OPTIONAL
 245          );
 246      }
 247  
 248      function is_valid_value($value) {
 249          // This is case-sensitive
 250          // Methods from RFC 2446
 251          $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER');
 252          return in_array($value, $methods);
 253      }
 254  }
 255  
 256  class iCalendar_property_prodid extends iCalendar_property {
 257  
 258      var $name        = 'PRODID';
 259      var $val_type    = RFC2445_TYPE_TEXT;
 260      var $val_default = NULL;
 261  
 262      function __construct() {
 263          parent::__construct();
 264          $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN';
 265  
 266          $this->valid_parameters = array(
 267              RFC2445_XNAME => RFC2445_OPTIONAL
 268          );
 269      }
 270  }
 271  
 272  class iCalendar_property_version extends iCalendar_property {
 273  
 274      var $name        = 'VERSION';
 275      var $val_type    = RFC2445_TYPE_TEXT;
 276      var $val_default = '2.0';
 277  
 278      function __construct() {
 279          parent::__construct();
 280          $this->valid_parameters = array(
 281              RFC2445_XNAME => RFC2445_OPTIONAL
 282          );
 283      }
 284  
 285      function is_valid_value($value) {
 286          return($value === '2.0' || $value === 2.0);
 287      }
 288  
 289  }
 290  
 291  // 4.8.1 Descriptive Component Properties
 292  // --------------------------------------
 293  
 294  class iCalendar_property_attach extends iCalendar_property {
 295  
 296      var $name        = 'ATTACH';
 297      var $val_type    = RFC2445_TYPE_URI;
 298  
 299      function __construct() {
 300          parent::__construct();
 301          $this->valid_parameters = array(
 302              'FMTTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 303              'ENCODING'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 304              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 305              RFC2445_XNAME => RFC2445_OPTIONAL
 306          );
 307      }
 308  
 309      function invariant_holds() {
 310          if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) {
 311              return false;
 312          }
 313          if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) {
 314              return false;
 315          }
 316  
 317          return true;
 318      }
 319  
 320      function is_valid_parameter($parameter, $value) {
 321  
 322          $parameter = strtoupper($parameter);
 323  
 324          if(!parent::is_valid_parameter($parameter, $value)) {
 325              return false;
 326          }
 327  
 328          if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') {
 329              return false;
 330          }
 331  
 332          if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') {
 333              return false;
 334          }
 335  
 336          return true;
 337      }
 338  }
 339  
 340  class iCalendar_property_categories extends iCalendar_property {
 341  
 342      var $name        = 'CATEGORIES';
 343      var $val_type    = RFC2445_TYPE_TEXT;
 344      var $val_multi   = true;
 345  
 346      function __construct() {
 347          parent::__construct();
 348          $this->valid_parameters = array(
 349              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 350              RFC2445_XNAME => RFC2445_OPTIONAL
 351          );
 352      }
 353  }
 354  
 355  class iCalendar_property_class extends iCalendar_property {
 356  
 357      var $name        = 'CLASS';
 358      var $val_type    = RFC2445_TYPE_TEXT;
 359      var $val_default = 'PUBLIC';
 360  
 361      function __construct() {
 362          parent::__construct();
 363          $this->valid_parameters = array(
 364              RFC2445_XNAME => RFC2445_OPTIONAL
 365          );
 366      }
 367  
 368      function is_valid_value($value) {
 369          // If this is not an xname, it is case-sensitive
 370          return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value)));
 371      }
 372  }
 373  
 374  class iCalendar_property_comment extends iCalendar_property {
 375  
 376      var $name        = 'COMMENT';
 377      var $val_type    = RFC2445_TYPE_TEXT;
 378  
 379      function __construct() {
 380          parent::__construct();
 381          $this->valid_parameters = array(
 382              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 383              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 384              RFC2445_XNAME => RFC2445_OPTIONAL
 385          );
 386      }
 387  }
 388  
 389  class iCalendar_property_description extends iCalendar_property {
 390  
 391      var $name        = 'DESCRIPTION';
 392      var $val_type    = RFC2445_TYPE_TEXT;
 393  
 394      function __construct() {
 395          parent::__construct();
 396          $this->valid_parameters = array(
 397              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 398              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 399              RFC2445_XNAME => RFC2445_OPTIONAL
 400          );
 401      }
 402  }
 403  
 404  class iCalendar_property_geo extends iCalendar_property {
 405  
 406      var $name        = 'GEO';
 407      var $val_type    = RFC2445_TYPE_TEXT;
 408  
 409      function __construct() {
 410          parent::__construct();
 411          $this->valid_parameters = array(
 412              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 413              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 414              RFC2445_XNAME => RFC2445_OPTIONAL
 415          );
 416      }
 417  
 418      function is_valid_value($value) {
 419          // This MUST be two floats separated by a semicolon
 420          if(!is_string($value)) {
 421              return false;
 422          }
 423  
 424          $floats = explode(';', $value);
 425          if(count($floats) != 2) {
 426              return false;
 427          }
 428  
 429          return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT);
 430      }
 431  
 432      function set_value($value) {
 433          // Must override this, otherwise the semicolon separating
 434          // the two floats would get auto-quoted, which is illegal
 435          if($this->is_valid_value($value)) {
 436              $this->value = $value;
 437              return true;
 438          }
 439  
 440          return false;
 441      }
 442  
 443  }
 444  
 445  class iCalendar_property_location extends iCalendar_property {
 446  
 447      var $name        = 'LOCATION';
 448      var $val_type    = RFC2445_TYPE_TEXT;
 449  
 450      function __construct() {
 451          parent::__construct();
 452          $this->valid_parameters = array(
 453              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 454              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 455              RFC2445_XNAME => RFC2445_OPTIONAL
 456          );
 457      }
 458  }
 459  
 460  class iCalendar_property_percent_complete extends iCalendar_property {
 461  
 462      var $name        = 'PERCENT-COMPLETE';
 463      var $val_type    = RFC2445_TYPE_INTEGER;
 464  
 465      function __construct() {
 466          parent::__construct();
 467          $this->valid_parameters = array(
 468              RFC2445_XNAME => RFC2445_OPTIONAL
 469          );
 470      }
 471  
 472      function is_valid_value($value) {
 473          // Only integers between 0 and 100 inclusive allowed
 474          if(!parent::is_valid_value($value)) {
 475              return false;
 476          }
 477          $value = intval($value);
 478          return ($value >= 0 && $value <= 100);
 479      }
 480  
 481  }
 482  
 483  class iCalendar_property_priority extends iCalendar_property {
 484  
 485      var $name        = 'PRIORITY';
 486      var $val_type    = RFC2445_TYPE_INTEGER;
 487  
 488      function __construct() {
 489          parent::__construct();
 490          $this->valid_parameters = array(
 491              RFC2445_XNAME => RFC2445_OPTIONAL
 492          );
 493      }
 494  
 495      function is_valid_value($value) {
 496          // Only integers between 0 and 9 inclusive allowed        
 497          if(!parent::is_valid_value($value)) {
 498              return false;
 499          }
 500  
 501          $value = intval($value);
 502          return ($value >= 0 && $value <= 9);
 503      }
 504  }
 505  
 506  class iCalendar_property_resources extends iCalendar_property {
 507  
 508      var $name        = 'RESOURCES';
 509      var $val_type    = RFC2445_TYPE_TEXT;
 510      var $val_multi   = true;
 511  
 512      function __construct() {
 513          parent::__construct();
 514          $this->valid_parameters = array(
 515              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 516              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 517              RFC2445_XNAME => RFC2445_OPTIONAL
 518          );
 519      }
 520  }
 521  
 522  class iCalendar_property_status extends iCalendar_property {
 523  
 524      var $name        = 'STATUS';
 525      var $val_type    = RFC2445_TYPE_TEXT;
 526  
 527      function __construct() {
 528          parent::__construct();
 529          $this->valid_parameters = array(
 530              RFC2445_XNAME => RFC2445_OPTIONAL
 531          );
 532      }
 533  
 534      function is_valid_value($value) {
 535          // This is case-sensitive
 536          switch ($this->parent_component) {
 537              case 'VEVENT':
 538                  $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED');
 539              break;
 540              case 'VTODO':
 541                  $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED');
 542              break;
 543              case 'VJOURNAL':
 544                  $allowed = array('DRAFT', 'FINAL', 'CANCELLED');
 545              break;
 546          }
 547          return in_array($value, $allowed);
 548  
 549      }
 550  
 551  }
 552  
 553  class iCalendar_property_summary extends iCalendar_property {
 554  
 555      var $name        = 'SUMMARY';
 556      var $val_type    = RFC2445_TYPE_TEXT;
 557  
 558      function __construct() {
 559          parent::__construct();
 560          $this->valid_parameters = array(
 561              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 562              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 563              RFC2445_XNAME => RFC2445_OPTIONAL
 564          );
 565      }
 566  }
 567  
 568  // 4.8.2 Date and Time Component Properties
 569  // ----------------------------------------
 570  
 571  class iCalendar_property_completed extends iCalendar_property {
 572  
 573      var $name        = 'COMPLETED';
 574      var $val_type    = RFC2445_TYPE_DATE_TIME;
 575  
 576      function __construct() {
 577          parent::__construct();
 578          $this->valid_parameters = array(
 579              RFC2445_XNAME => RFC2445_OPTIONAL
 580          );
 581      }
 582  
 583      function is_valid_value($value) {
 584          if(!parent::is_valid_value($value)) {
 585              return false;
 586          }
 587          // Time MUST be in UTC format
 588          return(substr($value, -1) == 'Z');
 589      }
 590  }
 591  
 592  class iCalendar_property_dtend extends iCalendar_property {
 593  
 594      var $name        = 'DTEND';
 595      var $val_type    = RFC2445_TYPE_DATE_TIME;
 596  
 597      function __construct() {
 598          parent::__construct();
 599          $this->valid_parameters = array(
 600              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 601              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 602              RFC2445_XNAME => RFC2445_OPTIONAL
 603          );
 604      }
 605  
 606      function is_valid_value($value) {
 607          if(!parent::is_valid_value($value)) {
 608              return false;
 609          }
 610  
 611          // If present in a FREEBUSY component, must be in UTC format
 612          if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
 613              return false;
 614          }
 615  
 616          return true;
 617  
 618      }
 619  
 620      function is_valid_parameter($parameter, $value) {
 621  
 622          $parameter = strtoupper($parameter);
 623  
 624          if(!parent::is_valid_parameter($parameter, $value)) {
 625              return false;
 626          }
 627          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
 628              return false;
 629          }
 630  
 631          return true;
 632      }
 633  }
 634  
 635  class iCalendar_property_due extends iCalendar_property {
 636  
 637      var $name        = 'DUE';
 638      var $val_type    = RFC2445_TYPE_DATE_TIME;
 639  
 640      function __construct() {
 641          parent::__construct();
 642          $this->valid_parameters = array(
 643              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 644              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 645              RFC2445_XNAME => RFC2445_OPTIONAL
 646          );
 647      }
 648  
 649      function is_valid_value($value) {
 650          if(!parent::is_valid_value($value)) {
 651              return false;
 652          }
 653  
 654          // If present in a FREEBUSY component, must be in UTC format
 655          if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
 656              return false;
 657          }
 658  
 659          return true;
 660  
 661      }
 662  
 663      function is_valid_parameter($parameter, $value) {
 664  
 665          $parameter = strtoupper($parameter);
 666  
 667          if(!parent::is_valid_parameter($parameter, $value)) {
 668              return false;
 669          }
 670          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
 671              return false;
 672          }
 673  
 674          return true;
 675      }
 676  }
 677  
 678  class iCalendar_property_dtstart extends iCalendar_property {
 679  
 680      var $name        = 'DTSTART';
 681      var $val_type    = RFC2445_TYPE_DATE_TIME;
 682  
 683      function __construct() {
 684          parent::__construct();
 685          $this->valid_parameters = array(
 686              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 687              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 688              RFC2445_XNAME => RFC2445_OPTIONAL
 689          );
 690      }
 691  
 692      // TODO: unimplemented stuff when parent is a VTIMEZONE component
 693  
 694      function is_valid_value($value) {
 695          if(!parent::is_valid_value($value)) {
 696              return false;
 697          }
 698  
 699          // If present in a FREEBUSY component, must be in UTC format
 700          if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') {
 701              return false;
 702          }
 703  
 704          return true;
 705      }
 706  
 707      function is_valid_parameter($parameter, $value) {
 708  
 709          $parameter = strtoupper($parameter);
 710  
 711          if(!parent::is_valid_parameter($parameter, $value)) {
 712              return false;
 713          }
 714          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
 715              return false;
 716          }
 717  
 718          return true;
 719      }
 720  }
 721  
 722  class iCalendar_property_duration extends iCalendar_property {
 723  
 724      var $name        = 'DURATION';
 725      var $val_type    = RFC2445_TYPE_DURATION;
 726  
 727      function __construct() {
 728          parent::__construct();
 729          $this->valid_parameters = array(
 730              RFC2445_XNAME => RFC2445_OPTIONAL
 731          );
 732      }
 733  
 734      function is_valid_value($value) {
 735          if(!parent::is_valid_value($value)) {
 736              return false;
 737          }
 738  
 739          // Value must be positive
 740          return ($value[0] != '-');
 741      }
 742  }
 743  
 744  class iCalendar_property_freebusy extends iCalendar_property {
 745  
 746      var $name        = 'FREEBUSY';
 747      var $val_type    = RFC2445_TYPE_PERIOD;
 748      var $val_multi   = true;
 749  
 750      function __construct() {
 751          parent::__construct();
 752          $this->valid_parameters = array(
 753              'FBTYPE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 754              RFC2445_XNAME => RFC2445_OPTIONAL
 755          );
 756      }
 757  
 758      function is_valid_value($value) {
 759          if(!parent::is_valid_value($value)) {
 760              return false;
 761          }
 762  
 763          $pos = strpos($value, '/'); // We know there's only one / in there
 764          if($value[$pos - 1] != 'Z') {
 765              // Start time MUST be in UTC
 766              return false;
 767          }
 768          if($value[$pos + 1] != 'P' && substr($value, -1) != 'Z') {
 769              // If the second part is not a period, it MUST be in UTC
 770              return false;
 771          }
 772  
 773          return true;
 774      }
 775  
 776      // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak)
 777  }
 778  
 779  class iCalendar_property_transp extends iCalendar_property {
 780  
 781      var $name        = 'TRANSP';
 782      var $val_type    = RFC2445_TYPE_TEXT;
 783      var $val_default = 'OPAQUE';
 784  
 785      function __construct() {
 786          parent::__construct();
 787          $this->valid_parameters = array(
 788              RFC2445_XNAME => RFC2445_OPTIONAL
 789          );
 790      }
 791  
 792      function is_valid_value($value) {
 793          return ($value === 'TRANSPARENT' || $value === 'OPAQUE');
 794      }
 795  }
 796  
 797  // TODO: 4.8.3 timezone component properties
 798  
 799  
 800  // 4.8.4 Relationship Component Properties
 801  // ---------------------------------------
 802  
 803  class iCalendar_property_attendee extends iCalendar_property {
 804  
 805      var $name        = 'ATTENDEE';
 806      var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
 807  
 808      // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH
 809      // TODO: standard has lots of detail here, make triple sure that we eventually conform
 810  
 811      function __construct() {
 812          parent::__construct();
 813          $this->valid_parameters = array(
 814              'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 815              'CN'             => RFC2445_OPTIONAL | RFC2445_ONCE,
 816              'ROLE'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 817              'PARTSTAT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 818              'RSVP'           => RFC2445_OPTIONAL | RFC2445_ONCE,
 819              'CUTYPE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 820              'MEMBER'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 821              'DELEGATED-TO'   => RFC2445_OPTIONAL | RFC2445_ONCE,
 822              'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE,
 823              'SENT-BY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 824              'DIR'            => RFC2445_OPTIONAL | RFC2445_ONCE,
 825              RFC2445_XNAME    => RFC2445_OPTIONAL
 826          );
 827      }
 828  
 829      function set_parent_component($componentname) {
 830          if(!parent::set_parent_component($componentname)) {
 831              return false;
 832          }
 833  
 834          if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') {
 835              // Most parameters become invalid in this case, the full allowed set is now:
 836              $this->valid_parameters = array(
 837                  'LANGUAGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 838                  RFC2445_XNAME    => RFC2445_OPTIONAL
 839              );
 840          }
 841  
 842          return false;
 843      }
 844  
 845  }
 846  
 847  class iCalendar_property_contact extends iCalendar_property {
 848  
 849      var $name        = 'CONTACT';
 850      var $val_type    = RFC2445_TYPE_TEXT;
 851  
 852      function __construct() {
 853          parent::__construct();
 854          $this->valid_parameters = array(
 855              'ALTREP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
 856              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 857              RFC2445_XNAME => RFC2445_OPTIONAL
 858          );
 859      }
 860  }
 861  
 862  class iCalendar_property_organizer extends iCalendar_property {
 863  
 864      var $name        = 'ORGANIZER';
 865      var $val_type    = RFC2445_TYPE_CAL_ADDRESS;
 866  
 867      function __construct() {
 868          parent::__construct();
 869          $this->valid_parameters = array(
 870              'CN'          => RFC2445_OPTIONAL | RFC2445_ONCE,
 871              'DIR'         => RFC2445_OPTIONAL | RFC2445_ONCE,
 872              'SENT-BY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 873              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
 874              RFC2445_XNAME => RFC2445_OPTIONAL
 875          );
 876      }
 877  
 878      // TODO:
 879  /*    
 880     Conformance: This property MUST be specified in an iCalendar object
 881     that specifies a group scheduled calendar entity. This property MUST
 882     be specified in an iCalendar object that specifies the publication of
 883     a calendar user's busy time. This property MUST NOT be specified in
 884     an iCalendar object that specifies only a time zone definition or
 885     that defines calendar entities that are not group scheduled entities,
 886     but are entities only on a single user's calendar.
 887  */
 888  
 889  }
 890  
 891  class iCalendar_property_recurrence_id extends iCalendar_property {
 892  
 893      // TODO: can only be specified when defining recurring components in the calendar
 894  /*
 895     Conformance: This property can be specified in an iCalendar object
 896     containing a recurring calendar component.
 897  
 898     Description: The full range of calendar components specified by a
 899     recurrence set is referenced by referring to just the "UID" property
 900     value corresponding to the calendar component. The "RECURRENCE-ID"
 901     property allows the reference to an individual instance within the
 902     recurrence set.
 903  */
 904  
 905      var $name        = 'RECURRENCE-ID';
 906      var $val_type    = RFC2445_TYPE_DATE_TIME;
 907  
 908      function __construct() {
 909          parent::__construct();
 910          $this->valid_parameters = array(
 911              'RANGE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 912              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 913              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 914              RFC2445_XNAME => RFC2445_OPTIONAL
 915          );
 916      }
 917  
 918      function is_valid_parameter($parameter, $value) {
 919  
 920          $parameter = strtoupper($parameter);
 921  
 922          if(!parent::is_valid_parameter($parameter, $value)) {
 923              return false;
 924          }
 925          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
 926              return false;
 927          }
 928  
 929          return true;
 930      }
 931  
 932  }
 933  
 934  class iCalendar_property_related_to extends iCalendar_property {
 935  
 936      var $name        = 'RELATED-TO';
 937      var $val_type    = RFC2445_TYPE_TEXT;
 938  
 939      // TODO: the value of this property must reference another component's UID
 940  
 941      function __construct() {
 942          parent::__construct();
 943          $this->valid_parameters = array(
 944              'RELTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
 945              RFC2445_XNAME => RFC2445_OPTIONAL
 946          );
 947      }
 948  }
 949  
 950  class iCalendar_property_url extends iCalendar_property {
 951  
 952      var $name        = 'URL';
 953      var $val_type    = RFC2445_TYPE_URI;
 954  
 955      function __construct() {
 956          parent::__construct();
 957          $this->valid_parameters = array(
 958              RFC2445_XNAME => RFC2445_OPTIONAL
 959          );
 960      }
 961  }
 962  
 963  class iCalendar_property_uid extends iCalendar_property {
 964  
 965      var $name        = 'UID';
 966      var $val_type    = RFC2445_TYPE_TEXT;
 967  
 968      function __construct() {
 969          parent::__construct();
 970          $this->valid_parameters = array(
 971              RFC2445_XNAME => RFC2445_OPTIONAL
 972          );
 973  
 974          // The exception to the rule: this is not a static value, so we
 975          // generate it on-the-fly here. Guaranteed to be different for
 976          // each instance of this property, too. Nice.
 977          $this->val_default = Bennu::generate_guid();
 978      }
 979  }
 980  
 981  // 4.8.5 Recurrence Component Properties
 982  // -------------------------------------
 983  
 984  class iCalendar_property_exdate extends iCalendar_property {
 985  
 986      var $name        = 'EXDATE';
 987      var $val_type    = RFC2445_TYPE_DATE_TIME;
 988      var $val_multi   = true;
 989  
 990      function __construct() {
 991          parent::__construct();
 992          $this->valid_parameters = array(
 993              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
 994              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
 995              RFC2445_XNAME => RFC2445_OPTIONAL
 996          );
 997      }
 998  
 999      function is_valid_parameter($parameter, $value) {
1000  
1001          $parameter = strtoupper($parameter);
1002  
1003          if(!parent::is_valid_parameter($parameter, $value)) {
1004              return false;
1005          }
1006          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) {
1007              return false;
1008          }
1009  
1010          return true;
1011      }
1012  
1013  }
1014  
1015  class iCalendar_property_exrule extends iCalendar_property {
1016  
1017      var $name        = 'EXRULE';
1018      var $val_type    = RFC2445_TYPE_RECUR;
1019  
1020      function __construct() {
1021          parent::__construct();
1022          $this->valid_parameters = array(
1023              RFC2445_XNAME => RFC2445_OPTIONAL
1024          );
1025      }
1026  }
1027  
1028  class iCalendar_property_rdate extends iCalendar_property {
1029  
1030      var $name        = 'RDATE';
1031      var $val_type    = RFC2445_TYPE_DATE_TIME;
1032      var $val_multi   = true;
1033  
1034      function __construct() {
1035          parent::__construct();
1036          $this->valid_parameters = array(
1037              'TZID'        => RFC2445_OPTIONAL | RFC2445_ONCE,
1038              'VALUE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
1039              RFC2445_XNAME => RFC2445_OPTIONAL
1040          );
1041      }
1042  
1043      function is_valid_parameter($parameter, $value) {
1044  
1045          $parameter = strtoupper($parameter);
1046  
1047          if(!parent::is_valid_parameter($parameter, $value)) {
1048              return false;
1049          }
1050          if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) {
1051              return false;
1052          }
1053  
1054          return true;
1055      }
1056  
1057  }
1058  
1059  class iCalendar_property_rrule extends iCalendar_property {
1060  
1061      var $name        = 'RRULE';
1062      var $val_type    = RFC2445_TYPE_RECUR;
1063  
1064      function __construct() {
1065          parent::__construct();
1066          $this->valid_parameters = array(
1067              RFC2445_XNAME => RFC2445_OPTIONAL
1068          );
1069      }
1070  }
1071  
1072  // 4.8.6 Alarm Component Properties
1073  // -------------------------------------------
1074  class iCalendar_property_action extends iCalendar_property {
1075  	 var $name      = 'ACTION';
1076      var $val_type   = RFC2445_TYPE_TEXT;
1077      
1078      function __construct() {
1079          parent::__construct();
1080          $this->valid_parameters = array(
1081              RFC2445_XNAME => RFC2445_OPTIONAL
1082          );
1083      }
1084      
1085      function is_valid_value($value) {
1086          if(!parent::is_valid_value($value)) {
1087              return false;
1088          }
1089          
1090          // Value must be one of the following, or an x-name.
1091          $valid_values = array('ACTION', 'DISPLAY', 'EMAIL', 'PROCEDURE');
1092          return(in_array($value, $valid_values) || rfc2445_is_xname($value));        
1093          
1094      }
1095  }
1096  
1097  class iCalendar_property_repeat extends iCalendar_property {
1098      var $name      = 'REPEAT';
1099      var $val_type   = RFC2445_TYPE_INTEGER;
1100      
1101      function __construct() {
1102          parent::__construct();
1103          $this->valid_parameters = array(
1104              RFC2445_XNAME => RFC2445_OPTIONAL
1105          );
1106      }   
1107  }
1108  
1109  class iCalendar_property_trigger extends iCalendar_property {
1110      var $name      = 'TRIGGER';
1111      var $val_type   = RFC2445_TYPE_TEXT;
1112      
1113      function __construct() {
1114          parent::__construct();
1115          $this->valid_parameters = array(
1116              'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1117              'RELATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
1118              RFC2445_XNAME => RFC2445_OPTIONAL
1119          );
1120      }
1121      
1122      function is_valid_value($value) {        
1123      	 if(!parent::is_valid_value($value)) {
1124              return false;
1125          }
1126          // Must either be DURATION or DATE_TIME type
1127          return(rfc2445_is_valid_value($value, RFC2445_TYPE_DURATION) 
1128              || rfc2445_is_valid_value($value, RFC2445_TYPE_DATE_TIME));
1129      }
1130  }
1131  
1132  
1133  
1134  // 4.8.7 Change Management Component Properties
1135  // --------------------------------------------
1136  
1137  class iCalendar_property_created extends iCalendar_property {
1138  
1139      var $name        = 'CREATED';
1140      var $val_type    = RFC2445_TYPE_DATE_TIME;
1141  
1142      function __construct() {
1143          parent::__construct();
1144          $this->valid_parameters = array(
1145              RFC2445_XNAME => RFC2445_OPTIONAL
1146          );
1147      }
1148  
1149      function is_valid_value($value) {
1150          if(!parent::is_valid_value($value)) {
1151              return false;
1152          }
1153          // Time MUST be in UTC format
1154          return(substr($value, -1) == 'Z');
1155      }
1156  }
1157  
1158  class iCalendar_property_dtstamp extends iCalendar_property {
1159  
1160      var $name        = 'DTSTAMP';
1161      var $val_type    = RFC2445_TYPE_DATE_TIME;
1162  
1163      function __construct() {
1164          parent::__construct();
1165          $this->valid_parameters = array(
1166              RFC2445_XNAME => RFC2445_OPTIONAL
1167          );
1168      }
1169  
1170      function is_valid_value($value) {
1171          if(!parent::is_valid_value($value)) {
1172              return false;
1173          }
1174          // Time MUST be in UTC format
1175          return(substr($value, -1) == 'Z');
1176      }
1177  }
1178  
1179  class iCalendar_property_last_modified extends iCalendar_property {
1180  
1181      var $name        = 'LAST-MODIFIED';
1182      var $val_type    = RFC2445_TYPE_DATE_TIME;
1183  
1184      function __construct() {
1185          parent::__construct();
1186          $this->valid_parameters = array(
1187              RFC2445_XNAME => RFC2445_OPTIONAL
1188          );
1189      }
1190  
1191      function is_valid_value($value) {
1192          if(!parent::is_valid_value($value)) {
1193              return false;
1194          }
1195          // Time MUST be in UTC format
1196          return(substr($value, -1) == 'Z');
1197      }
1198  }
1199  
1200  class iCalendar_property_sequence extends iCalendar_property {
1201  
1202      var $name        = 'SEQUENCE';
1203      var $val_type    = RFC2445_TYPE_INTEGER;
1204      var $val_default = 0;
1205  
1206      function __construct() {
1207          parent::__construct();
1208          $this->valid_parameters = array(
1209              RFC2445_XNAME => RFC2445_OPTIONAL
1210          );
1211      }
1212  
1213      function is_valid_value($value) {
1214          if(!parent::is_valid_value($value)) {
1215              return false;
1216          }
1217          $value = intval($value);
1218          return ($value >= 0);
1219      }
1220  }
1221  
1222  // 4.8.8 Miscellaneous Component Properties
1223  // ----------------------------------------
1224  
1225  class iCalendar_property_x extends iCalendar_property {
1226  
1227      var $name        = RFC2445_XNAME;
1228      var $val_type    = NULL;
1229  
1230      function __construct() {
1231          parent::__construct();
1232          $this->valid_parameters = array(
1233              // X-ALT-DESC (Description) can have FMTTYPE declaration of text/html is a property for HTML content.
1234              'FMTTYPE'     => RFC2445_OPTIONAL | RFC2445_ONCE,
1235              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1236              RFC2445_XNAME => RFC2445_OPTIONAL
1237          );
1238      }
1239  
1240      function set_name($name) {
1241  
1242          $name = strtoupper($name);
1243  
1244          if(rfc2445_is_xname($name)) {
1245              $this->name = $name;
1246              return true;
1247          }
1248  
1249          return false;
1250      }
1251  }
1252  
1253  class iCalendar_property_request_status extends iCalendar_property {
1254  
1255      // IMPORTANT NOTE: This property value includes TEXT fields
1256      // separated by semicolons. Unfortunately, auto-value-formatting
1257      // cannot be used in this case. As an exception, the value passed
1258      // to this property MUST be already escaped.
1259  
1260      var $name        = 'REQUEST-STATUS';
1261      var $val_type    = RFC2445_TYPE_TEXT;
1262  
1263      function __construct() {
1264          parent::__construct();
1265          $this->valid_parameters = array(
1266              'LANGUAGE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
1267              RFC2445_XNAME => RFC2445_OPTIONAL
1268          );
1269      }
1270  
1271      function is_valid_value($value) {
1272          if(!is_string($value) || empty($value)) {
1273              return false;
1274          }
1275  
1276          $len   = strlen($value);
1277          $parts = array();
1278          $from  = 0;
1279          $escch = false;
1280  
1281          for($i = 0; $i < $len; ++$i) {
1282              if($value[$i] == ';' && !$escch) {
1283                  // Token completed
1284                  $parts[] = substr($value, $from, $i - $from);
1285                  $from = $i + 1;
1286                  continue;
1287              }
1288              $escch = ($value[$i] == '\\');
1289          }
1290          // Add one last token with the remaining text; if the value
1291          // ended with a ';' it was illegal, so check that this token
1292          // is not the empty string.
1293          $parts[] = substr($value, $from);
1294  
1295          $count = count($parts);
1296  
1297          // May have 2 or 3 tokens (last one is optional)
1298          if($count != 2 && $count != 3) {
1299              return false;
1300          }
1301  
1302          // REMEMBER: if ANY part is empty, we have an illegal value
1303  
1304          // First token must be hierarchical numeric status (3 levels max)
1305          if(strlen($parts[0]) == 0) {
1306              return false;
1307          }
1308  
1309          if($parts[0][0] < '1' || $parts[0][0] > '4') {
1310              return false;
1311          }
1312  
1313          $len = strlen($parts[0]);
1314  
1315          // Max 3 levels, and can't end with a period
1316          if($len > 5 || $parts[0][$len - 1] == '.') {
1317              return false;
1318          }
1319  
1320          for($i = 1; $i < $len; ++$i) {
1321              if(($i & 1) == 1 && $parts[0][$i] != '.') {
1322                  // Even-indexed chars must be periods
1323                  return false;
1324              }
1325              else if(($i & 1) == 0 && ($parts[0][$i] < '0' || $parts[0][$i] > '9')) {
1326                  // Odd-indexed chars must be numbers
1327                  return false;
1328              }
1329          }
1330  
1331          // Second and third tokens must be TEXT, and already escaped, so
1332          // they are not allowed to have UNESCAPED semicolons, commas, slashes,
1333          // or any newlines at all
1334  
1335          for($i = 1; $i < $count; ++$i) {
1336              if(strpos($parts[$i], "\n") !== false) {
1337                  return false;
1338              }
1339  
1340              $len = strlen($parts[$i]);
1341              if($len == 0) {
1342                  // Cannot be empty
1343                  return false;
1344              }
1345  
1346              $parts[$i] .= '#'; // This guard token saves some conditionals in the loop
1347  
1348              for($j = 0; $j < $len; ++$j) {
1349                  $thischar = $parts[$i][$j];
1350                  $nextchar = $parts[$i][$j + 1];
1351                  if($thischar == '\\') {
1352                      // Next char must now be one of ";,\nN"
1353                      if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' &&
1354                         $nextchar != 'n' && $nextchar != 'N') {
1355                          return false;
1356                      }
1357  
1358                      // OK, this escaped sequence is correct, bypass next char
1359                      ++$j;
1360                      continue;
1361                  }
1362                  if($thischar == ';' || $thischar == ',' || $thischar == '\\') {
1363                      // This wasn't escaped as it should
1364                      return false;
1365                  }
1366              }
1367          }
1368  
1369          return true;
1370      }
1371  
1372      function set_value($value) {
1373          // Must override this, otherwise the value would be quoted again
1374          if($this->is_valid_value($value)) {
1375              $this->value = $value;
1376              return true;
1377          }
1378  
1379          return false;
1380      }
1381  
1382  }
1383  
1384  class iCalendar_property_tzid extends iCalendar_property {
1385  
1386      var $name        = 'TZID';
1387      var $val_type    = RFC2445_TYPE_TEXT;
1388  
1389      function __construct() {
1390          parent::__construct();
1391          $this->valid_parameters = array(
1392              RFC2445_XNAME => RFC2445_OPTIONAL
1393          );
1394      }
1395  
1396      function is_valid_value($value) {
1397          if(!parent::is_valid_value($value)) {
1398              return false;
1399          } else {
1400              return true;
1401          }
1402      }
1403  }
1404  
1405  class iCalendar_property_tzname extends iCalendar_property {
1406  
1407      var $name        = 'TZNAME';
1408      var $val_type    = RFC2445_TYPE_TEXT;
1409  
1410      function __construct() {
1411          parent::__construct();
1412          $this->valid_parameters = array(
1413              'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
1414              RFC2445_XNAME => RFC2445_OPTIONAL
1415          );
1416      }
1417  
1418      function is_valid_value($value) {
1419          if(!parent::is_valid_value($value)) {
1420              return false;
1421          } else {
1422              return true;
1423          }
1424      }
1425  }
1426  
1427  class iCalendar_property_tzoffsetfrom extends iCalendar_property {
1428  
1429      var $name        = 'TZOFFSETFROM';
1430      var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1431  
1432      function __construct() {
1433          parent::__construct();
1434          $this->valid_parameters = array(
1435              RFC2445_XNAME => RFC2445_OPTIONAL
1436          );
1437      }
1438  
1439      function is_valid_value($value) {
1440          if(!parent::is_valid_value($value)) {
1441              return false;
1442          } else {
1443              return true;
1444          }
1445      }
1446  }
1447  
1448  class iCalendar_property_tzoffsetto extends iCalendar_property {
1449  
1450      var $name        = 'TZOFFSETTO';
1451      var $val_type    = RFC2445_TYPE_UTC_OFFSET;
1452  
1453      function __construct() {
1454          parent::__construct();
1455          $this->valid_parameters = array(
1456              RFC2445_XNAME => RFC2445_OPTIONAL
1457          );
1458      }
1459  
1460      function is_valid_value($value) {
1461          if(!parent::is_valid_value($value)) {
1462              return false;
1463          } else {
1464          	 return true;
1465          }
1466      }
1467  }
1468  
1469  class iCalendar_property_tzurl extends iCalendar_property {
1470  
1471      var $name        = 'TZURL';
1472      var $val_type    = RFC2445_TYPE_URI;
1473  
1474      function __construct() {
1475          parent::__construct();
1476          $this->valid_parameters = array(
1477                  RFC2445_XNAME => RFC2445_OPTIONAL
1478          );
1479      }
1480  }
1481  
1482  #######################
1483  /*
1484  class iCalendar_property_class extends iCalendar_property {
1485  
1486      var $name        = 'CLASS';
1487      var $val_type    = RFC2445_TYPE_TEXT;
1488  
1489      function __construct() {
1490          parent::__construct();
1491          $this->valid_parameters = array(
1492              RFC2445_XNAME => RFC2445_OPTIONAL
1493          );
1494      }
1495  }
1496  */
1497