5a4127d3177575686f98105ca79f626c3f179eeb
[moodle.git] / lib / bennu / iCalendar_components.php
1 <?php
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  */
16 class iCalendar_component {
17     var $name             = NULL;
18     var $properties       = NULL;
19     var $components       = NULL;
20     var $valid_properties = NULL;
21     var $valid_components = NULL;
23     function iCalendar_component() {
24         $this->construct();
25     }
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     }
37     function get_name() {
38         return $this->name;
39     }
41     function add_property($name, $value = NULL, $parameters = NULL) {
43         // Uppercase first of all
44         $name = strtoupper($name);
46         // Are we trying to add a valid property?
47         $xname = false;
48         if(!isset($this->valid_properties[$name])) {
49             // If not, is it an x-name as per RFC 2445?
50             if(!rfc2445_is_xname($name)) {
51                 return false;
52             }
53             // Since this is an xname, all components are supposed to allow this property
54             $xname = true;
55         }
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         }
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         }
75         // Set this property's parent component to ourselves, because some
76         // properties behave differently according to what component they apply to.
77         $property->set_parent_component($this->name);
79         // Set parameters before value; this helps with some properties which
80         // accept a VALUE parameter, and thus change their default value type.
82         // The parameters must be valid according to property specifications
83         if(!empty($parameters)) {
84             foreach($parameters as $paramname => $paramvalue) {
85                 if(!$property->set_parameter($paramname, $paramvalue)) {
86                     return false;
87                 }
88             }
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         }
97         // $value MUST be valid according to the property data type
98         if(!$property->set_value($value)) {
99             return false;
100         }
102         // If this property is restricted to only once, blindly overwrite value
103         if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE) {
104             $this->properties[$name] = array($property);
105         }
107         // Otherwise add it to the instance array for this property
108         else {
109             $this->properties[$name][] = $property;
110         }
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         }
122         return true;        
123         
124     }
126     function add_component($component) {
128         // With the detailed interface, you can add only components with this function
129         if(!is_object($component) || !is_subclass_of($component, 'iCalendar_component')) {
130             return false;
131         }
133         $name = $component->get_name();
135         // Only valid components as specified by this component are allowed
136         if(!in_array($name, $this->valid_components)) {
137             return false;
138         }
140         // Add it
141         $this->components[$name][] = $component;
143         return true;
144     }
146     function get_property_list($name) {
147     }
149     function invariant_holds() {
150         return true;
151     }
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         }
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         }
178         return true;
179     }
180     
181     function serialize() {
182         // Check for validity of the object
183         if(!$this->is_valid()) {
184             return false;
185         }
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         }
195         // Start tag
196         $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF;
198         // List of properties
199         if(!empty($this->properties)) {
200             foreach($this->properties as $name => $properties) {
201                 foreach($properties as $property) {
202                     $string .= $property->serialize();
203                 }
204             }
205         }
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         }
216         // End tag
217         $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF;
219         return $string;
220     }
224 class iCalendar extends iCalendar_component {
225     var $name = 'VCALENDAR';
227     function construct() {
228         $this->valid_properties = array(
229             'CALSCALE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
230             'METHOD'      => RFC2445_OPTIONAL | RFC2445_ONCE,
231             'PRODID'      => RFC2445_REQUIRED | RFC2445_ONCE,
232             'VERSION'     => RFC2445_REQUIRED | RFC2445_ONCE,
233             RFC2445_XNAME => RFC2445_OPTIONAL 
234         );
236         $this->valid_components = array(
237             'VEVENT'
238             // TODO: add support for the other component types
239             //, 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
240         );
241         parent::construct();
242     }
246 class iCalendar_event extends iCalendar_component {
248     var $name       = 'VEVENT';
249     var $properties;
250     
251     function construct() {
252         
253         $this->valid_components = array('VALARM');
255         $this->valid_properties = array(
256             'CLASS'          => RFC2445_OPTIONAL | RFC2445_ONCE,
257             'CREATED'        => RFC2445_OPTIONAL | RFC2445_ONCE,
258             'DESCRIPTION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
259             // Standard ambiguous here: in 4.6.1 it says that DTSTAMP in optional,
260             // while in 4.8.7.2 it says it's REQUIRED. Go with REQUIRED.
261             'DTSTAMP'        => RFC2445_REQUIRED | RFC2445_ONCE,
262             // Standard ambiguous here: in 4.6.1 it says that DTSTART in optional,
263             // while in 4.8.2.4 it says it's REQUIRED. Go with REQUIRED.
264             'DTSTART'        => RFC2445_REQUIRED | RFC2445_ONCE,
265             'GEO'            => RFC2445_OPTIONAL | RFC2445_ONCE,
266             'LAST-MODIFIED'  => RFC2445_OPTIONAL | RFC2445_ONCE,
267             'LOCATION'       => RFC2445_OPTIONAL | RFC2445_ONCE,
268             'ORGANIZER'      => RFC2445_OPTIONAL | RFC2445_ONCE,
269             'PRIORITY'       => RFC2445_OPTIONAL | RFC2445_ONCE,
270             'SEQUENCE'       => RFC2445_OPTIONAL | RFC2445_ONCE,
271             'STATUS'         => RFC2445_OPTIONAL | RFC2445_ONCE,
272             'SUMMARY'        => RFC2445_OPTIONAL | RFC2445_ONCE,
273             'TRANSP'         => RFC2445_OPTIONAL | RFC2445_ONCE,
274             // Standard ambiguous here: in 4.6.1 it says that UID in optional,
275             // while in 4.8.4.7 it says it's REQUIRED. Go with REQUIRED.
276             'UID'            => RFC2445_REQUIRED | RFC2445_ONCE,
277             'URL'            => RFC2445_OPTIONAL | RFC2445_ONCE,
278             'RECURRENCE-ID'  => RFC2445_OPTIONAL | RFC2445_ONCE,
279             'DTEND'          => RFC2445_OPTIONAL | RFC2445_ONCE,
280             'DURATION'       => RFC2445_OPTIONAL | RFC2445_ONCE,
281             'ATTACH'         => RFC2445_OPTIONAL,
282             'ATTENDEE'       => RFC2445_OPTIONAL,
283             'CATEGORIES'     => RFC2445_OPTIONAL,
284             'COMMENT'        => RFC2445_OPTIONAL,
285             'CONTACT'        => RFC2445_OPTIONAL,
286             'EXDATE'         => RFC2445_OPTIONAL,
287             'EXRULE'         => RFC2445_OPTIONAL,
288             'REQUEST-STATUS' => RFC2445_OPTIONAL,
289             'RELATED-TO'     => RFC2445_OPTIONAL,
290             'RESOURCES'      => RFC2445_OPTIONAL,
291             'RDATE'          => RFC2445_OPTIONAL,
292             'RRULE'          => RFC2445_OPTIONAL,
293             RFC2445_XNAME    => RFC2445_OPTIONAL
294         );
296         parent::construct();
297     }
299     function invariant_holds() {
300         // DTEND and DURATION must not appear together
301         if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
302             return false;
303         }
305         
306         if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
307             // DTEND must be later than DTSTART
308             // The standard is not clear on how to hande different value types though
309             // TODO: handle this correctly even if the value types are different
310             if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
311                 return false;
312             }
314             // DTEND and DTSTART must have the same value type
315             if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
316                 return false;
317             }
319         }
320         return true;
321     }
325 class iCalendar_todo extends iCalendar_component {
326     var $name       = 'VTODO';
327     var $properties;
329     function construct() {
331         $this->properties = array(
332             'class'       => RFC2445_OPTIONAL | RFC2445_ONCE,
333             'completed'   => RFC2445_OPTIONAL | RFC2445_ONCE,
334             'created'     => RFC2445_OPTIONAL | RFC2445_ONCE,
335             'description' => RFC2445_OPTIONAL | RFC2445_ONCE,
336             'dtstamp'     => RFC2445_OPTIONAL | RFC2445_ONCE,
337             'dtstart'     => RFC2445_OPTIONAL | RFC2445_ONCE,
338             'geo'         => RFC2445_OPTIONAL | RFC2445_ONCE,
339             'last-modified'    => RFC2445_OPTIONAL | RFC2445_ONCE,
340             'location'    => RFC2445_OPTIONAL | RFC2445_ONCE,
341             'organizer'   => RFC2445_OPTIONAL | RFC2445_ONCE,
342             'percent'     => RFC2445_OPTIONAL | RFC2445_ONCE,
343             'priority'    => RFC2445_OPTIONAL | RFC2445_ONCE,
344             'recurid'     => RFC2445_OPTIONAL | RFC2445_ONCE,
345             'sequence'    => RFC2445_OPTIONAL | RFC2445_ONCE,
346             'status'      => RFC2445_OPTIONAL | RFC2445_ONCE,
347             'summary'     => RFC2445_OPTIONAL | RFC2445_ONCE,
348             'uid'         => RFC2445_OPTIONAL | RFC2445_ONCE,
349             'url'         => RFC2445_OPTIONAL | RFC2445_ONCE,
350             'due'         => RFC2445_OPTIONAL | RFC2445_ONCE,
351             'duration'    => RFC2445_OPTIONAL | RFC2445_ONCE,
352             'attach'      => RFC2445_OPTIONAL,
353             'attendee'    => RFC2445_OPTIONAL,
354             'categories'  => RFC2445_OPTIONAL,
355             'comment'     => RFC2445_OPTIONAL,
356             'contact'     => RFC2445_OPTIONAL,
357             'exdate'      => RFC2445_OPTIONAL,
358             'exrule'      => RFC2445_OPTIONAL,
359             'rstatus'     => RFC2445_OPTIONAL,
360             'related'     => RFC2445_OPTIONAL,
361             'resources'   => RFC2445_OPTIONAL,
362             'rdate'       => RFC2445_OPTIONAL,
363             'rrule'       => RFC2445_OPTIONAL,
364             'xprop'       => RFC2445_OPTIONAL
365         );
367         parent::construct();
368         // TODO:
369         // either 'due' or 'duration' may appear in  a 'eventprop', but 'due'
370         // and 'duration' MUST NOT occur in the same 'eventprop'
371     }
374 class iCalendar_journal extends iCalendar_component {
375     // TODO: implement
378 class iCalendar_freebusy extends iCalendar_component {
379     // TODO: implement
382 class iCalendar_alarm extends iCalendar_component {
383     // TODO: implement
386 class iCalendar_timezone extends iCalendar_component {
387     var $name       = 'VTIMEZONE';
388     var $properties;
390     function construct() {
392         $this->properties = array(
393             'tzid'        => RFC2445_REQUIRED | RFC2445_ONCE,
394             'last-modified'    => RFC2445_OPTIONAL | RFC2445_ONCE,
395             'tzurl'       => RFC2445_OPTIONAL | RFC2445_ONCE,
396             // TODO: the next two are components of their own!
397             'standardc'   => RFC2445_REQUIRED,
398             'daylightc'   => RFC2445_REQUIRED,
399             'x-prop'      => RFC2445_OPTIONAL
400         );
401         
402         parent::construct();
403     }
407 // REMINDER: DTEND must be later than DTSTART for all components which support both
408 // REMINDER: DUE must be later than DTSTART for all components which support both