PageRenderTime 27ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/bennu/iCalendar_components.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 699 lines | 498 code | 109 blank | 92 comment | 92 complexity | 7fe47f7f3d0e3bdcdeb18a43a26c85c6 MD5 | raw file
  1. <?php
  2. /**
  3. * BENNU - PHP iCalendar library
  4. * (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
  5. *
  6. * Released under the LGPL.
  7. *
  8. * See http://bennu.sourceforge.net/ for more information and downloads.
  9. *
  10. * @author Ioannis Papaioannou
  11. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  12. */
  13. class iCalendar_component {
  14. var $name = NULL;
  15. var $properties = NULL;
  16. var $components = NULL;
  17. var $valid_properties = NULL;
  18. var $valid_components = NULL;
  19. /**
  20. * Added to hold errors from last run of unserialize
  21. * @var $parser_errors array
  22. */
  23. var $parser_errors = NULL;
  24. function __construct() {
  25. // Initialize the components array
  26. if(empty($this->components)) {
  27. $this->components = array();
  28. foreach($this->valid_components as $name) {
  29. $this->components[$name] = array();
  30. }
  31. }
  32. }
  33. function get_name() {
  34. return $this->name;
  35. }
  36. function add_property($name, $value = NULL, $parameters = NULL) {
  37. // Uppercase first of all
  38. $name = strtoupper($name);
  39. // Are we trying to add a valid property?
  40. $xname = false;
  41. if(!isset($this->valid_properties[$name])) {
  42. // If not, is it an x-name as per RFC 2445?
  43. if(!rfc2445_is_xname($name)) {
  44. return false;
  45. }
  46. // Since this is an xname, all components are supposed to allow this property
  47. $xname = true;
  48. }
  49. // Create a property object of the correct class
  50. if($xname) {
  51. $property = new iCalendar_property_x;
  52. $property->set_name($name);
  53. }
  54. else {
  55. $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $name));
  56. $property = new $classname;
  57. }
  58. // If $value is NULL, then this property must define a default value.
  59. if($value === NULL) {
  60. $value = $property->default_value();
  61. if($value === NULL) {
  62. return false;
  63. }
  64. }
  65. // Set this property's parent component to ourselves, because some
  66. // properties behave differently according to what component they apply to.
  67. $property->set_parent_component($this->name);
  68. // Set parameters before value; this helps with some properties which
  69. // accept a VALUE parameter, and thus change their default value type.
  70. // The parameters must be valid according to property specifications
  71. if(!empty($parameters)) {
  72. foreach($parameters as $paramname => $paramvalue) {
  73. if(!$property->set_parameter($paramname, $paramvalue)) {
  74. return false;
  75. }
  76. }
  77. // Some parameters interact among themselves (e.g. ENCODING and VALUE)
  78. // so make sure that after the dust settles, these invariants hold true
  79. if(!$property->invariant_holds()) {
  80. return false;
  81. }
  82. }
  83. // $value MUST be valid according to the property data type
  84. if(!$property->set_value($value)) {
  85. return false;
  86. }
  87. // Check if the property already exists, and is limited to one occurrance,
  88. // DON'T overwrite the value - this can be done explicity with set_value() instead.
  89. if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE && isset($this->properties[$name])) {
  90. return false;
  91. }
  92. else {
  93. // Otherwise add it to the instance array for this property
  94. $this->properties[$name][] = $property;
  95. }
  96. // Finally: after all these, does the component invariant hold?
  97. if(!$this->invariant_holds()) {
  98. // If not, completely undo the property addition
  99. array_pop($this->properties[$name]);
  100. if(empty($this->properties[$name])) {
  101. unset($this->properties[$name]);
  102. }
  103. return false;
  104. }
  105. return true;
  106. }
  107. function add_component($component) {
  108. // With the detailed interface, you can add only components with this function
  109. if(!is_object($component) || !is_subclass_of($component, 'iCalendar_component')) {
  110. return false;
  111. }
  112. $name = $component->get_name();
  113. // Only valid components as specified by this component are allowed
  114. if(!in_array($name, $this->valid_components)) {
  115. return false;
  116. }
  117. // Add it
  118. $this->components[$name][] = $component;
  119. return true;
  120. }
  121. function get_property_list($name) {
  122. }
  123. function invariant_holds() {
  124. return true;
  125. }
  126. function is_valid() {
  127. // If we have any child components, check that they are all valid
  128. if(!empty($this->components)) {
  129. foreach($this->components as $component => $instances) {
  130. foreach($instances as $number => $instance) {
  131. if(!$instance->is_valid()) {
  132. return false;
  133. }
  134. }
  135. }
  136. }
  137. // Finally, check the valid property list for any mandatory properties
  138. // that have not been set and do not have a default value
  139. foreach($this->valid_properties as $property => $propdata) {
  140. if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
  141. $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $property));
  142. $object = new $classname;
  143. if($object->default_value() === NULL) {
  144. return false;
  145. }
  146. unset($object);
  147. }
  148. }
  149. return true;
  150. }
  151. function serialize() {
  152. // Check for validity of the object
  153. if(!$this->is_valid()) {
  154. return false;
  155. }
  156. // Maybe the object is valid, but there are some required properties that
  157. // have not been given explicit values. In that case, set them to defaults.
  158. foreach($this->valid_properties as $property => $propdata) {
  159. if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) {
  160. $this->add_property($property);
  161. }
  162. }
  163. // Start tag
  164. $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF;
  165. // List of properties
  166. if(!empty($this->properties)) {
  167. foreach($this->properties as $name => $properties) {
  168. foreach($properties as $property) {
  169. $string .= $property->serialize();
  170. }
  171. }
  172. }
  173. // List of components
  174. if(!empty($this->components)) {
  175. foreach($this->components as $name => $components) {
  176. foreach($components as $component) {
  177. $string .= $component->serialize();
  178. }
  179. }
  180. }
  181. // End tag
  182. $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF;
  183. return $string;
  184. }
  185. /**
  186. * unserialize()
  187. *
  188. * I needed a way to convert an iCalendar component back to a Bennu object so I could
  189. * easily access and modify it after it had been stored; if this functionality is already
  190. * present somewhere in the library, I apologize for adding it here unnecessarily; however,
  191. * I couldn't find it so I added it myself.
  192. * @param string $string the iCalendar object to load in to this iCalendar_component
  193. * @return bool true if the file parsed with no errors. False if there were errors.
  194. */
  195. function unserialize($string) {
  196. $string = rfc2445_unfold($string); // Unfold any long lines
  197. $lines = preg_split("<".RFC2445_CRLF."|\n|\r>", $string, 0, PREG_SPLIT_NO_EMPTY); // Create an array of lines.
  198. $components = array(); // Initialise a stack of components
  199. $this->clear_errors();
  200. foreach ($lines as $key => $line) {
  201. // ignore empty lines
  202. if (trim($line) == '') {
  203. continue;
  204. }
  205. // Divide the line up into label, parameters and data fields.
  206. if (!preg_match('#^(?P<label>[-[:alnum:]]+)(?P<params>(?:;(?:(?:[-[:alnum:]]+)=(?:[^[:cntrl:]";:,]+|"[^[:cntrl:]"]+")))*):(?P<data>.*)$#', $line, $match)) {
  207. $this->parser_error('Invalid line: '.$key.', ignoring');
  208. continue;
  209. }
  210. // parse parameters
  211. $params = array();
  212. if (preg_match_all('#;(?P<param>[-[:alnum:]]+)=(?P<value>[^[:cntrl:]";:,]+|"[^[:cntrl:]"]+")#', $match['params'], $pmatch)) {
  213. $params = array_combine($pmatch['param'], $pmatch['value']);
  214. }
  215. $label = $match['label'];
  216. $data = $match['data'];
  217. unset($match, $pmatch);
  218. if ($label == 'BEGIN') {
  219. // This is the start of a component.
  220. $current_component = array_pop($components); // Get the current component off the stack so we can check its valid components
  221. if ($current_component == null) { // If there's nothing on the stack
  222. $current_component = $this; // use the iCalendar
  223. }
  224. if (in_array($data, $current_component->valid_components)) { // Check that the new component is a valid subcomponent of the current one
  225. if($current_component != $this) {
  226. array_push($components, $current_component); // We're done with the current component, put it back on the stack.
  227. }
  228. if(strpos($data, 'V') === 0) {
  229. $data = substr($data, 1);
  230. }
  231. $cname = 'iCalendar_' . strtolower($data);
  232. $new_component = new $cname;
  233. array_push($components, $new_component); // Push a new component onto the stack
  234. } else {
  235. if($current_component != $this) {
  236. array_push($components, $current_component);
  237. $this->parser_error('Invalid component type on line '.$key);
  238. }
  239. }
  240. unset($current_component, $new_component);
  241. } else if ($label == 'END') {
  242. // It's the END of a component.
  243. $component = array_pop($components); // Pop the top component off the stack - we're now done with it
  244. $parent_component = array_pop($components); // Pop the component's conatining component off the stack so we can add this component to it.
  245. if($parent_component == null) {
  246. $parent_component = $this; // If there's no components on the stack, use the iCalendar object
  247. }
  248. if ($parent_component->add_component($component) === false) {
  249. $this->parser_error("Failed to add component on line $key");
  250. }
  251. if ($parent_component != $this) { // If we're not using the iCalendar
  252. array_push($components, $parent_component); // Put the component back on the stack
  253. }
  254. unset($parent_component, $component);
  255. } else {
  256. $component = array_pop($components); // Get the component off the stack so we can add properties to it
  257. if ($component == null) { // If there's nothing on the stack
  258. $component = $this; // use the iCalendar
  259. }
  260. if ($component->add_property($label, $data, $params) === false) {
  261. $this->parser_error("Failed to add property '$label' on line $key");
  262. }
  263. if($component != $this) { // If we're not using the iCalendar
  264. array_push($components, $component); // Put the component back on the stack
  265. }
  266. unset($component);
  267. }
  268. }
  269. }
  270. function clear_errors() {
  271. $this->parser_errors = array();
  272. }
  273. function parser_error($error) {
  274. $this->parser_errors[] = $error;
  275. }
  276. }
  277. class iCalendar extends iCalendar_component {
  278. var $name = 'VCALENDAR';
  279. function __construct() {
  280. $this->valid_properties = array(
  281. 'CALSCALE' => RFC2445_OPTIONAL | RFC2445_ONCE,
  282. 'METHOD' => RFC2445_OPTIONAL | RFC2445_ONCE,
  283. 'PRODID' => RFC2445_REQUIRED | RFC2445_ONCE,
  284. 'VERSION' => RFC2445_REQUIRED | RFC2445_ONCE,
  285. RFC2445_XNAME => RFC2445_OPTIONAL
  286. );
  287. $this->valid_components = array(
  288. 'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
  289. );
  290. parent::__construct();
  291. }
  292. }
  293. class iCalendar_event extends iCalendar_component {
  294. var $name = 'VEVENT';
  295. var $properties;
  296. function __construct() {
  297. $this->valid_components = array('VALARM');
  298. $this->valid_properties = array(
  299. 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  300. 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  301. 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  302. // Standard ambiguous here: in 4.6.1 it says that DTSTAMP in optional,
  303. // while in 4.8.7.2 it says it's REQUIRED. Go with REQUIRED.
  304. 'DTSTAMP' => RFC2445_REQUIRED | RFC2445_ONCE,
  305. // Standard ambiguous here: in 4.6.1 it says that DTSTART in optional,
  306. // while in 4.8.2.4 it says it's REQUIRED. Go with REQUIRED.
  307. 'DTSTART' => RFC2445_REQUIRED | RFC2445_ONCE,
  308. 'GEO' => RFC2445_OPTIONAL | RFC2445_ONCE,
  309. 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  310. 'LOCATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  311. 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
  312. 'PRIORITY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  313. 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
  314. 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  315. 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  316. 'TRANSP' => RFC2445_OPTIONAL | RFC2445_ONCE,
  317. // Standard ambiguous here: in 4.6.1 it says that UID in optional,
  318. // while in 4.8.4.7 it says it's REQUIRED. Go with REQUIRED.
  319. 'UID' => RFC2445_REQUIRED | RFC2445_ONCE,
  320. 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
  321. 'RECURRENCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  322. 'DTEND' => RFC2445_OPTIONAL | RFC2445_ONCE,
  323. 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  324. 'ATTACH' => RFC2445_OPTIONAL,
  325. 'ATTENDEE' => RFC2445_OPTIONAL,
  326. 'CATEGORIES' => RFC2445_OPTIONAL,
  327. 'COMMENT' => RFC2445_OPTIONAL,
  328. 'CONTACT' => RFC2445_OPTIONAL,
  329. 'EXDATE' => RFC2445_OPTIONAL,
  330. 'EXRULE' => RFC2445_OPTIONAL,
  331. 'REQUEST-STATUS' => RFC2445_OPTIONAL,
  332. 'RELATED-TO' => RFC2445_OPTIONAL,
  333. 'RESOURCES' => RFC2445_OPTIONAL,
  334. 'RDATE' => RFC2445_OPTIONAL,
  335. 'RRULE' => RFC2445_OPTIONAL,
  336. RFC2445_XNAME => RFC2445_OPTIONAL
  337. );
  338. parent::__construct();
  339. }
  340. function invariant_holds() {
  341. // DTEND and DURATION must not appear together
  342. if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
  343. return false;
  344. }
  345. if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
  346. // DTEND must be later than DTSTART
  347. // The standard is not clear on how to hande different value types though
  348. // TODO: handle this correctly even if the value types are different
  349. if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
  350. return false;
  351. }
  352. // DTEND and DTSTART must have the same value type
  353. if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
  354. return false;
  355. }
  356. }
  357. return true;
  358. }
  359. }
  360. class iCalendar_todo extends iCalendar_component {
  361. var $name = 'VTODO';
  362. var $properties;
  363. function __construct() {
  364. $this->valid_components = array('VALARM');
  365. $this->valid_properties = array(
  366. 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  367. 'COMPLETED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  368. 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  369. 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  370. 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
  371. 'DTSTAP' => RFC2445_OPTIONAL | RFC2445_ONCE,
  372. 'GEO' => RFC2445_OPTIONAL | RFC2445_ONCE,
  373. 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  374. 'LOCATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  375. 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
  376. 'PERCENT' => RFC2445_OPTIONAL | RFC2445_ONCE,
  377. 'PRIORITY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  378. 'RECURID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  379. 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
  380. 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  381. 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  382. 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  383. 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
  384. 'DUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
  385. 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  386. 'ATTACH' => RFC2445_OPTIONAL,
  387. 'ATTENDEE' => RFC2445_OPTIONAL,
  388. 'CATEGORIES' => RFC2445_OPTIONAL,
  389. 'COMMENT' => RFC2445_OPTIONAL,
  390. 'CONTACT' => RFC2445_OPTIONAL,
  391. 'EXDATE' => RFC2445_OPTIONAL,
  392. 'EXRULE' => RFC2445_OPTIONAL,
  393. 'RSTATUS' => RFC2445_OPTIONAL,
  394. 'RELATED' => RFC2445_OPTIONAL,
  395. 'RESOURCES' => RFC2445_OPTIONAL,
  396. 'RDATE' => RFC2445_OPTIONAL,
  397. 'RRULE' => RFC2445_OPTIONAL,
  398. RFC2445_XNAME => RFC2445_OPTIONAL
  399. );
  400. parent::__construct();
  401. }
  402. function invariant_holds() {
  403. // DTEND and DURATION must not appear together
  404. if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
  405. return false;
  406. }
  407. if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
  408. // DTEND must be later than DTSTART
  409. // The standard is not clear on how to hande different value types though
  410. // TODO: handle this correctly even if the value types are different
  411. if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
  412. return false;
  413. }
  414. // DTEND and DTSTART must have the same value type
  415. if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
  416. return false;
  417. }
  418. }
  419. if(isset($this->properties['DUE']) && isset($this->properties['DTSTART'])) {
  420. if($this->properties['DUE'][0]->value <= $this->properties['DTSTART'][0]->value) {
  421. return false;
  422. }
  423. }
  424. return true;
  425. }
  426. }
  427. class iCalendar_journal extends iCalendar_component {
  428. var $name = 'VJOURNAL';
  429. var $properties;
  430. function __construct() {
  431. $this->valid_properties = array(
  432. 'CLASS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  433. 'CREATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  434. 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  435. 'DTSTART' => RFC2445_OPTIONAL | RFC2445_ONCE,
  436. 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
  437. 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  438. 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
  439. 'RECURRANCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  440. 'SEQUENCE' => RFC2445_OPTIONAL | RFC2445_ONCE,
  441. 'STATUS' => RFC2445_OPTIONAL | RFC2445_ONCE,
  442. 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  443. 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  444. 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
  445. 'ATTACH' => RFC2445_OPTIONAL,
  446. 'ATTENDEE' => RFC2445_OPTIONAL,
  447. 'CATEGORIES' => RFC2445_OPTIONAL,
  448. 'COMMENT' => RFC2445_OPTIONAL,
  449. 'CONTACT' => RFC2445_OPTIONAL,
  450. 'EXDATE' => RFC2445_OPTIONAL,
  451. 'EXRULE' => RFC2445_OPTIONAL,
  452. 'RELATED-TO' => RFC2445_OPTIONAL,
  453. 'RDATE' => RFC2445_OPTIONAL,
  454. 'RRULE' => RFC2445_OPTIONAL,
  455. RFC2445_XNAME => RFC2445_OPTIONAL
  456. );
  457. parent::__construct();
  458. }
  459. }
  460. class iCalendar_freebusy extends iCalendar_component {
  461. var $name = 'VFREEBUSY';
  462. var $properties;
  463. function __construct() {
  464. $this->valid_components = array();
  465. $this->valid_properties = array(
  466. 'CONTACT' => RFC2445_OPTIONAL | RFC2445_ONCE,
  467. 'DTSTART' => RFC2445_OPTIONAL | RFC2445_ONCE,
  468. 'DTEND' => RFC2445_OPTIONAL | RFC2445_ONCE,
  469. 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  470. 'DTSTAMP' => RFC2445_OPTIONAL | RFC2445_ONCE,
  471. 'ORGANIZER' => RFC2445_OPTIONAL | RFC2445_ONCE,
  472. 'UID' => RFC2445_OPTIONAL | RFC2445_ONCE,
  473. 'URL' => RFC2445_OPTIONAL | RFC2445_ONCE,
  474. // TODO: the next two are components of their own!
  475. 'ATTENDEE' => RFC2445_OPTIONAL,
  476. 'COMMENT' => RFC2445_OPTIONAL,
  477. 'FREEBUSY' => RFC2445_OPTIONAL,
  478. 'RSTATUS' => RFC2445_OPTIONAL,
  479. RFC2445_XNAME => RFC2445_OPTIONAL
  480. );
  481. parent::__construct();
  482. }
  483. function invariant_holds() {
  484. // DTEND and DURATION must not appear together
  485. if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
  486. return false;
  487. }
  488. if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
  489. // DTEND must be later than DTSTART
  490. // The standard is not clear on how to hande different value types though
  491. // TODO: handle this correctly even if the value types are different
  492. if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
  493. return false;
  494. }
  495. // DTEND and DTSTART must have the same value type
  496. if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
  497. return false;
  498. }
  499. }
  500. return true;
  501. }
  502. }
  503. class iCalendar_alarm extends iCalendar_component {
  504. var $name = 'VALARM';
  505. var $properties;
  506. function __construct() {
  507. $this->valid_components = array();
  508. $this->valid_properties = array(
  509. 'ACTION' => RFC2445_REQUIRED | RFC2445_ONCE,
  510. 'TRIGGER' => RFC2445_REQUIRED | RFC2445_ONCE,
  511. // If one of these 2 occurs, so must the other.
  512. 'DURATION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  513. 'REPEAT' => RFC2445_OPTIONAL | RFC2445_ONCE,
  514. // The following is required if action == "PROCEDURE" | "AUDIO"
  515. 'ATTACH' => RFC2445_OPTIONAL,
  516. // The following is required if trigger == "EMAIL" | "DISPLAY"
  517. 'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
  518. // The following are required if action == "EMAIL"
  519. 'SUMMARY' => RFC2445_OPTIONAL | RFC2445_ONCE,
  520. 'ATTENDEE' => RFC2445_OPTIONAL,
  521. RFC2445_XNAME => RFC2445_OPTIONAL
  522. );
  523. parent::__construct();
  524. }
  525. function invariant_holds() {
  526. // DTEND and DURATION must not appear together
  527. if(isset($this->properties['ACTION'])) {
  528. switch ($this->properties['ACTION'][0]->value) {
  529. case 'AUDIO':
  530. if (!isset($this->properties['ATTACH'])) {
  531. return false;
  532. }
  533. break;
  534. case 'DISPLAY':
  535. if (!isset($this->properties['DESCRIPTION'])) {
  536. return false;
  537. }
  538. break;
  539. case 'EMAIL':
  540. if (!isset($this->properties['DESCRIPTION']) || !isset($this->properties['SUMMARY']) || !isset($this->properties['ATTACH'])) {
  541. return false;
  542. }
  543. break;
  544. case 'PROCEDURE':
  545. if (!isset($this->properties['ATTACH']) || count($this->properties['ATTACH']) > 1) {
  546. return false;
  547. }
  548. break;
  549. }
  550. }
  551. return true;
  552. }
  553. }
  554. class iCalendar_timezone extends iCalendar_component {
  555. var $name = 'VTIMEZONE';
  556. var $properties;
  557. function __construct() {
  558. $this->valid_components = array('STANDARD', 'DAYLIGHT');
  559. $this->valid_properties = array(
  560. 'TZID' => RFC2445_REQUIRED | RFC2445_ONCE,
  561. 'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
  562. 'TZURL' => RFC2445_OPTIONAL | RFC2445_ONCE,
  563. RFC2445_XNAME => RFC2445_OPTIONAL
  564. );
  565. parent::__construct();
  566. }
  567. }
  568. class iCalendar_standard extends iCalendar_component {
  569. var $name = 'STANDARD';
  570. var $properties;
  571. function __construct() {
  572. $this->valid_components = array();
  573. $this->valid_properties = array(
  574. 'DTSTART' => RFC2445_REQUIRED | RFC2445_ONCE,
  575. 'TZOFFSETTO' => RFC2445_REQUIRED | RFC2445_ONCE,
  576. 'TZOFFSETFROM' => RFC2445_REQUIRED | RFC2445_ONCE,
  577. 'COMMENT' => RFC2445_OPTIONAL,
  578. 'RDATE' => RFC2445_OPTIONAL,
  579. 'RRULE' => RFC2445_OPTIONAL,
  580. 'TZNAME' => RFC2445_OPTIONAL,
  581. RFC2445_XNAME => RFC2445_OPTIONAL,
  582. );
  583. parent::__construct();
  584. }
  585. }
  586. class iCalendar_daylight extends iCalendar_standard {
  587. var $name = 'DAYLIGHT';
  588. }
  589. // REMINDER: DTEND must be later than DTSTART for all components which support both
  590. // REMINDER: DUE must be later than DTSTART for all components which support both