commit 3537d9bedf5f5c8c39937d4f9a72e0cca5f84583
Author: Mark Johnson <mark@barrenfrozenwasteland.com>
Date:   Tue May 4 10:34:16 2010 +0100

    Added latest version of bennu from SVN and calendar import interface.

diff --git a/calendar/calendar_import_form.php b/calendar/calendar_import_form.php
new file mode 100644
index 0000000..e2b9ec0
--- /dev/null
+++ b/calendar/calendar_import_form.php
@@ -0,0 +1,49 @@
+<?php
+require_once '../config.php';
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->dirroot.'/calendar/lib.php');
+
+class calendar_import_form extends moodleform {
+    
+    /**
+     * Defines the form elements
+     */
+    function definition() {
+        $mform    =& $this->_form;
+        $mform->addElement('header', 'importheader', 'Select iCal file to import:');
+        $mform->addElement('file', 'importfile', get_string('importcalendar', 'calendar'));
+        $mform->addElement('hidden', 'courseid');
+
+        $mform->addElement('submit', 'preview', 'Preview Import');
+    }
+}
+
+class calendar_import_confirm_form extends moodleform {
+
+    /**
+     * Defines the form elements
+     */
+    function definition() {
+        global $allowed, $courseid;
+        $mform    =& $this->_form;
+        $mform->addElement('header', 'confirmheader', 'Import these events as:');
+        $mform->addElement('hidden', 'calendar');
+        $mform->addElement('hidden', 'courseid');
+        $radio = array();
+
+        if($allowed->site) {
+            $radio[] = &MoodleQuickForm::createElement('radio', 'eventtype', '', get_string('globalevents', 'calendar'), 1);
+        }
+        if($allowed->courses) {
+            $radio[] = &MoodleQuickForm::createElement('radio', 'eventtype', '', get_string('courseevents', 'calendar'), $courseid);
+        }
+        if($allowed->user){
+            $radio[] = &MoodleQuickForm::createElement('radio', 'eventtype', '', get_string('userevents', 'calendar'), 0);
+        }
+        $mform->addGroup($radio, 'eventtypes', get_string('eventkind', 'calendar'));
+
+        $mform->addElement('submit', 'import', 'Import Events');
+    }
+}
+
+?>
diff --git a/calendar/import.php b/calendar/import.php
new file mode 100644
index 0000000..f2ca5ed
--- /dev/null
+++ b/calendar/import.php
@@ -0,0 +1,149 @@
+<?php
+
+require_once('../config.php');
+require_once($CFG->libdir.'/bennu/bennu.inc.php');
+require_once($CFG->libdir.'/tablelib.php');
+require_once($CFG->dirroot.'/calendar/calendar_import_form.php');
+require_once($CFG->dirroot.'/calendar/lib.php');
+
+$courseid = required_param('courseid', PARAM_INT);
+$course = get_record('course', 'id', $courseid);
+
+if ($courseid && $courseid != SITEID) {
+    require_login($courseid);
+} else if ($CFG->forcelogin) {
+    require_login();
+}
+
+
+$now = usergetdate(time());
+$navlinks = array();
+$navlinks[] = array('name' => get_string('calendar', 'calendar'),
+                    'link' =>calendar_get_link_href(CALENDAR_URL.'view.php?view=upcoming&amp;course='.$courseid.'&amp;', $now['mday'], $now['mon'], $now['year']),
+                    'type' => 'misc');
+$navlinks[] = array('name' => get_string('importcalendar', 'calendar'), 'link' =>'', 'type' => 'misc');
+
+$navigation = build_navigation($navlinks);
+
+print_header_simple(get_string('importcalendar', 'calendar'), '', $navigation);
+
+echo '<h1>'.get_string('importcalendar', 'calendar').'</h1>';
+
+$mform = new calendar_import_form();
+$data = new stdClass;
+$data->courseid = $courseid;
+$mform->set_data($data);
+
+$allowed = new stdClass;
+calendar_get_allowed_types($allowed);
+$importform = new calendar_import_confirm_form();
+if($data = $importform->get_data()) {
+    
+    $ical = new iCalendar;
+    $ical->unserialize($data->calendar);
+    $eventcount = 0;
+    $updatecount = 0;
+
+    foreach($ical->components['VEVENT'] as $event) {
+        $eventrecord = new stdClass;
+
+        $name = $event->properties['SUMMARY'][0]->value;
+        $name = str_replace('\n', '<br />', $name);
+        $name = str_replace('\\', '', $name);
+        $name = preg_replace('/\s+/', ' ', $name);
+        $eventrecord->name = clean_param($name, PARAM_CLEAN);
+
+        $description = $event->properties['DESCRIPTION'][0]->value;
+        $description = str_replace('\n', '<br />', $description);
+        $description = str_replace('\\', '', $description);
+        $description = preg_replace('/\s+/', ' ', $description);
+        $eventrecord->description = clean_param($description, PARAM_CLEAN);
+
+        $eventrecord->courseid = $data->eventtypes['eventtype'];
+        $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value);
+        $eventrecord->timeduration = strtotime($event->properties['DTEND'][0]->value) - $eventrecord->timestart;
+        $eventrecord->uuid = substr($event->properties['UID'][0]->value, 0, 36); // The UUID field only holds 36 characters.
+        $eventrecord->userid = $USER->id;
+        $eventrecord->timemodified = time();
+        
+        if($updaterecord = get_record('event', 'uuid', $eventrecord->uuid)) {
+            $eventrecord->id = $updaterecord->id;
+            if (update_record('event', $eventrecord)) {
+                $updatecount++;
+            } else {
+                echo '<p>Failed to update event: '.$eventrecord->name.' '.date('H:i d/m/Y', $eventrecord->timestart).'</p>';
+            }
+        } else {
+            if (insert_record('event', $eventrecord)) {
+                $eventcount++;
+            } else {
+                echo '<p>Failed to add event: '.$eventrecord->name.' '.date('H:i d/m/Y', $eventrecord->timestart).'</p>';
+            }
+        }
+    }
+    echo '<p>'.$eventcount.' events imported successfully.</p>';
+    echo '<p>'.$updatecount.' events updated.</p>';
+    echo '<p><a href="'.calendar_get_link_href(CALENDAR_URL.'view.php?view=upcoming&amp;course='.$courseid.'&amp;', $now['mday'], $now['mon'], $now['year']).'">Back to Calendar.</a></p>';
+
+} else {
+    if ($formdata = $mform->get_data()) {
+        $calendar = file_get_contents($_FILES['importfile']['tmp_name']);
+        $ical = new iCalendar;
+        $ical->unserialize($calendar);
+
+        echo '<p>';
+        foreach ($ical->parser_errors as $error) {
+            echo $error.'<br />';
+        }
+        echo '</p>';
+
+        $table = new flexible_table('ical_import');
+        $columns = array('summary', 'description', 'start', 'duration', 'uid');
+        $headers = array('Summary', 'Description', 'Start', 'Duration', 'UUID');
+        $table->define_columns($columns);
+        $table->define_headers($headers);
+        $table->setup();
+        $count = 0;
+
+        foreach($ical->components['VEVENT'] as $event) {
+            if($count < 20) {
+                $mevent = new stdClass;
+                $mevent->name = $event->properties['SUMMARY'][0]->value;
+                $mevent->description = $event->properties['DESCRIPTION'][0]->value;
+                $mevent->timestart = strtotime($event->properties['DTSTART'][0]->value);
+                $mevent->duration = strtotime($event->properties['DTEND'][0]->value) - $mevent->timestart;
+                $mevent->uuid = $event->properties['UID'][0]->value;
+                $mevent->timemodified = time();
+
+                    $row = array();
+                $row[] = $mevent->name;
+                $row[] = $mevent->description;
+                $row[] = date('d/m/Y H:i', $mevent->timestart);
+                $row[] = date('H:i', $mevent->duration);
+                $row[] = $mevent->uuid;
+                $table->add_data($row);
+            }
+            $count++;
+
+        }
+        echo '<h2>Import Preview</h2>';
+        $table->print_html();
+        if($count > 20) {
+            echo ($count-20).' more...';
+        }
+        $toform = new stdClass();
+        $toform->courseid = $courseid;
+        $toform->calendar = $calendar;
+        $importform->set_data($toform);
+        $importform->display();
+
+    }
+    $mform->display();
+
+}
+
+print_footer();
+
+
+?>
+
diff --git a/calendar/view.php b/calendar/view.php
index b3245b1..eb43430 100644
--- a/calendar/view.php
+++ b/calendar/view.php
@@ -179,7 +179,8 @@
                  .'</a>';
         }
     }
-
+    print_single_button('import.php', array('courseid'=>$courseid), get_string('importcalendar', 'calendar'));
+    
     echo '</div>';
     echo '</div>';
     echo '</td>';
diff --git a/lib/bennu/bennu.class.php b/lib/bennu/bennu.class.php
index 40435c7..7f8e81a 100644
--- a/lib/bennu/bennu.class.php
+++ b/lib/bennu/bennu.class.php
@@ -1,59 +1,59 @@
-<?php // $Id$
-
-/**
- *  BENNU - PHP iCalendar library
- *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
- *
- *  Released under the LGPL.
- *
- *  See http://bennu.sourceforge.net/ for more information and downloads.
- *
- * @author Ioannis Papaioannou 
- * @version $Id$
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
-class Bennu {
-    function timestamp_to_datetime($t = NULL) {
-        if($t === NULL) {
-            $t = time();
-        }
-        return gmstrftime('%Y%m%dT%H%M%SZ', $t);
-    }
-
-    function generate_guid() {
-        // Implemented as per the Network Working Group draft on UUIDs and GUIDs
-    
-        // These two octets get special treatment
-        $time_hi_and_version       = sprintf('%02x', (1 << 6) + mt_rand(0, 15)); // 0100 plus 4 random bits
-        $clock_seq_hi_and_reserved = sprintf('%02x', (1 << 7) + mt_rand(0, 63)); // 10 plus 6 random bits
-    
-        // Need another 14 random octects
-        $pool = '';
-        for($i = 0; $i < 7; ++$i) {
-            $pool .= sprintf('%04x', mt_rand(0, 65535));
-        }
-    
-        // time_low = 4 octets
-        $random  = substr($pool, 0, 8).'-';
-    
-        // time_mid = 2 octets
-        $random .= substr($pool, 8, 4).'-';
-    
-        // time_high_and_version = 2 octets
-        $random .= $time_hi_and_version.substr($pool, 12, 2).'-';
-    
-        // clock_seq_high_and_reserved = 1 octet
-        $random .= $clock_seq_hi_and_reserved;
-    
-        // clock_seq_low = 1 octet
-        $random .= substr($pool, 13, 2).'-';
-    
-        // node = 6 octets
-        $random .= substr($pool, 14, 12);
-    
-        return $random;
-    }
-}
-
-?>
+<?php // $Id: bennu.class.php,v 1.2 2006/01/13 14:10:43 defacer Exp $
+
+/**
+ *  BENNU - PHP iCalendar library
+ *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ *  Released under the LGPL.
+ *
+ *  See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou 
+ * @version $Id: bennu.class.php,v 1.2 2006/01/13 14:10:43 defacer Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class Bennu {
+    function timestamp_to_datetime($t = NULL) {
+        if($t === NULL) {
+            $t = time();
+        }
+        return gmstrftime('%Y%m%dT%H%M%SZ', $t);
+    }
+
+    function generate_guid() {
+        // Implemented as per the Network Working Group draft on UUIDs and GUIDs
+    
+        // These two octets get special treatment
+        $time_hi_and_version       = sprintf('%02x', (1 << 6) + mt_rand(0, 15)); // 0100 plus 4 random bits
+        $clock_seq_hi_and_reserved = sprintf('%02x', (1 << 7) + mt_rand(0, 63)); // 10 plus 6 random bits
+    
+        // Need another 14 random octects
+        $pool = '';
+        for($i = 0; $i < 7; ++$i) {
+            $pool .= sprintf('%04x', mt_rand(0, 65535));
+        }
+    
+        // time_low = 4 octets
+        $random  = substr($pool, 0, 8).'-';
+    
+        // time_mid = 2 octets
+        $random .= substr($pool, 8, 4).'-';
+    
+        // time_high_and_version = 2 octets
+        $random .= $time_hi_and_version.substr($pool, 12, 2).'-';
+    
+        // clock_seq_high_and_reserved = 1 octet
+        $random .= $clock_seq_hi_and_reserved;
+    
+        // clock_seq_low = 1 octet
+        $random .= substr($pool, 13, 2).'-';
+    
+        // node = 6 octets
+        $random .= substr($pool, 14, 12);
+    
+        return $random;
+    }
+}
+
+?>
diff --git a/lib/bennu/bennu.inc.php b/lib/bennu/bennu.inc.php
index 53ea680..1f9a974 100644
--- a/lib/bennu/bennu.inc.php
+++ b/lib/bennu/bennu.inc.php
@@ -1,25 +1,25 @@
-<?php // $Id$
-
-/**
- *  BENNU - PHP iCalendar library
- *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
- *
- *  Released under the LGPL.
- *
- *  See http://bennu.sourceforge.net/ for more information and downloads.
- *
- * @author Ioannis Papaioannou 
- * @version $Id$
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
-if(!defined('_BENNU_VERSION')) {
-    define('_BENNU_VERSION', '0.1');
-    include('bennu.class.php');
-    include('iCalendar_rfc2445.php');
-    include('iCalendar_components.php');
-    include('iCalendar_properties.php');
-    include('iCalendar_parameters.php');
-}
-
-?>
+<?php // $Id: bennu.inc.php,v 1.5 2006/01/13 14:01:32 defacer Exp $
+
+/**
+ *  BENNU - PHP iCalendar library
+ *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ *  Released under the LGPL.
+ *
+ *  See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou 
+ * @version $Id: bennu.inc.php,v 1.5 2006/01/13 14:01:32 defacer Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+if(!defined('_BENNU_VERSION')) {
+    define('_BENNU_VERSION', '0.1');
+    include('bennu.class.php');
+    include('iCalendar_rfc2445.php');
+    include('iCalendar_components.php');
+    include('iCalendar_properties.php');
+    include('iCalendar_parameters.php');
+}
+
+?>
diff --git a/lib/bennu/iCalendar_components.php b/lib/bennu/iCalendar_components.php
index 20ed339..386f30e 100644
--- a/lib/bennu/iCalendar_components.php
+++ b/lib/bennu/iCalendar_components.php
@@ -1,4 +1,4 @@
-<?php // $Id$
+<?php
 
 /**
  *  BENNU - PHP iCalendar library
@@ -9,7 +9,6 @@
  *  See http://bennu.sourceforge.net/ for more information and downloads.
  *
  * @author Ioannis Papaioannou 
- * @version $Id$
  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  */
 
@@ -19,6 +18,11 @@ class iCalendar_component {
     var $components       = NULL;
     var $valid_properties = NULL;
     var $valid_components = NULL;
+    /**
+     * Added to hold errors from last run of unserialize
+     * @var $parser_errors array
+     */
+    var $parser_errors = NULL;
 
     function iCalendar_component() {
         $this->construct();
@@ -99,13 +103,13 @@ class iCalendar_component {
             return false;
         }
 
-        // If this property is restricted to only once, blindly overwrite value
-        if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE) {
-            $this->properties[$name] = array($property);
-        }
-
-        // Otherwise add it to the instance array for this property
-        else {
+        // Check if the property already exists, and is limited to one occurrance,
+        // DON'T overwrite the value - this can be done explicity with set_value() instead.
+        if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE && isset($this->properties[$name])) {
+            return false;
+        } 
+		else {
+             // Otherwise add it to the instance array for this property
             $this->properties[$name][] = $property;
         }
 
@@ -218,6 +222,122 @@ class iCalendar_component {
 
         return $string;
     }
+    
+    /**
+    * unserialize()
+    *
+    * I needed a way to convert an iCalendar component back to a Bennu object so I could
+    * easily access and modify it after it had been stored; if this functionality is already
+    * present somewhere in the library, I apologize for adding it here unnecessarily; however,
+    * I couldn't find it so I added it myself.
+    * @param string $string the iCalendar object to load in to this iCalendar_component
+    * @return bool true if the file parsed with no errors. False if there were errors.
+    */
+    
+    function unserialize($string) {
+        $string = rfc2445_unfold($string); // Unfold any long lines
+        $lines = explode(RFC2445_CRLF, $string); // Create an array of lines
+        
+        $components = array(); // Initialise a stack of components
+        $this->clear_errors();
+        foreach ($lines as $key => $line) {
+            // Divide the line up into label, parameters and data fields.
+
+            $separator_index = strpos($line, RFC2445_VALUE_SEPARATOR);
+            
+            if($separator_index === false) { // Check that there's a colon in the line, otherwise it's invalid.
+                $this->parser_error('Invalid line: '.$key.', ignoring');
+                continue;
+            }
+
+            $name_and_parameters = substr($line, 0, $separator_index);
+            $data = substr($line, $separator_index + 1);
+
+            $params = array();
+            if (strpos($name_and_parameters, RFC2445_PARAMETER_SEPARATOR)) {
+                $fields = explode(RFC2445_PARAMETER_SEPARATOR, $name_and_parameters);
+                $label = array_shift($fields); // Everything before the first ; is the label.
+
+                if(count($fields) > 0) {                    
+                    foreach($fields as $field) { // Divide every parameter into a key => value pair and store them in an array
+                        list($parameter_name, $parameter_value) = explode('=', $field, 2);
+                        $params[$parameter_name] = $parameter_value;
+                    }
+                }
+                
+            }
+            else {
+                // If there's no semicolon, the remainder of the line is the label.
+                $label = $name_and_parameters;
+            }
+
+            unset($fields);
+
+            if ($label == 'BEGIN') {
+                // This is the start of a component.
+                $current_component = array_pop($components); // Get the current component off the stack so we can check its valid components
+                if ($current_component == null) { // If there's nothing on the stack
+                    $current_component = $this; // use the iCalendar
+                }
+                if (in_array($data, $current_component->valid_components)) { // Check that the new component is a valid subcomponent of the current one
+                    if($current_component != $this) {
+                        array_push($components, $current_component); // We're done with the current component, put it back on the stack.
+                    }
+                    if(strpos($data, 'V') === 0) {
+                        $data = substr($data, 1);
+                    }
+                    $cname = 'iCalendar_' . strtolower($data);
+                    $new_component = new $cname;
+                    array_push($components, $new_component); // Push a new component onto the stack
+                } else {
+                    if($current_component != $this) {
+                        array_push($components, $current_component);
+                        $this->parser_error('Invalid component type on line '.$key);
+                    }                        
+                }
+                unset($current_component, $new_component);
+            } else if ($label == 'END') {
+                // It's the END of a component.
+                $component = array_pop($components); // Pop the top component off the stack - we're now done with it
+                $parent_component = array_pop($components); // Pop the component's conatining component off the stack so we can add this component to it.
+                if($parent_component == null) {
+                    $parent_component = $this; // If there's no components on the stack, use the iCalendar object
+                }
+                if ($parent_component->add_component($component) === false) {
+                    $this->parser_error('Failed to add component on line '.$key);
+                }
+                if ($parent_component != $this) { // If we're not using the iCalendar
+                        array_push($components, $parent_component); // Put the component back on the stack
+                }
+                unset($parent_component, $component);
+            } else {
+                
+                $component = array_pop($components); // Get the component off the stack so we can add properties to it
+                if ($component == null) { // If there's nothing on the stack
+                    $component = $this; // use the iCalendar
+                }
+
+                if ($component->add_property($label, $data, $params) === false) {
+                    $this->parser_error('Failed to add property on line '.$key);
+                }
+
+                if($component != $this) { // If we're not using the iCalendar
+                    array_push($components, $component); // Put the component back on the stack
+                }
+                unset($component);
+            }
+
+        }
+        
+    }
+
+    function clear_errors() {
+        $this->parser_errors = array();
+    }
+
+    function parser_error($error) {
+        $this->parser_errors[] = $error;
+    }
 
 }
 
@@ -234,9 +354,7 @@ class iCalendar extends iCalendar_component {
         );
 
         $this->valid_components = array(
-            'VEVENT'
-            // TODO: add support for the other component types
-            //, 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
+            'VEVENT', 'VTODO', 'VJOURNAL', 'VFREEBUSY', 'VTIMEZONE', 'VALARM'
         );
         parent::construct();
     }
@@ -327,60 +445,225 @@ class iCalendar_todo extends iCalendar_component {
     var $properties;
 
     function construct() {
+        
+        $this->valid_components = array('VALARM');
 
-        $this->properties = array(
-            'class'       => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'completed'   => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'created'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'description' => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'dtstamp'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'dtstart'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'geo'         => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'last-modified'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'location'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'organizer'   => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'percent'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'priority'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'recurid'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'sequence'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'status'      => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'summary'     => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'uid'         => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'url'         => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'due'         => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'duration'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'attach'      => RFC2445_OPTIONAL,
-            'attendee'    => RFC2445_OPTIONAL,
-            'categories'  => RFC2445_OPTIONAL,
-            'comment'     => RFC2445_OPTIONAL,
-            'contact'     => RFC2445_OPTIONAL,
-            'exdate'      => RFC2445_OPTIONAL,
-            'exrule'      => RFC2445_OPTIONAL,
-            'rstatus'     => RFC2445_OPTIONAL,
-            'related'     => RFC2445_OPTIONAL,
-            'resources'   => RFC2445_OPTIONAL,
-            'rdate'       => RFC2445_OPTIONAL,
-            'rrule'       => RFC2445_OPTIONAL,
-            'xprop'       => RFC2445_OPTIONAL
+        $this->valid_properties = array(
+            'CLASS'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'COMPLETED'   => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'CREATED'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DESCRIPTION' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTAMP'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTAP'      => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'GEO'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'LOCATION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ORGANIZER'   => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'PERCENT'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'PRIORITY'    => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'RECURID'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'SEQUENCE'    => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'STATUS'      => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'SUMMARY'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'UID'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'URL'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DUE'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DURATION'    => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ATTACH'      => RFC2445_OPTIONAL,
+            'ATTENDEE'    => RFC2445_OPTIONAL,
+            'CATEGORIES'  => RFC2445_OPTIONAL,
+            'COMMENT'     => RFC2445_OPTIONAL,
+            'CONTACT'     => RFC2445_OPTIONAL,
+            'EXDATE'      => RFC2445_OPTIONAL,
+            'EXRULE'      => RFC2445_OPTIONAL,
+            'RSTATUS'     => RFC2445_OPTIONAL,
+            'RELATED'     => RFC2445_OPTIONAL,
+            'RESOURCES'   => RFC2445_OPTIONAL,
+            'RDATE'       => RFC2445_OPTIONAL,
+            'RRULE'       => RFC2445_OPTIONAL,
+            RFC2445_XNAME => RFC2445_OPTIONAL
         );
 
         parent::construct();
-        // TODO:
-        // either 'due' or 'duration' may appear in  a 'eventprop', but 'due'
-        // and 'duration' MUST NOT occur in the same 'eventprop'
     }
+    
+    function invariant_holds() {
+        // DTEND and DURATION must not appear together
+        if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
+            return false;
+        }
+
+        
+        if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
+            // DTEND must be later than DTSTART
+            // The standard is not clear on how to hande different value types though
+            // TODO: handle this correctly even if the value types are different
+            if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
+                return false;
+            }
+
+            // DTEND and DTSTART must have the same value type
+            if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
+                return false;
+            }
+
+        }
+        
+        if(isset($this->properties['DUE']) && isset($this->properties['DTSTART'])) {
+            if($this->properties['DUE'][0]->value <= $this->properties['DTSTART'][0]->value) {
+                return false;
+            }   
+        }
+        
+        return true;
+    }
+    
 }
 
 class iCalendar_journal extends iCalendar_component {
-    // TODO: implement
+    var $name = 'VJOURNAL';
+    var $properties;
+    
+    function construct() {
+    	
+        $this->valid_properties = array(
+            'CLASS'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'CREATED'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DESCRIPTION'   => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTART'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTAMP'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'LAST-MODIFIED' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ORGANIZER'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'RECURRANCE-ID' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'SEQUENCE'      => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'STATUS'        => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'SUMMARY'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'UID'           => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'URL'           => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ATTACH'        => RFC2445_OPTIONAL,
+            'ATTENDEE'      => RFC2445_OPTIONAL,
+            'CATEGORIES'    => RFC2445_OPTIONAL,
+            'COMMENT'       => RFC2445_OPTIONAL,
+            'CONTACT'       => RFC2445_OPTIONAL,
+            'EXDATE'        => RFC2445_OPTIONAL,
+            'EXRULE'        => RFC2445_OPTIONAL,
+            'RELATED-TO'    => RFC2445_OPTIONAL,
+            'RDATE'         => RFC2445_OPTIONAL,
+            'RRULE'         => RFC2445_OPTIONAL,
+            RFC2445_XNAME   => RFC2445_OPTIONAL            
+        );
+        
+         parent::construct();
+        
+    }
 }
 
 class iCalendar_freebusy extends iCalendar_component {
-    // TODO: implement
+    var $name       = 'VFREEBUSY';
+    var $properties;
+
+    function construct() {
+        $this->valid_components = array();
+        $this->valid_properties = array(
+            'CONTACT'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTART'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTEND'         => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DURATION'      => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'DTSTAMP'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ORGANIZER'     => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'UID'           => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'URL'           => RFC2445_OPTIONAL | RFC2445_ONCE,
+            // TODO: the next two are components of their own!
+            'ATTENDEE'      => RFC2445_OPTIONAL,
+            'COMMENT'       => RFC2445_OPTIONAL,
+            'FREEBUSY'      => RFC2445_OPTIONAL,
+            'RSTATUS'       => RFC2445_OPTIONAL,
+            RFC2445_XNAME   => RFC2445_OPTIONAL
+        );
+        
+        parent::construct();
+    }
+    
+    function invariant_holds() {
+        // DTEND and DURATION must not appear together
+        if(isset($this->properties['DTEND']) && isset($this->properties['DURATION'])) {
+            return false;
+        }
+
+        
+        if(isset($this->properties['DTEND']) && isset($this->properties['DTSTART'])) {
+            // DTEND must be later than DTSTART
+            // The standard is not clear on how to hande different value types though
+            // TODO: handle this correctly even if the value types are different
+            if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
+                return false;
+            }
+
+            // DTEND and DTSTART must have the same value type
+            if($this->properties['DTEND'][0]->val_type != $this->properties['DTSTART'][0]->val_type) {
+                return false;
+            }
+
+        }
+        return true;
+    }
 }
 
 class iCalendar_alarm extends iCalendar_component {
-    // TODO: implement
+    var $name       = 'VALARM';
+    var $properties;
+
+    function construct() {
+        $this->valid_components = array();
+        $this->valid_properties = array(
+            'ACTION'    => RFC2445_REQUIRED | RFC2445_ONCE,
+            'TRIGGER'   => RFC2445_REQUIRED | RFC2445_ONCE,
+            // If one of these 2 occurs, so must the other.
+            'DURATION'  => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'REPEAT'    => RFC2445_OPTIONAL | RFC2445_ONCE, 
+            // The following is required if action == "PROCEDURE" | "AUDIO"           
+            'ATTACH'    => RFC2445_OPTIONAL,
+            // The following is required if trigger == "EMAIL" | "DISPLAY" 
+            'DESCRIPTION'  => RFC2445_OPTIONAL | RFC2445_ONCE,
+            // The following are required if action == "EMAIL"
+            'SUMMARY'   => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'ATTENDEE'  => RFC2445_OPTIONAL,
+            RFC2445_XNAME   => RFC2445_OPTIONAL
+        );
+     
+        parent::construct();
+    }
+        
+    function invariant_holds() {
+        // DTEND and DURATION must not appear together
+        if(isset($this->properties['ACTION'])) {
+            switch ($this->properties['ACTION'][0]->value) {
+            	case 'AUDIO':
+                    if (!isset($this->properties['ATTACH'])) {
+                    	return false;
+                    }
+                    break;
+                case 'DISPLAY':
+                    if (!isset($this->properties['DESCRIPTION'])) {
+                    	return false;
+                    }
+                    break;
+                case 'EMAIL':
+                    if (!isset($this->properties['DESCRIPTION']) || !isset($this->properties['SUMMARY']) || !isset($this->properties['ATTACH'])) {
+                        return false;
+                    }
+                    break;
+                case 'PROCEDURE':
+                    if (!isset($this->properties['ATTACH']) || count($this->properties['ATTACH']) > 1) {
+                    	return false;
+                    }
+                    break;
+            }
+        }
+        return true;
+    }
+        
+        
 }
 
 class iCalendar_timezone extends iCalendar_component {
@@ -389,14 +672,13 @@ class iCalendar_timezone extends iCalendar_component {
 
     function construct() {
 
-        $this->properties = array(
-            'tzid'        => RFC2445_REQUIRED | RFC2445_ONCE,
-            'last-modified'    => RFC2445_OPTIONAL | RFC2445_ONCE,
-            'tzurl'       => RFC2445_OPTIONAL | RFC2445_ONCE,
-            // TODO: the next two are components of their own!
-            'standardc'   => RFC2445_REQUIRED,
-            'daylightc'   => RFC2445_REQUIRED,
-            'x-prop'      => RFC2445_OPTIONAL
+        $this->valid_components = array('STANDARD', 'DAYLIGHT');
+
+        $this->valid_properties = array(
+            'TZID'        => RFC2445_REQUIRED | RFC2445_ONCE,
+            'LAST-MODIFIED'    => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'TZURL'       => RFC2445_OPTIONAL | RFC2445_ONCE,
+            RFC2445_XNAME => RFC2445_OPTIONAL
         );
         
         parent::construct();
@@ -404,7 +686,31 @@ class iCalendar_timezone extends iCalendar_component {
 
 }
 
+class iCalendar_standard extends iCalendar_component {
+    var $name       = 'STANDARD';
+    var $properties;
+    
+    function construct() {
+        $this->valid_components = array();
+        $this->valid_properties = array(
+            'DTSTART'   =>  RFC2445_REQUIRED | RFC2445_ONCE,
+            'TZOFFSETTO'    =>  RFC2445_REQUIRED | RFC2445_ONCE,
+            'TZOFFSETFROM'  =>  RFC2445_REQUIRED | RFC2445_ONCE,
+            'COMMENT'   =>  RFC2445_OPTIONAL,
+            'RDATE'   =>  RFC2445_OPTIONAL,
+            'RRULE'   =>  RFC2445_OPTIONAL,
+            'TZNAME'   =>  RFC2445_OPTIONAL,
+            RFC2445_XNAME   =>  RFC2445_OPTIONAL,
+        ); 
+        parent::construct();  
+    }
+}
+
+class iCalendar_daylight extends iCalendar_standard {
+    var $name   =   'DAYLIGHT';
+}
+
 // REMINDER: DTEND must be later than DTSTART for all components which support both
 // REMINDER: DUE must be later than DTSTART for all components which support both
 
-?>
\ No newline at end of file
+?>
diff --git a/lib/bennu/iCalendar_parameters.php b/lib/bennu/iCalendar_parameters.php
index 71b3042..1f7ed92 100644
--- a/lib/bennu/iCalendar_parameters.php
+++ b/lib/bennu/iCalendar_parameters.php
@@ -1,240 +1,240 @@
-<?php // $Id$
-
-/**
- *  BENNU - PHP iCalendar library
- *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
- *
- *  Released under the LGPL.
- *
- *  See http://bennu.sourceforge.net/ for more information and downloads.
- *
- * @author Ioannis Papaioannou 
- * @version $Id$
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
-class iCalendar_parameter {
-    function multiple_values_allowed($parameter) {
-        switch($parameter) {
-            case 'DELEGATED-FROM':
-            case 'DELEGATED-TO':
-            case 'MEMBER':
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    function default_value($parameter) {
-        switch($parameter) {
-            case 'CUTYPE':   return 'INDIVIDUAL';
-            case 'FBTYPE':   return 'BUSY';
-            case 'PARTSTAT': return 'NEEDS-ACTION';
-            case 'RELATED':  return 'START';
-            case 'RELTYPE':  return 'PARENT';
-            case 'ROLE':     return 'REQ-PARTICIPANT';
-            case 'RSVP':     return 'FALSE';
-            default:         return NULL;
-        }
-    }
-
-    function is_valid_value(&$parent_property, $parameter, $value) {
-        switch($parameter) {
-            // These must all be a URI
-            case 'ALTREP':
-            case 'DIR':
-                return rfc2445_is_valid_value($value, RFC2445_TYPE_URI);
-            break;
-
-            // These must be CAL-ADDRESS, which is equivalent to URI
-            case 'DELEGATED-FROM':
-            case 'DELEGATED-TO':
-            case 'MEMBER':
-            case 'SENT-BY':
-                return rfc2445_is_valid_value($value, RFC2445_TYPE_CAL_ADDRESS);
-            break;
-
-            // These are textual parameters, so the MUST NOT contain double quotes
-            case 'CN':
-                return (strpos($value, '"') === false);
-            break;
-
-            // These have enumerated legal values
-            case 'CUTYPE':
-                $value = strtoupper($value);
-                return ($value == 'INDIVIDUAL' || $value == 'GROUP' || $value == 'RESOURCE' || $value == 'ROOM' || $value == 'UNKNOWN' || rfc2445_is_xname($value));
-            break;
-
-            case 'ENCODING':
-                $value = strtoupper($value);
-                return ($value == '8BIT' || $value == 'BASE64' || rfc2445_is_xname($value));
-            break;
-
-            case 'FBTYPE':
-                $value = strtoupper($value);
-                return ($value == 'FREE' || $value == 'BUSY' || $value == 'BUSY-UNAVAILABLE' || $value == 'BUSY-TENTATIVE' || rfc2445_is_xname($value));
-            break;
-
-            case 'FMTTYPE':
-                $fmttypes = array(
-                        'TEXT'        => array('PLAIN', 'RICHTEXT', 'ENRICHED', 'TAB-SEPARATED-VALUES', 'HTML', 'SGML',
-                                               'VND.LATEX-Z', 'VND.FMI.FLEXSTOR'),
-                        'MULTIPART'   => array('MIXED', 'ALTERNATIVE', 'DIGEST', 'PARALLEL', 'APPLEDOUBLE', 'HEADER-SET',
-                                               'FORM-DATA', 'RELATED', 'REPORT', 'VOICE-MESSAGE', 'SIGNED', 'ENCRYPTED',
-                                               'BYTERANGES'),
-                        'MESSAGE'     => array('RFC822', 'PARTIAL', 'EXTERNAL-BODY', 'NEWS', 'HTTP'),
-                        'APPLICATION' => array('OCTET-STREAM', 'POSTSCRIPT', 'ODA', 'ATOMICMAIL', 'ANDREW-INSET', 'SLATE',
-                                               'WITA', 'DEC-DX', 'DCA-RFT', 'ACTIVEMESSAGE', 'RTF', 'APPLEFILE',
-                                               'MAC-BINHEX40', 'NEWS-MESSAGE-ID', 'NEWS-TRANSMISSION', 'WORDPERFECT5.1',
-                                               'PDF', 'ZIP', 'MACWRITEII', 'MSWORD', 'REMOTE-PRINTING', 'MATHEMATICA',
-                                               'CYBERCASH', 'COMMONGROUND', 'IGES', 'RISCOS', 'ESHOP', 'X400-BP', 'SGML',
-                                               'CALS-1840', 'PGP-ENCRYPTED', 'PGP-SIGNATURE', 'PGP-KEYS', 'VND.FRAMEMAKER',
-                                               'VND.MIF', 'VND.MS-EXCEL', 'VND.MS-POWERPOINT', 'VND.MS-PROJECT',
-                                               'VND.MS-WORKS', 'VND.MS-TNEF', 'VND.SVD', 'VND.MUSIC-NIFF', 'VND.MS-ARTGALRY',
-                                               'VND.TRUEDOC', 'VND.KOAN', 'VND.STREET-STREAM', 'VND.FDF',
-                                               'SET-PAYMENT-INITIATION', 'SET-PAYMENT', 'SET-REGISTRATION-INITIATION',
-                                               'SET-REGISTRATION', 'VND.SEEMAIL', 'VND.BUSINESSOBJECTS',
-                                               'VND.MERIDIAN-SLINGSHOT', 'VND.XARA', 'SGML-OPEN-CATALOG', 'VND.RAPID',
-                                               'VND.ENLIVEN', 'VND.JAPANNET-REGISTRATION-WAKEUP', 
-                                               'VND.JAPANNET-VERIFICATION-WAKEUP', 'VND.JAPANNET-PAYMENT-WAKEUP',
-                                               'VND.JAPANNET-DIRECTORY-SERVICE', 'VND.INTERTRUST.DIGIBOX', 'VND.INTERTRUST.NNCP'),
-                        'IMAGE'       => array('JPEG', 'GIF', 'IEF', 'G3FAX', 'TIFF', 'CGM', 'NAPLPS', 'VND.DWG', 'VND.SVF',
-                                               'VND.DXF', 'PNG', 'VND.FPX', 'VND.NET-FPX'),
-                        'AUDIO'       => array('BASIC', '32KADPCM', 'VND.QCELP'),
-                        'VIDEO'       => array('MPEG', 'QUICKTIME', 'VND.VIVO', 'VND.MOTOROLA.VIDEO', 'VND.MOTOROLA.VIDEOP')
-                );
-                $value = strtoupper($value);
-                if(rfc2445_is_xname($value)) {
-                    return true;
-                }
-                @list($type, $subtype) = explode('/', $value);
-                if(empty($type) || empty($subtype)) {
-                    return false;
-                }
-                if(!isset($fmttypes[$type]) || !in_array($subtype, $fmttypes[$type])) {
-                    return false;
-                }
-                return true;
-            break;
-
-            case 'LANGUAGE':
-                $value = strtoupper($value);
-                $parts = explode('-', $value);
-                foreach($parts as $part) {
-                    if(empty($part)) {
-                        return false;
-                    }
-                    if(strspn($part, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') != strlen($part)) {
-                        return false;
-                    }
-                }
-                return true;
-            break;
-
-            case 'PARTSTAT':
-                $value = strtoupper($value);
-                switch($parent_property->parent_component) {
-                    case 'VEVENT':
-                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
-                                || $value == 'DELEGATED' || rfc2445_is_xname($value));
-                    break;
-                    case 'VTODO':
-                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
-                                || $value == 'DELEGATED' || $value == 'COMPLETED' || $value == 'IN-PROCESS' || rfc2445_is_xname($value));
-                    break;
-                    case 'VJOURNAL':
-                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || rfc2445_is_xname($value));
-                    break;
-                }
-                return false;
-            break;
-
-            case 'RANGE':
-                $value = strtoupper($value);
-                return ($value == 'THISANDPRIOR' || $value == 'THISANDFUTURE');
-            break;
-
-            case 'RELATED':
-                $value = strtoupper($value);
-                return ($value == 'START' || $value == 'END');
-            break;
-
-            case 'RELTYPE':
-                $value = strtoupper($value);
-                return ($value == 'PARENT' || $value == 'CHILD' || $value == 'SIBLING' || rfc2445_is_xname($value));
-            break;
-
-            case 'ROLE':
-                $value = strtoupper($value);
-                return ($value == 'CHAIR' || $value == 'REQ-PARTICIPANT' || $value == 'OPT-PARTICIPANT' || $value == 'NON-PARTICIPANT' || rfc2445_is_xname($value));
-            break;
-
-            case 'RSVP':
-                $value = strtoupper($value);
-                return ($value == 'TRUE' || $value == 'FALSE');
-            break;
-
-            case 'TZID':
-                if(empty($value)) {
-                    return false;
-                }
-                return (strcspn($value, '";:,') == strlen($value));
-            break;
-
-            case 'VALUE':
-                $value = strtoupper($value);
-                return ($value == 'BINARY'    || $value == 'BOOLEAN'    || $value == 'CAL-ADDRESS' || $value == 'DATE'    ||
-                        $value == 'DATE-TIME' || $value == 'DURATION'   || $value == 'FLOAT'       || $value == 'INTEGER' ||
-                        $value == 'PERIOD'    || $value == 'RECUR'      || $value == 'TEXT'        || $value == 'TIME'    ||
-                        $value == 'URI'       || $value == 'UTC-OFFSET' || rfc2445_is_xname($value));
-            break;
-        }
-    }
-
-    function do_value_formatting($parameter, $value) {
-        switch($parameter) {
-            // Parameters of type CAL-ADDRESS or URI MUST be double-quoted
-            case 'ALTREP':
-            case 'DIR':
-            case 'DELEGATED-FROM':
-            case 'DELEGATED-TO':
-            case 'MEMBER':
-            case 'SENT-BY':
-                return '"'.$value.'"';
-            break;
-
-            // Textual parameter types must be double quoted if they contain COLON, SEMICOLON
-            // or COMMA. Quoting always sounds easier and standards-conformant though.
-            case 'CN':
-                return '"'.$value.'"';
-            break;
-
-            // Parameters with enumerated legal values, just make them all caps
-            case 'CUTYPE':
-            case 'ENCODING':
-            case 'FBTYPE':
-            case 'FMTTYPE':
-            case 'LANGUAGE':
-            case 'PARTSTAT':
-            case 'RANGE':
-            case 'RELATED':
-            case 'RELTYPE':
-            case 'ROLE':
-            case 'RSVP':
-            case 'VALUE':
-                return strtoupper($value);
-            break;
-
-            // Parameters we shouldn't be messing with
-            case 'TZID':
-                return $value;
-            break;
-        }
-    }
-
-    function undo_value_formatting($parameter, $value) {
-    }
-
-}
-
-?>
+<?php // $Id: iCalendar_parameters.php,v 1.8 2006/01/13 14:10:43 defacer Exp $
+
+/**
+ *  BENNU - PHP iCalendar library
+ *  (c) 2005-2006 Ioannis Papaioannou (pj@moodle.org). All rights reserved.
+ *
+ *  Released under the LGPL.
+ *
+ *  See http://bennu.sourceforge.net/ for more information and downloads.
+ *
+ * @author Ioannis Papaioannou 
+ * @version $Id: iCalendar_parameters.php,v 1.8 2006/01/13 14:10:43 defacer Exp $
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+class iCalendar_parameter {
+    function multiple_values_allowed($parameter) {
+        switch($parameter) {
+            case 'DELEGATED-FROM':
+            case 'DELEGATED-TO':
+            case 'MEMBER':
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    function default_value($parameter) {
+        switch($parameter) {
+            case 'CUTYPE':   return 'INDIVIDUAL';
+            case 'FBTYPE':   return 'BUSY';
+            case 'PARTSTAT': return 'NEEDS-ACTION';
+            case 'RELATED':  return 'START';
+            case 'RELTYPE':  return 'PARENT';
+            case 'ROLE':     return 'REQ-PARTICIPANT';
+            case 'RSVP':     return 'FALSE';
+            default:         return NULL;
+        }
+    }
+
+    function is_valid_value(&$parent_property, $parameter, $value) {
+        switch($parameter) {
+            // These must all be a URI
+            case 'ALTREP':
+            case 'DIR':
+                return rfc2445_is_valid_value($value, RFC2445_TYPE_URI);
+            break;
+
+            // These must be CAL-ADDRESS, which is equivalent to URI
+            case 'DELEGATED-FROM':
+            case 'DELEGATED-TO':
+            case 'MEMBER':
+            case 'SENT-BY':
+                return rfc2445_is_valid_value($value, RFC2445_TYPE_CAL_ADDRESS);
+            break;
+
+            // These are textual parameters, so the MUST NOT contain double quotes
+            case 'CN':
+                return (strpos($value, '"') === false);
+            break;
+
+            // These have enumerated legal values
+            case 'CUTYPE':
+                $value = strtoupper($value);
+                return ($value == 'INDIVIDUAL' || $value == 'GROUP' || $value == 'RESOURCE' || $value == 'ROOM' || $value == 'UNKNOWN' || rfc2445_is_xname($value));
+            break;
+
+            case 'ENCODING':
+                $value = strtoupper($value);
+                return ($value == '8BIT' || $value == 'BASE64' || rfc2445_is_xname($value));
+            break;
+
+            case 'FBTYPE':
+                $value = strtoupper($value);
+                return ($value == 'FREE' || $value == 'BUSY' || $value == 'BUSY-UNAVAILABLE' || $value == 'BUSY-TENTATIVE' || rfc2445_is_xname($value));
+            break;
+
+            case 'FMTTYPE':
+                $fmttypes = array(
+                        'TEXT'        => array('PLAIN', 'RICHTEXT', 'ENRICHED', 'TAB-SEPARATED-VALUES', 'HTML', 'SGML',
+                                               'VND.LATEX-Z', 'VND.FMI.FLEXSTOR'),
+                        'MULTIPART'   => array('MIXED', 'ALTERNATIVE', 'DIGEST', 'PARALLEL', 'APPLEDOUBLE', 'HEADER-SET',
+                                               'FORM-DATA', 'RELATED', 'REPORT', 'VOICE-MESSAGE', 'SIGNED', 'ENCRYPTED',
+                                               'BYTERANGES'),
+                        'MESSAGE'     => array('RFC822', 'PARTIAL', 'EXTERNAL-BODY', 'NEWS', 'HTTP'),
+                        'APPLICATION' => array('OCTET-STREAM', 'POSTSCRIPT', 'ODA', 'ATOMICMAIL', 'ANDREW-INSET', 'SLATE',
+                                               'WITA', 'DEC-DX', 'DCA-RFT', 'ACTIVEMESSAGE', 'RTF', 'APPLEFILE',
+                                               'MAC-BINHEX40', 'NEWS-MESSAGE-ID', 'NEWS-TRANSMISSION', 'WORDPERFECT5.1',
+                                               'PDF', 'ZIP', 'MACWRITEII', 'MSWORD', 'REMOTE-PRINTING', 'MATHEMATICA',
+                                               'CYBERCASH', 'COMMONGROUND', 'IGES', 'RISCOS', 'ESHOP', 'X400-BP', 'SGML',
+                                               'CALS-1840', 'PGP-ENCRYPTED', 'PGP-SIGNATURE', 'PGP-KEYS', 'VND.FRAMEMAKER',
+                                               'VND.MIF', 'VND.MS-EXCEL', 'VND.MS-POWERPOINT', 'VND.MS-PROJECT',
+                                               'VND.MS-WORKS', 'VND.MS-TNEF', 'VND.SVD', 'VND.MUSIC-NIFF', 'VND.MS-ARTGALRY',
+                                               'VND.TRUEDOC', 'VND.KOAN', 'VND.STREET-STREAM', 'VND.FDF',
+                                               'SET-PAYMENT-INITIATION', 'SET-PAYMENT', 'SET-REGISTRATION-INITIATION',
+                                               'SET-REGISTRATION', 'VND.SEEMAIL', 'VND.BUSINESSOBJECTS',
+                                               'VND.MERIDIAN-SLINGSHOT', 'VND.XARA', 'SGML-OPEN-CATALOG', 'VND.RAPID',
+                                               'VND.ENLIVEN', 'VND.JAPANNET-REGISTRATION-WAKEUP', 
+                                               'VND.JAPANNET-VERIFICATION-WAKEUP', 'VND.JAPANNET-PAYMENT-WAKEUP',
+                                               'VND.JAPANNET-DIRECTORY-SERVICE', 'VND.INTERTRUST.DIGIBOX', 'VND.INTERTRUST.NNCP'),
+                        'IMAGE'       => array('JPEG', 'GIF', 'IEF', 'G3FAX', 'TIFF', 'CGM', 'NAPLPS', 'VND.DWG', 'VND.SVF',
+                                               'VND.DXF', 'PNG', 'VND.FPX', 'VND.NET-FPX'),
+                        'AUDIO'       => array('BASIC', '32KADPCM', 'VND.QCELP'),
+                        'VIDEO'       => array('MPEG', 'QUICKTIME', 'VND.VIVO', 'VND.MOTOROLA.VIDEO', 'VND.MOTOROLA.VIDEOP')
+                );
+                $value = strtoupper($value);
+                if(rfc2445_is_xname($value)) {
+                    return true;
+                }
+                @list($type, $subtype) = explode('/', $value);
+                if(empty($type) || empty($subtype)) {
+                    return false;
+                }
+                if(!isset($fmttypes[$type]) || !in_array($subtype, $fmttypes[$type])) {
+                    return false;
+                }
+                return true;
+            break;
+
+            case 'LANGUAGE':
+                $value = strtoupper($value);
+                $parts = explode('-', $value);
+                foreach($parts as $part) {
+                    if(empty($part)) {
+                        return false;
+                    }
+                    if(strspn($part, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') != strlen($part)) {
+                        return false;
+                    }
+                }
+                return true;
+            break;
+
+            case 'PARTSTAT':
+                $value = strtoupper($value);
+                switch($parent_property->parent_component) {
+                    case 'VEVENT':
+                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
+                                || $value == 'DELEGATED' || rfc2445_is_xname($value));
+                    break;
+                    case 'VTODO':
+                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE'
+                                || $value == 'DELEGATED' || $value == 'COMPLETED' || $value == 'IN-PROCESS' || rfc2445_is_xname($value));
+                    break;
+                    case 'VJOURNAL':
+                        return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || rfc2445_is_xname($value));
+                    break;
+                }
+                return false;
+            break;
+
+            case 'RANGE':
+                $value = strtoupper($value);
+                return ($value == 'THISANDPRIOR' || $value == 'THISANDFUTURE');
+            break;
+
+            case 'RELATED':
+                $value = strtoupper($value);
+                return ($value == 'START' || $value == 'END');
+            break;
+
+            case 'RELTYPE':
+                $value = strtoupper($value);
+                return ($value == 'PARENT' || $value == 'CHILD' || $value == 'SIBLING' || rfc2445_is_xname($value));
+            break;
+
+            case 'ROLE':
+                $value = strtoupper($value);
+                return ($value == 'CHAIR' || $value == 'REQ-PARTICIPANT' || $value == 'OPT-PARTICIPANT' || $value == 'NON-PARTICIPANT' || rfc2445_is_xname($value));
+            break;
+
+            case 'RSVP':
+                $value = strtoupper($value);
+                return ($value == 'TRUE' || $value == 'FALSE');
+            break;
+
+            case 'TZID':
+                if(empty($value)) {
+                    return false;
+                }
+                return (strcspn($value, '";:,') == strlen($value));
+            break;
+
+            case 'VALUE':
+                $value = strtoupper($value);
+                return ($value == 'BINARY'    || $value == 'BOOLEAN'    || $value == 'CAL-ADDRESS' || $value == 'DATE'    ||
+                        $value == 'DATE-TIME' || $value == 'DURATION'   || $value == 'FLOAT'       || $value == 'INTEGER' ||
+                        $value == 'PERIOD'    || $value == 'RECUR'      || $value == 'TEXT'        || $value == 'TIME'    ||
+                        $value == 'URI'       || $value == 'UTC-OFFSET' || rfc2445_is_xname($value));
+            break;
+        }
+    }
+
+    function do_value_formatting($parameter, $value) {
+        switch($parameter) {
+            // Parameters of type CAL-ADDRESS or URI MUST be double-quoted
+            case 'ALTREP':
+            case 'DIR':
+            case 'DELEGATED-FROM':
+            case 'DELEGATED-TO':
+            case 'MEMBER':
+            case 'SENT-BY':
+                return '"'.$value.'"';
+            break;
+
+            // Textual parameter types must be double quoted if they contain COLON, SEMICOLON
+            // or COMMA. Quoting always sounds easier and standards-conformant though.
+            case 'CN':
+                return '"'.$value.'"';
+            break;
+
+            // Parameters with enumerated legal values, just make them all caps
+            case 'CUTYPE':
+            case 'ENCODING':
+            case 'FBTYPE':
+            case 'FMTTYPE':
+            case 'LANGUAGE':
+            case 'PARTSTAT':
+            case 'RANGE':
+            case 'RELATED':
+            case 'RELTYPE':
+            case 'ROLE':
+            case 'RSVP':
+            case 'VALUE':
+                return strtoupper($value);
+            break;
+
+            // Parameters we shouldn't be messing with
+            case 'TZID':
+                return $value;
+            break;
+        }
+    }
+
+    function undo_value_formatting($parameter, $value) {
+    }
+
+}
+
+?>
diff --git a/lib/bennu/iCalendar_properties.php b/lib/bennu/iCalendar_properties.php
index 3f3120e..85041e4 100644
--- a/lib/bennu/iCalendar_properties.php
+++ b/lib/bennu/iCalendar_properties.php
@@ -744,7 +744,7 @@ class iCalendar_property_freebusy extends iCalendar_property {
             // Start time MUST be in UTC
             return false;
         }
-        if($value{$pos + 1} != 'P' && $substr($value, -1) != 'Z') {
+        if($value{$pos + 1} != 'P' && substr($value, -1) != 'Z') {
             // If the second part is not a period, it MUST be in UTC
             return false;
         }
@@ -1036,7 +1036,64 @@ class iCalendar_property_rrule extends iCalendar_property {
     }
 }
 
-// TODO: 4.8.6 Alarm Component Properties
+// 4.8.6 Alarm Component Properties
+// -------------------------------------------
+class iCalendar_property_action extends iCalendar_property {
+	var $name      = 'ACTION';
+    var $val_type   = RFC2445_TYPE_TEXT;
+    
+    function construct() {
+        $this->valid_parameters = array(
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+    
+    function is_valid_value($value) {
+        if(!parent::is_valid_value($value)) {
+            return false;
+        }
+        
+        // Value must be one of the following, or an x-name.
+        $valid_values = array('ACTION', 'DISPLAY', 'EMAIL', 'PROCEDURE');
+        return(in_array($value, $valid_values) || rfc2445_is_xname($value));        
+        
+    }
+}
+
+class iCalendar_property_repeat extends iCalendar_property {
+    var $name      = 'REPEAT';
+    var $val_type   = RFC2445_TYPE_INTEGER;
+    
+    function construct() {
+        $this->valid_parameters = array(
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }   
+}
+
+class iCalendar_property_trigger extends iCalendar_property {
+    var $name      = 'TRIGGER';
+    var $val_type   = RFC2445_TYPE_TEXT;
+    
+    function construct() {
+        $this->valid_parameters = array(
+            'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            'RELATED' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+    
+    function is_valid_value($value) {        
+    	if(!parent::is_valid_value($value)) {
+            return false;
+        }
+        // Must either be DURATION or DATE_TIME type
+        return(rfc2445_is_valid_value($value, RFC2445_TYPE_DURATION) 
+            || rfc2445_is_valid_value($value, RFC2445_TYPE_DATE_TIME));
+    }
+}
+
+
 
 // 4.8.7 Change Management Component Properties
 // --------------------------------------------
@@ -1280,6 +1337,88 @@ class iCalendar_property_request_status extends iCalendar_property {
 
 }
 
+class iCalendar_property_tzid extends iCalendar_property {
+
+    var $name        = 'TZID';
+    var $val_type    = RFC2445_TYPE_TEXT;
+
+    function construct() {
+        $this->valid_parameters = array(
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+
+    function is_valid_value($value) {
+        if(!parent::is_valid_value($value)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
+
+class iCalendar_property_tzname extends iCalendar_property {
+
+    var $name        = 'TZNAME';
+    var $val_type    = RFC2445_TYPE_TEXT;
+
+    function construct() {
+        $this->valid_parameters = array(
+            'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE,
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+
+    function is_valid_value($value) {
+        if(!parent::is_valid_value($value)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
+
+class iCalendar_property_tzoffsetfrom extends iCalendar_property {
+
+    var $name        = 'TZOFFSETFROM';
+    var $val_type    = RFC2445_TYPE_UTC_OFFSET;
+
+    function construct() {
+        $this->valid_parameters = array(
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+
+    function is_valid_value($value) {
+        if(!parent::is_valid_value($value)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
+
+class iCalendar_property_tzoffsetto extends iCalendar_property {
+
+    var $name        = 'TZOFFSETTO';
+    var $val_type    = RFC2445_TYPE_UTC_OFFSET;
+
+    function construct() {
+        $this->valid_parameters = array(
+            RFC2445_XNAME => RFC2445_OPTIONAL
+        );
+    }
+
+    function is_valid_value($value) {
+        if(!parent::is_valid_value($value)) {
+            return false;
+        } else {
+        	return true;
+        }
+    }
+}
+
+
 
 #######################
 /*
diff --git a/lib/bennu/iCalendar_rfc2445.php b/lib/bennu/iCalendar_rfc2445.php
index 053adcc..da6b842 100644
--- a/lib/bennu/iCalendar_rfc2445.php
+++ b/lib/bennu/iCalendar_rfc2445.php
@@ -27,6 +27,9 @@ define('RFC2445_WSP',                "\t ");
 define('RFC2445_WEEKDAYS',           'MO,TU,WE,TH,FR,SA,SU');
 define('RFC2445_FOLDED_LINE_LENGTH', 75);
 
+define('RFC2445_PARAMETER_SEPARATOR',	';');
+define('RFC2445_VALUE_SEPARATOR',    	':');
+
 define('RFC2445_REQUIRED', 0x01);
 define('RFC2445_OPTIONAL', 0x02);
 define('RFC2445_ONCE',     0x04);
