diff --git a/plugins/libcalendaring/composer.json b/plugins/libcalendaring/composer.json
index 4b7c5e1b..3b30cd3d 100644
--- a/plugins/libcalendaring/composer.json
+++ b/plugins/libcalendaring/composer.json
@@ -1,25 +1,26 @@
 {
     "name": "kolab/libcalendaring",
     "type": "roundcube-plugin",
     "description": "Library providing common functions for calendaring plugins",
     "homepage": "http://git.kolab.org/roundcubemail-plugins-kolab/",
     "license": "AGPLv3",
     "version": "3.2.8",
     "authors": [
         {
             "name": "Thomas Bruederli",
             "email": "bruederli@kolabsys.com",
             "role": "Lead"
         }
     ],
     "repositories": [
         {
             "type": "composer",
             "url": "http://plugins.roundcube.net"
         }
     ],
     "require": {
-        "php": ">=5.3.0",
-        "roundcube/plugin-installer": ">=0.1.3"
+        "php": ">=5.4.0",
+        "roundcube/plugin-installer": ">=0.1.3",
+        "sabre/vobject": "~3.3.3"
     }
 }
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component.php b/plugins/libcalendaring/lib/Sabre/VObject/Component.php
deleted file mode 100644
index 1c1d9244..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component.php
+++ /dev/null
@@ -1,405 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * VObject Component
- *
- * This class represents a VCALENDAR/VCARD component. A component is for example
- * VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
- * ends with END:COMPONENTNAME
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Component extends Node {
-
-    /**
-     * Name, for example VEVENT
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Children properties and components
-     *
-     * @var array
-     */
-    public $children = array();
-
-    /**
-     * If components are added to this map, they will be automatically mapped
-     * to their respective classes, if parsed by the reader or constructed with
-     * the 'create' method.
-     *
-     * @var array
-     */
-    static public $classMap = array(
-        'VALARM'        => 'Sabre\\VObject\\Component\\VAlarm',
-        'VCALENDAR'     => 'Sabre\\VObject\\Component\\VCalendar',
-        'VCARD'         => 'Sabre\\VObject\\Component\\VCard',
-        'VEVENT'        => 'Sabre\\VObject\\Component\\VEvent',
-        'VJOURNAL'      => 'Sabre\\VObject\\Component\\VJournal',
-        'VTODO'         => 'Sabre\\VObject\\Component\\VTodo',
-        'VFREEBUSY'     => 'Sabre\\VObject\\Component\\VFreeBusy',
-    );
-
-    /**
-     * Creates the new component by name, but in addition will also see if
-     * there's a class mapped to the property name.
-     *
-     * @param string $name
-     * @param string $value
-     * @return Component
-     */
-    static public function create($name, $value = null) {
-
-        $name = strtoupper($name);
-
-        if (isset(self::$classMap[$name])) {
-            return new self::$classMap[$name]($name, $value);
-        } else {
-            return new self($name, $value);
-        }
-
-    }
-
-    /**
-     * Creates a new component.
-     *
-     * By default this object will iterate over its own children, but this can
-     * be overridden with the iterator argument
-     *
-     * @param string $name
-     * @param ElementList $iterator
-     */
-    public function __construct($name, ElementList $iterator = null) {
-
-        $this->name = strtoupper($name);
-        if (!is_null($iterator)) $this->iterator = $iterator;
-
-    }
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        $str = "BEGIN:" . $this->name . "\r\n";
-
-        /**
-         * Gives a component a 'score' for sorting purposes.
-         *
-         * This is solely used by the childrenSort method.
-         *
-         * A higher score means the item will be lower in the list.
-         * To avoid score collisions, each "score category" has a reasonable
-         * space to accomodate elements. The $key is added to the $score to
-         * preserve the original relative order of elements.
-         *
-         * @param int $key
-         * @param array $array
-         * @return int
-         */
-        $sortScore = function($key, $array) {
-
-            if ($array[$key] instanceof Component) {
-
-                // We want to encode VTIMEZONE first, this is a personal
-                // preference.
-                if ($array[$key]->name === 'VTIMEZONE') {
-                    $score=300000000;
-                    return $score+$key;
-                } else {
-                    $score=400000000;
-                    return $score+$key;
-                }
-            } else {
-                // Properties get encoded first
-                // VCARD version 4.0 wants the VERSION property to appear first
-                if ($array[$key] instanceof Property) {
-                    if ($array[$key]->name === 'VERSION') {
-                        $score=100000000;
-                        return $score+$key;
-                    } else {
-                        // All other properties
-                        $score=200000000;
-                        return $score+$key;
-                    }
-                }
-            }
-
-        };
-
-        $tmp = $this->children;
-        uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
-
-            $sA = $sortScore($a, $tmp);
-            $sB = $sortScore($b, $tmp);
-
-            if ($sA === $sB) return 0;
-
-            return ($sA < $sB) ? -1 : 1;
-
-        });
-
-        foreach($this->children as $child) $str.=$child->serialize();
-        $str.= "END:" . $this->name . "\r\n";
-
-        return $str;
-
-    }
-
-    /**
-     * Adds a new component or element
-     *
-     * You can call this method with the following syntaxes:
-     *
-     * add(Node $node)
-     * add(string $name, $value, array $parameters = array())
-     *
-     * The first version adds an Element
-     * The second adds a property as a string.
-     *
-     * @param mixed $item
-     * @param mixed $itemValue
-     * @return void
-     */
-    public function add($item, $itemValue = null, array $parameters = array()) {
-
-        if ($item instanceof Node) {
-            if (!is_null($itemValue)) {
-                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
-            }
-            $item->parent = $this;
-            $this->children[] = $item;
-        } elseif(is_string($item)) {
-
-            $item = Property::create($item,$itemValue, $parameters);
-            $item->parent = $this;
-            $this->children[] = $item;
-
-        } else {
-
-            throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
-
-        }
-
-    }
-
-    /**
-     * Returns an iterable list of children
-     *
-     * @return ElementList
-     */
-    public function children() {
-
-        return new ElementList($this->children);
-
-    }
-
-    /**
-     * Returns an array with elements that match the specified name.
-     *
-     * This function is also aware of MIME-Directory groups (as they appear in
-     * vcards). This means that if a property is grouped as "HOME.EMAIL", it
-     * will also be returned when searching for just "EMAIL". If you want to
-     * search for a property in a specific group, you can select on the entire
-     * string ("HOME.EMAIL"). If you want to search on a specific property that
-     * has not been assigned a group, specify ".EMAIL".
-     *
-     * Keys are retained from the 'children' array, which may be confusing in
-     * certain cases.
-     *
-     * @param string $name
-     * @return array
-     */
-    public function select($name) {
-
-        $group = null;
-        $name = strtoupper($name);
-        if (strpos($name,'.')!==false) {
-            list($group,$name) = explode('.', $name, 2);
-        }
-
-        $result = array();
-        foreach($this->children as $key=>$child) {
-
-            if (
-                strtoupper($child->name) === $name &&
-                (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
-            ) {
-
-                $result[$key] = $child;
-
-            }
-        }
-
-        reset($result);
-        return $result;
-
-    }
-
-    /**
-     * This method only returns a list of sub-components. Properties are
-     * ignored.
-     *
-     * @return array
-     */
-    public function getComponents() {
-
-        $result = array();
-        foreach($this->children as $child) {
-            if ($child instanceof Component) {
-                $result[] = $child;
-            }
-        }
-
-        return $result;
-
-    }
-
-    /**
-     * Validates the node for correctness.
-     *
-     * The following options are supported:
-     *   - Node::REPAIR - If something is broken, and automatic repair may
-     *                    be attempted.
-     *
-     * An array is returned with warnings.
-     *
-     * Every item in the array has the following properties:
-     *    * level - (number between 1 and 3 with severity information)
-     *    * message - (human readable message)
-     *    * node - (reference to the offending node)
-     *
-     * @param int $options
-     * @return array
-     */
-    public function validate($options = 0) {
-
-        $result = array();
-        foreach($this->children as $child) {
-            $result = array_merge($result, $child->validate($options));
-        }
-        return $result;
-
-    }
-
-    /* Magic property accessors {{{ */
-
-    /**
-     * Using 'get' you will either get a property or component,
-     *
-     * If there were no child-elements found with the specified name,
-     * null is returned.
-     *
-     * @param string $name
-     * @return Property
-     */
-    public function __get($name) {
-
-        $matches = $this->select($name);
-        if (count($matches)===0) {
-            return null;
-        } else {
-            $firstMatch = current($matches);
-            /** @var $firstMatch Property */
-            $firstMatch->setIterator(new ElementList(array_values($matches)));
-            return $firstMatch;
-        }
-
-    }
-
-    /**
-     * This method checks if a sub-element with the specified name exists.
-     *
-     * @param string $name
-     * @return bool
-     */
-    public function __isset($name) {
-
-        $matches = $this->select($name);
-        return count($matches)>0;
-
-    }
-
-    /**
-     * Using the setter method you can add properties or subcomponents
-     *
-     * You can either pass a Component, Property
-     * object, or a string to automatically create a Property.
-     *
-     * If the item already exists, it will be removed. If you want to add
-     * a new item with the same name, always use the add() method.
-     *
-     * @param string $name
-     * @param mixed $value
-     * @return void
-     */
-    public function __set($name, $value) {
-
-        $matches = $this->select($name);
-        $overWrite = count($matches)?key($matches):null;
-
-        if ($value instanceof Component || $value instanceof Property) {
-            $value->parent = $this;
-            if (!is_null($overWrite)) {
-                $this->children[$overWrite] = $value;
-            } else {
-                $this->children[] = $value;
-            }
-        } elseif (is_scalar($value)) {
-            $property = Property::create($name,$value);
-            $property->parent = $this;
-            if (!is_null($overWrite)) {
-                $this->children[$overWrite] = $property;
-            } else {
-                $this->children[] = $property;
-            }
-        } else {
-            throw new \InvalidArgumentException('You must pass a \\Sabre\\VObject\\Component, \\Sabre\\VObject\\Property or scalar type');
-        }
-
-    }
-
-    /**
-     * Removes all properties and components within this component.
-     *
-     * @param string $name
-     * @return void
-     */
-    public function __unset($name) {
-
-        $matches = $this->select($name);
-        foreach($matches as $k=>$child) {
-
-            unset($this->children[$k]);
-            $child->parent = null;
-
-        }
-
-    }
-
-    /* }}} */
-
-    /**
-     * This method is automatically called when the object is cloned.
-     * Specifically, this will ensure all child elements are also cloned.
-     *
-     * @return void
-     */
-    public function __clone() {
-
-        foreach($this->children as $key=>$child) {
-            $this->children[$key] = clone $child;
-            $this->children[$key]->parent = $this;
-        }
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VAlarm.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VAlarm.php
deleted file mode 100644
index 2f86c44f..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VAlarm.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-use Sabre\VObject;
-
-/**
- * VAlarm component
- *
- * This component contains some additional functionality specific for VALARMs.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VAlarm extends VObject\Component {
-
-    /**
-     * Returns a DateTime object when this alarm is going to trigger.
-     *
-     * This ignores repeated alarm, only the first trigger is returned.
-     *
-     * @return DateTime
-     */
-    public function getEffectiveTriggerTime() {
-
-        $trigger = $this->TRIGGER;
-        if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
-            $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
-            $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
-
-            $parentComponent = $this->parent;
-            if ($related === 'START') {
-
-                if ($parentComponent->name === 'VTODO') {
-                    $propName = 'DUE';
-                } else {
-                    $propName = 'DTSTART';
-                }
-
-                $effectiveTrigger = clone $parentComponent->$propName->getDateTime();
-                $effectiveTrigger->add($triggerDuration);
-            } else {
-                if ($parentComponent->name === 'VTODO') {
-                    $endProp = 'DUE';
-                } elseif ($parentComponent->name === 'VEVENT') {
-                    $endProp = 'DTEND';
-                } else {
-                    throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
-                }
-
-                if (isset($parentComponent->$endProp)) {
-                    $effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
-                    $effectiveTrigger->add($triggerDuration);
-                } elseif (isset($parentComponent->DURATION)) {
-                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
-                    $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
-                    $effectiveTrigger->add($duration);
-                    $effectiveTrigger->add($triggerDuration);
-                } else {
-                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
-                    $effectiveTrigger->add($triggerDuration);
-                }
-            }
-        } else {
-            $effectiveTrigger = $trigger->getDateTime();
-        }
-        return $effectiveTrigger;
-
-    }
-
-    /**
-     * Returns true or false depending on if the event falls in the specified
-     * time-range. This is used for filtering purposes.
-     *
-     * The rules used to determine if an event falls within the specified
-     * time-range is based on the CalDAV specification.
-     *
-     * @param \DateTime $start
-     * @param \DateTime $end
-     * @return bool
-     */
-    public function isInTimeRange(\DateTime $start, \DateTime $end) {
-
-        $effectiveTrigger = $this->getEffectiveTriggerTime();
-
-        if (isset($this->DURATION)) {
-            $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
-            $repeat = (string)$this->repeat;
-            if (!$repeat) {
-                $repeat = 1;
-            }
-
-            $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
-
-            foreach($period as $occurrence) {
-
-                if ($start <= $occurrence && $end > $occurrence) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
-        }
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VCalendar.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VCalendar.php
deleted file mode 100644
index 9de67982..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VCalendar.php
+++ /dev/null
@@ -1,244 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-
-use Sabre\VObject;
-
-/**
- * The VCalendar component
- *
- * This component adds functionality to a component, specific for a VCALENDAR.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VCalendar extends VObject\Document {
-
-    static $defaultName = 'VCALENDAR';
-
-    /**
-     * Returns a list of all 'base components'. For instance, if an Event has
-     * a recurrence rule, and one instance is overridden, the overridden event
-     * will have the same UID, but will be excluded from this list.
-     *
-     * VTIMEZONE components will always be excluded.
-     *
-     * @param string $componentName filter by component name
-     * @return array
-     */
-    public function getBaseComponents($componentName = null) {
-
-        $components = array();
-        foreach($this->children as $component) {
-
-            if (!$component instanceof VObject\Component)
-                continue;
-
-            if (isset($component->{'RECURRENCE-ID'}))
-                continue;
-
-            if ($componentName && $component->name !== strtoupper($componentName))
-                continue;
-
-            if ($component->name === 'VTIMEZONE')
-                continue;
-
-            $components[] = $component;
-
-        }
-
-        return $components;
-
-    }
-
-    /**
-     * If this calendar object, has events with recurrence rules, this method
-     * can be used to expand the event into multiple sub-events.
-     *
-     * Each event will be stripped from it's recurrence information, and only
-     * the instances of the event in the specified timerange will be left
-     * alone.
-     *
-     * In addition, this method will cause timezone information to be stripped,
-     * and normalized to UTC.
-     *
-     * This method will alter the VCalendar. This cannot be reversed.
-     *
-     * This functionality is specifically used by the CalDAV standard. It is
-     * possible for clients to request expand events, if they are rather simple
-     * clients and do not have the possibility to calculate recurrences.
-     *
-     * @param DateTime $start
-     * @param DateTime $end
-     * @return void
-     */
-    public function expand(\DateTime $start, \DateTime $end) {
-
-        $newEvents = array();
-
-        foreach($this->select('VEVENT') as $key=>$vevent) {
-
-            if (isset($vevent->{'RECURRENCE-ID'})) {
-                unset($this->children[$key]);
-                continue;
-            }
-
-
-            if (!$vevent->rrule) {
-                unset($this->children[$key]);
-                if ($vevent->isInTimeRange($start, $end)) {
-                    $newEvents[] = $vevent;
-                }
-                continue;
-            }
-
-            $uid = (string)$vevent->uid;
-            if (!$uid) {
-                throw new \LogicException('Event did not have a UID!');
-            }
-
-            $it = new VObject\RecurrenceIterator($this, $vevent->uid);
-            $it->fastForward($start);
-
-            while($it->valid() && $it->getDTStart() < $end) {
-
-                if ($it->getDTEnd() > $start) {
-
-                    $newEvents[] = $it->getEventObject();
-
-                }
-                $it->next();
-
-            }
-            unset($this->children[$key]);
-
-        }
-
-        foreach($newEvents as $newEvent) {
-
-            foreach($newEvent->children as $child) {
-                if ($child instanceof VObject\Property\DateTime &&
-                    $child->getDateType() == VObject\Property\DateTime::LOCALTZ) {
-                        $child->setDateTime($child->getDateTime(),VObject\Property\DateTime::UTC);
-                    }
-            }
-
-            $this->add($newEvent);
-
-        }
-
-        // Removing all VTIMEZONE components
-        unset($this->VTIMEZONE);
-
-    }
-
-    /**
-     * Validates the node for correctness.
-     * An array is returned with warnings.
-     *
-     * Every item in the array has the following properties:
-     *    * level - (number between 1 and 3 with severity information)
-     *    * message - (human readable message)
-     *    * node - (reference to the offending node)
-     *
-     * @return array
-     */
-    /*
-    public function validate() {
-
-        $warnings = array();
-
-        $version = $this->select('VERSION');
-        if (count($version)!==1) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time',
-                'node' => $this,
-            );
-        } else {
-            if ((string)$this->VERSION !== '2.0') {
-                $warnings[] = array(
-                    'level' => 1,
-                    'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
-                    'node' => $this,
-                );
-            }
-        }
-        $version = $this->select('PRODID');
-        if (count($version)!==1) {
-            $warnings[] = array(
-                'level' => 2,
-                'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time',
-                'node' => $this,
-            );
-        }
-        if (count($this->CALSCALE) > 1) {
-            $warnings[] = array(
-                'level' => 2,
-                'message' => 'The CALSCALE property must not be specified more than once.',
-                'node' => $this,
-            );
-        }
-        if (count($this->METHOD) > 1) {
-            $warnings[] = array(
-                'level' => 2,
-                'message' => 'The METHOD property must not be specified more than once.',
-                'node' => $this,
-            );
-        }
-
-        $allowedComponents = array(
-            'VEVENT',
-            'VTODO',
-            'VJOURNAL',
-            'VFREEBUSY',
-            'VTIMEZONE',
-        );
-        $allowedProperties = array(
-            'PRODID',
-            'VERSION',
-            'CALSCALE',
-            'METHOD',
-        );
-        $componentsFound = 0;
-        foreach($this->children as $child) {
-            if($child instanceof Component) {
-                $componentsFound++;
-                if (!in_array($child->name, $allowedComponents)) {
-                    $warnings[] = array(
-                        'level' => 1,
-                        'message' => 'The ' . $child->name . " component is not allowed in the VCALENDAR component",
-                        'node' => $this,
-                    );
-                }
-            }
-            if ($child instanceof Property) {
-                if (!in_array($child->name, $allowedProperties)) {
-                    $warnings[] = array(
-                        'level' => 2,
-                        'message' => 'The ' . $child->name . " property is not allowed in the VCALENDAR component",
-                        'node' => $this,
-                    );
-                }
-            }
-        }
-
-        if ($componentsFound===0) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'An iCalendar object must have at least 1 component.',
-                'node' => $this,
-            );
-        }
-
-        return array_merge(
-            $warnings,
-            parent::validate()
-        );
-
-    }
-     */
-
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VCard.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VCard.php
deleted file mode 100644
index 0fc8b702..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VCard.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-
-use Sabre\VObject;
-
-/**
- * The VCard component
- *
- * This component represents the BEGIN:VCARD and END:VCARD found in every
- * vcard.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VCard extends VObject\Component {
-
-    static $defaultName = 'VCARD';
-
-    /**
-     * VCards with version 2.1, 3.0 and 4.0 are found.
-     *
-     * If the VCARD doesn't know its version, 4.0 is assumed.
-     */
-    const DEFAULT_VERSION = '4.0';
-
-    /**
-     * Validates the node for correctness.
-     *
-     * The following options are supported:
-     *   - Node::REPAIR - If something is broken, and automatic repair may
-     *                    be attempted.
-     *
-     * An array is returned with warnings.
-     *
-     * Every item in the array has the following properties:
-     *    * level - (number between 1 and 3 with severity information)
-     *    * message - (human readable message)
-     *    * node - (reference to the offending node)
-     *
-     * @param int $options
-     * @return array
-     */
-    public function validate($options = 0) {
-
-        $warnings = array();
-
-        $version = $this->select('VERSION');
-        if (count($version)!==1) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
-                'node' => $this,
-            );
-            if ($options & self::REPAIR) {
-                $this->VERSION = self::DEFAULT_VERSION;
-            }
-        } else {
-            $version = (string)$this->VERSION;
-            if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
-                $warnings[] = array(
-                    'level' => 1,
-                    'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
-                    'node' => $this,
-                );
-                if ($options & self::REPAIR) {
-                    $this->VERSION = '4.0';
-                }
-            }
-
-        }
-        $fn = $this->select('FN');
-        if (count($fn)!==1) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'The FN property must appear in the VCARD component exactly 1 time',
-                'node' => $this,
-            );
-            if (($options & self::REPAIR) && count($fn) === 0) {
-                // We're going to try to see if we can use the contents of the
-                // N property.
-                if (isset($this->N)) {
-                    $value = explode(';', (string)$this->N);
-                    if (isset($value[1]) && $value[1]) {
-                        $this->FN = $value[1] . ' ' . $value[0];
-                    } else {
-                        $this->FN = $value[0];
-                    }
-
-                // Otherwise, the ORG property may work
-                } elseif (isset($this->ORG)) {
-                    $this->FN = (string)$this->ORG;
-                }
-
-            }
-        }
-
-        return array_merge(
-            parent::validate($options),
-            $warnings
-        );
-
-    }
-
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VEvent.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VEvent.php
deleted file mode 100644
index 2375c531..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VEvent.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-use Sabre\VObject;
-
-/**
- * VEvent component
- *
- * This component contains some additional functionality specific for VEVENT's.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VEvent extends VObject\Component {
-
-    /**
-     * Returns true or false depending on if the event falls in the specified
-     * time-range. This is used for filtering purposes.
-     *
-     * The rules used to determine if an event falls within the specified
-     * time-range is based on the CalDAV specification.
-     *
-     * @param \DateTime $start
-     * @param \DateTime $end
-     * @return bool
-     */
-    public function isInTimeRange(\DateTime $start, \DateTime $end) {
-
-        if ($this->RRULE) {
-            $it = new VObject\RecurrenceIterator($this);
-            $it->fastForward($start);
-
-            // We fast-forwarded to a spot where the end-time of the
-            // recurrence instance exceeded the start of the requested
-            // time-range.
-            //
-            // If the starttime of the recurrence did not exceed the
-            // end of the time range as well, we have a match.
-            return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
-
-        }
-
-        $effectiveStart = $this->DTSTART->getDateTime();
-        if (isset($this->DTEND)) {
-
-            // The DTEND property is considered non inclusive. So for a 3 day
-            // event in july, dtstart and dtend would have to be July 1st and
-            // July 4th respectively.
-            //
-            // See:
-            // http://tools.ietf.org/html/rfc5545#page-54
-            $effectiveEnd = $this->DTEND->getDateTime();
-
-        } elseif (isset($this->DURATION)) {
-            $effectiveEnd = clone $effectiveStart;
-            $effectiveEnd->add( VObject\DateTimeParser::parseDuration($this->DURATION) );
-        } elseif ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
-            $effectiveEnd = clone $effectiveStart;
-            $effectiveEnd->modify('+1 day');
-        } else {
-            $effectiveEnd = clone $effectiveStart;
-        }
-        return (
-            ($start <= $effectiveEnd) && ($end > $effectiveStart)
-        );
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VFreeBusy.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VFreeBusy.php
deleted file mode 100644
index 7afe9fdb..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VFreeBusy.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-
-use Sabre\VObject;
-
-/**
- * The VFreeBusy component
- *
- * This component adds functionality to a component, specific for VFREEBUSY
- * components.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VFreeBusy extends VObject\Component {
-
-    /**
-     * Checks based on the contained FREEBUSY information, if a timeslot is
-     * available.
-     *
-     * @param DateTime $start
-     * @param Datetime $end
-     * @return bool
-     */
-    public function isFree(\DateTime $start, \Datetime $end) {
-
-        foreach($this->select('FREEBUSY') as $freebusy) {
-
-            // We are only interested in FBTYPE=BUSY (the default),
-            // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
-            if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
-                continue;
-            }
-
-            // The freebusy component can hold more than 1 value, separated by
-            // commas.
-            $periods = explode(',', (string)$freebusy);
-
-            foreach($periods as $period) {
-                // Every period is formatted as [start]/[end]. The start is an
-                // absolute UTC time, the end may be an absolute UTC time, or
-                // duration (relative) value.
-                list($busyStart, $busyEnd) = explode('/', $period);
-
-                $busyStart = VObject\DateTimeParser::parse($busyStart);
-                $busyEnd = VObject\DateTimeParser::parse($busyEnd);
-                if ($busyEnd instanceof \DateInterval) {
-                    $tmp = clone $busyStart;
-                    $tmp->add($busyEnd);
-                    $busyEnd = $tmp;
-                }
-
-                if($start < $busyEnd && $end > $busyStart) {
-                    return false;
-                }
-
-            }
-
-        }
-
-        return true;
-
-    }
-
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VJournal.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VJournal.php
deleted file mode 100644
index 23288787..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VJournal.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-
-use Sabre\VObject;
-
-/**
- * VJournal component
- *
- * This component contains some additional functionality specific for VJOURNALs.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VJournal extends VObject\Component {
-
-    /**
-     * Returns true or false depending on if the event falls in the specified
-     * time-range. This is used for filtering purposes.
-     *
-     * The rules used to determine if an event falls within the specified
-     * time-range is based on the CalDAV specification.
-     *
-     * @param DateTime $start
-     * @param DateTime $end
-     * @return bool
-     */
-    public function isInTimeRange(\DateTime $start, \DateTime $end) {
-
-        $dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
-        if ($dtstart) {
-            $effectiveEnd = clone $dtstart;
-            if ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
-                $effectiveEnd->modify('+1 day');
-            }
-
-            return ($start <= $effectiveEnd && $end > $dtstart);
-
-        }
-        return false;
-
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Component/VTodo.php b/plugins/libcalendaring/lib/Sabre/VObject/Component/VTodo.php
deleted file mode 100644
index b1579cf7..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Component/VTodo.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Component;
-
-use Sabre\VObject;
-
-/**
- * VTodo component
- *
- * This component contains some additional functionality specific for VTODOs.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VTodo extends VObject\Component {
-
-    /**
-     * Returns true or false depending on if the event falls in the specified
-     * time-range. This is used for filtering purposes.
-     *
-     * The rules used to determine if an event falls within the specified
-     * time-range is based on the CalDAV specification.
-     *
-     * @param DateTime $start
-     * @param DateTime $end
-     * @return bool
-     */
-    public function isInTimeRange(\DateTime $start, \DateTime $end) {
-
-        $dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
-        $duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null;
-        $due = isset($this->DUE)?$this->DUE->getDateTime():null;
-        $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
-        $created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
-
-        if ($dtstart) {
-            if ($duration) {
-                $effectiveEnd = clone $dtstart;
-                $effectiveEnd->add($duration);
-                return $start <= $effectiveEnd && $end > $dtstart;
-            } elseif ($due) {
-                return
-                    ($start < $due || $start <= $dtstart) &&
-                    ($end > $dtstart || $end >= $due);
-            } else {
-                return $start <= $dtstart && $end > $dtstart;
-            }
-        }
-        if ($due) {
-            return ($start < $due && $end >= $due);
-        }
-        if ($completed && $created) {
-            return
-                ($start <= $created || $start <= $completed) &&
-                ($end >= $created || $end >= $completed);
-        }
-        if ($completed) {
-            return ($start <= $completed && $end >= $completed);
-        }
-        if ($created) {
-            return ($end > $created);
-        }
-        return true;
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/DateTimeParser.php b/plugins/libcalendaring/lib/Sabre/VObject/DateTimeParser.php
deleted file mode 100644
index 03600506..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/DateTimeParser.php
+++ /dev/null
@@ -1,181 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * DateTimeParser
- *
- * This class is responsible for parsing the several different date and time
- * formats iCalendar and vCards have.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class DateTimeParser {
-
-    /**
-     * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
-     *
-     * Specifying a reference timezone is optional. It will only be used
-     * if the non-UTC format is used. The argument is used as a reference, the
-     * returned DateTime object will still be in the UTC timezone.
-     *
-     * @param string $dt
-     * @param DateTimeZone $tz
-     * @return DateTime
-     */
-    static public function parseDateTime($dt,\DateTimeZone $tz = null) {
-
-        // Format is YYYYMMDD + "T" + hhmmss
-        $result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
-
-        if (!$result) {
-            throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
-        }
-
-        if ($matches[7]==='Z' || is_null($tz)) {
-            $tz = new \DateTimeZone('UTC');
-        }
-        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
-
-        // Still resetting the timezone, to normalize everything to UTC
-        $date->setTimeZone(new \DateTimeZone('UTC'));
-        return $date;
-
-    }
-
-    /**
-     * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
-     *
-     * @param string $date
-     * @return DateTime
-     */
-    static public function parseDate($date) {
-
-        // Format is YYYYMMDD
-        $result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
-
-        if (!$result) {
-            throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
-        }
-
-        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
-        return $date;
-
-    }
-
-    /**
-     * Parses an iCalendar (RFC5545) formatted duration value.
-     *
-     * This method will either return a DateTimeInterval object, or a string
-     * suitable for strtotime or DateTime::modify.
-     *
-     * @param string $duration
-     * @param bool $asString
-     * @return DateInterval|string
-     */
-    static public function parseDuration($duration, $asString = false) {
-
-        $result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
-        if (!$result) {
-            throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
-        }
-
-        if (!$asString) {
-            $invert = false;
-            if ($matches['plusminus']==='-') {
-                $invert = true;
-            }
-
-
-            $parts = array(
-                'week',
-                'day',
-                'hour',
-                'minute',
-                'second',
-            );
-            foreach($parts as $part) {
-                $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
-            }
-
-
-            // We need to re-construct the $duration string, because weeks and
-            // days are not supported by DateInterval in the same string.
-            $duration = 'P';
-            $days = $matches['day'];
-            if ($matches['week']) {
-                $days+=$matches['week']*7;
-            }
-            if ($days)
-                $duration.=$days . 'D';
-
-            if ($matches['minute'] || $matches['second'] || $matches['hour']) {
-                $duration.='T';
-
-                if ($matches['hour'])
-                    $duration.=$matches['hour'].'H';
-
-                if ($matches['minute'])
-                    $duration.=$matches['minute'].'M';
-
-                if ($matches['second'])
-                    $duration.=$matches['second'].'S';
-
-            }
-
-            if ($duration==='P') {
-                $duration = 'PT0S';
-            }
-            $iv = new \DateInterval($duration);
-            if ($invert) $iv->invert = true;
-
-            return $iv;
-
-        }
-
-
-
-        $parts = array(
-            'week',
-            'day',
-            'hour',
-            'minute',
-            'second',
-        );
-
-        $newDur = '';
-        foreach($parts as $part) {
-            if (isset($matches[$part]) && $matches[$part]) {
-                $newDur.=' '.$matches[$part] . ' ' . $part . 's';
-            }
-        }
-
-        $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
-        if ($newDur === '+') { $newDur = '+0 seconds'; };
-        return $newDur;
-
-    }
-
-    /**
-     * Parses either a Date or DateTime, or Duration value.
-     *
-     * @param string $date
-     * @param DateTimeZone|string $referenceTZ
-     * @return DateTime|DateInterval
-     */
-    static public function parse($date, $referenceTZ = null) {
-
-        if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
-            return self::parseDuration($date);
-        } elseif (strlen($date)===8) {
-            return self::parseDate($date);
-        } else {
-            return self::parseDateTime($date, $referenceTZ);
-        }
-
-    }
-
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Document.php b/plugins/libcalendaring/lib/Sabre/VObject/Document.php
deleted file mode 100644
index 50a662ee..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Document.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * Document
- *
- * A document is just like a component, except that it's also the top level
- * element.
- *
- * Both a VCALENDAR and a VCARD are considered documents.
- *
- * This class also provides a registry for document types.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-abstract class Document extends Component {
-
-    /**
-     * The default name for this component.
-     *
-     * This should be 'VCALENDAR' or 'VCARD'.
-     *
-     * @var string
-     */
-    static $defaultName;
-
-    /**
-     * Creates a new document.
-     *
-     * We're changing the default behavior slightly here. First, we don't want
-     * to have to specify a name (we already know it), and we want to allow
-     * children to be specified in the first argument.
-     *
-     * But, the default behavior also works.
-     *
-     * So the two sigs:
-     *
-     * new Document(array $children = array());
-     * new Document(string $name, array $children = array())
-     *
-     * @return void
-     */
-    public function __construct() {
-
-        $args = func_get_args();
-        if (count($args)===0 || is_array($args[0])) {
-            array_unshift($args, static::$defaultName);
-            call_user_func_array(array('parent', '__construct'), $args);
-        } else {
-            call_user_func_array(array('parent', '__construct'), $args);
-        }
-
-    }
-
-    /**
-     * Creates a new component
-     *
-     * This method automatically searches for the correct component class, based
-     * on its name.
-     *
-     * You can specify the children either in key=>value syntax, in which case
-     * properties will automatically be created, or you can just pass a list of
-     * Component and Property object.
-     *
-     * @param string $name
-     * @param array $children
-     * @return Component
-     */
-    public function createComponent($name, array $children = array()) {
-
-        $component = Component::create($name);
-        foreach($children as $k=>$v) {
-
-            if ($v instanceof Node) {
-                $component->add($v);
-            } else {
-                $component->add($k, $v);
-            }
-
-        }
-        return $component;
-
-    }
-
-    /**
-     * Factory method for creating new properties
-     *
-     * This method automatically searches for the correct property class, based
-     * on its name.
-     *
-     * You can specify the parameters either in key=>value syntax, in which case
-     * parameters will automatically be created, or you can just pass a list of
-     * Parameter objects.
-     *
-     * @param string $name
-     * @param mixed $value
-     * @param array $parameters
-     * @return Property
-     */
-    public function createProperty($name, $value = null, array $parameters = array()) {
-
-        return Property::create($name, $value, $parameters);
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/ElementList.php b/plugins/libcalendaring/lib/Sabre/VObject/ElementList.php
deleted file mode 100644
index 1c203708..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/ElementList.php
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * VObject ElementList
- *
- * This class represents a list of elements. Lists are the result of queries,
- * such as doing $vcalendar->vevent where there's multiple VEVENT objects.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class ElementList implements \Iterator, \Countable, \ArrayAccess {
-
-    /**
-     * Inner elements
-     *
-     * @var array
-     */
-    protected $elements = array();
-
-    /**
-     * Creates the element list.
-     *
-     * @param array $elements
-     */
-    public function __construct(array $elements) {
-
-        $this->elements = $elements;
-
-    }
-
-    /* {{{ Iterator interface */
-
-    /**
-     * Current position
-     *
-     * @var int
-     */
-    private $key = 0;
-
-    /**
-     * Returns current item in iteration
-     *
-     * @return Element
-     */
-    public function current() {
-
-        return $this->elements[$this->key];
-
-    }
-
-    /**
-     * To the next item in the iterator
-     *
-     * @return void
-     */
-    public function next() {
-
-        $this->key++;
-
-    }
-
-    /**
-     * Returns the current iterator key
-     *
-     * @return int
-     */
-    public function key() {
-
-        return $this->key;
-
-    }
-
-    /**
-     * Returns true if the current position in the iterator is a valid one
-     *
-     * @return bool
-     */
-    public function valid() {
-
-        return isset($this->elements[$this->key]);
-
-    }
-
-    /**
-     * Rewinds the iterator
-     *
-     * @return void
-     */
-    public function rewind() {
-
-        $this->key = 0;
-
-    }
-
-    /* }}} */
-
-    /* {{{ Countable interface */
-
-    /**
-     * Returns the number of elements
-     *
-     * @return int
-     */
-    public function count() {
-
-        return count($this->elements);
-
-    }
-
-    /* }}} */
-
-    /* {{{ ArrayAccess Interface */
-
-
-    /**
-     * Checks if an item exists through ArrayAccess.
-     *
-     * @param int $offset
-     * @return bool
-     */
-    public function offsetExists($offset) {
-
-        return isset($this->elements[$offset]);
-
-    }
-
-    /**
-     * Gets an item through ArrayAccess.
-     *
-     * @param int $offset
-     * @return mixed
-     */
-    public function offsetGet($offset) {
-
-        return $this->elements[$offset];
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * @param int $offset
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($offset,$value) {
-
-        throw new \LogicException('You can not add new objects to an ElementList');
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return void
-     */
-    public function offsetUnset($offset) {
-
-        throw new \LogicException('You can not remove objects from an ElementList');
-
-    }
-
-    /* }}} */
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/FreeBusyGenerator.php b/plugins/libcalendaring/lib/Sabre/VObject/FreeBusyGenerator.php
deleted file mode 100644
index 96d0be5a..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/FreeBusyGenerator.php
+++ /dev/null
@@ -1,322 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * This class helps with generating FREEBUSY reports based on existing sets of
- * objects.
- *
- * It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
- * generates a single VFREEBUSY object.
- *
- * VFREEBUSY components are described in RFC5545, The rules for what should
- * go in a single freebusy report is taken from RFC4791, section 7.10.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class FreeBusyGenerator {
-
-    /**
-     * Input objects
-     *
-     * @var array
-     */
-    protected $objects;
-
-    /**
-     * Start of range
-     *
-     * @var DateTime|null
-     */
-    protected $start;
-
-    /**
-     * End of range
-     *
-     * @var DateTime|null
-     */
-    protected $end;
-
-    /**
-     * VCALENDAR object
-     *
-     * @var Component
-     */
-    protected $baseObject;
-
-    /**
-     * Creates the generator.
-     *
-     * Check the setTimeRange and setObjects methods for details about the
-     * arguments.
-     *
-     * @param DateTime $start
-     * @param DateTime $end
-     * @param mixed $objects
-     * @return void
-     */
-    public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
-
-        if ($start && $end) {
-            $this->setTimeRange($start, $end);
-        }
-
-        if ($objects) {
-            $this->setObjects($objects);
-        }
-
-    }
-
-    /**
-     * Sets the VCALENDAR object.
-     *
-     * If this is set, it will not be generated for you. You are responsible
-     * for setting things like the METHOD, CALSCALE, VERSION, etc..
-     *
-     * The VFREEBUSY object will be automatically added though.
-     *
-     * @param Component $vcalendar
-     * @return void
-     */
-    public function setBaseObject(Component $vcalendar) {
-
-        $this->baseObject = $vcalendar;
-
-    }
-
-    /**
-     * Sets the input objects
-     *
-     * You must either specify a valendar object as a strong, or as the parse
-     * Component.
-     * It's also possible to specify multiple objects as an array.
-     *
-     * @param mixed $objects
-     * @return void
-     */
-    public function setObjects($objects) {
-
-        if (!is_array($objects)) {
-            $objects = array($objects);
-        }
-
-        $this->objects = array();
-        foreach($objects as $object) {
-
-            if (is_string($object)) {
-                $this->objects[] = Reader::read($object);
-            } elseif ($object instanceof Component) {
-                $this->objects[] = $object;
-            } else {
-                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
-            }
-
-        }
-
-    }
-
-    /**
-     * Sets the time range
-     *
-     * Any freebusy object falling outside of this time range will be ignored.
-     *
-     * @param DateTime $start
-     * @param DateTime $end
-     * @return void
-     */
-    public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
-
-        $this->start = $start;
-        $this->end = $end;
-
-    }
-
-    /**
-     * Parses the input data and returns a correct VFREEBUSY object, wrapped in
-     * a VCALENDAR.
-     *
-     * @return Component
-     */
-    public function getResult() {
-
-        $busyTimes = array();
-
-        foreach($this->objects as $object) {
-
-            foreach($object->getBaseComponents() as $component) {
-
-                switch($component->name) {
-
-                    case 'VEVENT' :
-
-                        $FBTYPE = 'BUSY';
-                        if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
-                            break;
-                        }
-                        if (isset($component->STATUS)) {
-                            $status = strtoupper($component->STATUS);
-                            if ($status==='CANCELLED') {
-                                break;
-                            }
-                            if ($status==='TENTATIVE') {
-                                $FBTYPE = 'BUSY-TENTATIVE';
-                            }
-                        }
-
-                        $times = array();
-
-                        if ($component->RRULE) {
-
-                            $iterator = new RecurrenceIterator($object, (string)$component->uid);
-                            if ($this->start) {
-                                $iterator->fastForward($this->start);
-                            }
-
-                            $maxRecurrences = 200;
-
-                            while($iterator->valid() && --$maxRecurrences) {
-
-                                $startTime = $iterator->getDTStart();
-                                if ($this->end && $startTime > $this->end) {
-                                    break;
-                                }
-                                $times[] = array(
-                                    $iterator->getDTStart(),
-                                    $iterator->getDTEnd(),
-                                );
-
-                                $iterator->next();
-
-                            }
-
-                        } else {
-
-                            $startTime = $component->DTSTART->getDateTime();
-                            if ($this->end && $startTime > $this->end) {
-                                break;
-                            }
-                            $endTime = null;
-                            if (isset($component->DTEND)) {
-                                $endTime = $component->DTEND->getDateTime();
-                            } elseif (isset($component->DURATION)) {
-                                $duration = DateTimeParser::parseDuration((string)$component->DURATION);
-                                $endTime = clone $startTime;
-                                $endTime->add($duration);
-                            } elseif ($component->DTSTART->getDateType() === Property\DateTime::DATE) {
-                                $endTime = clone $startTime;
-                                $endTime->modify('+1 day');
-                            } else {
-                                // The event had no duration (0 seconds)
-                                break;
-                            }
-
-                            $times[] = array($startTime, $endTime);
-
-                        }
-
-                        foreach($times as $time) {
-
-                            if ($this->end && $time[0] > $this->end) break;
-                            if ($this->start && $time[1] < $this->start) break;
-
-                            $busyTimes[] = array(
-                                $time[0],
-                                $time[1],
-                                $FBTYPE,
-                            );
-                        }
-                        break;
-
-                    case 'VFREEBUSY' :
-                        foreach($component->FREEBUSY as $freebusy) {
-
-                            $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
-
-                            // Skipping intervals marked as 'free'
-                            if ($fbType==='FREE')
-                                continue;
-
-                            $values = explode(',', $freebusy);
-                            foreach($values as $value) {
-                                list($startTime, $endTime) = explode('/', $value);
-                                $startTime = DateTimeParser::parseDateTime($startTime);
-
-                                if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
-                                    $duration = DateTimeParser::parseDuration($endTime);
-                                    $endTime = clone $startTime;
-                                    $endTime->add($duration);
-                                } else {
-                                    $endTime = DateTimeParser::parseDateTime($endTime);
-                                }
-
-                                if($this->start && $this->start > $endTime) continue;
-                                if($this->end && $this->end < $startTime) continue;
-                                $busyTimes[] = array(
-                                    $startTime,
-                                    $endTime,
-                                    $fbType
-                                );
-
-                            }
-
-
-                        }
-                        break;
-
-
-
-                }
-
-
-            }
-
-        }
-
-        if ($this->baseObject) {
-            $calendar = $this->baseObject;
-        } else {
-            $calendar = Component::create('VCALENDAR');
-            $calendar->version = '2.0';
-            $calendar->prodid = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
-            $calendar->calscale = 'GREGORIAN';
-        }
-
-        $vfreebusy = Component::create('VFREEBUSY');
-        $calendar->add($vfreebusy);
-
-        if ($this->start) {
-            $dtstart = Property::create('DTSTART');
-            $dtstart->setDateTime($this->start,Property\DateTime::UTC);
-            $vfreebusy->add($dtstart);
-        }
-        if ($this->end) {
-            $dtend = Property::create('DTEND');
-            $dtend->setDateTime($this->end,Property\DateTime::UTC);
-            $vfreebusy->add($dtend);
-        }
-        $dtstamp = Property::create('DTSTAMP');
-        $dtstamp->setDateTime(new \DateTime('now'), Property\DateTime::UTC);
-        $vfreebusy->add($dtstamp);
-
-        foreach($busyTimes as $busyTime) {
-
-            $busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
-            $busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
-
-            $prop = Property::create(
-                'FREEBUSY',
-                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
-            );
-            $prop['FBTYPE'] = $busyTime[2];
-            $vfreebusy->add($prop);
-
-        }
-
-        return $calendar;
-
-    }
-
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Node.php b/plugins/libcalendaring/lib/Sabre/VObject/Node.php
deleted file mode 100644
index bee68ec2..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Node.php
+++ /dev/null
@@ -1,187 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * Base class for all nodes
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
-
-    /**
-     * The following constants are used by the validate() method.
-     */
-    const REPAIR = 1;
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    abstract function serialize();
-
-    /**
-     * Iterator override
-     *
-     * @var ElementList
-     */
-    protected $iterator = null;
-
-    /**
-     * A link to the parent node
-     *
-     * @var Node
-     */
-    public $parent = null;
-
-    /**
-     * Validates the node for correctness.
-     *
-     * The following options are supported:
-     *   - Node::REPAIR - If something is broken, and automatic repair may
-     *                    be attempted.
-     *
-     * An array is returned with warnings.
-     *
-     * Every item in the array has the following properties:
-     *    * level - (number between 1 and 3 with severity information)
-     *    * message - (human readable message)
-     *    * node - (reference to the offending node)
-     *
-     * @param int $options
-     * @return array
-     */
-    public function validate($options = 0) {
-
-        return array();
-
-    }
-
-    /* {{{ IteratorAggregator interface */
-
-    /**
-     * Returns the iterator for this object
-     *
-     * @return ElementList
-     */
-    public function getIterator() {
-
-        if (!is_null($this->iterator))
-            return $this->iterator;
-
-        return new ElementList(array($this));
-
-    }
-
-    /**
-     * Sets the overridden iterator
-     *
-     * Note that this is not actually part of the iterator interface
-     *
-     * @param ElementList $iterator
-     * @return void
-     */
-    public function setIterator(ElementList $iterator) {
-
-        $this->iterator = $iterator;
-
-    }
-
-    /* }}} */
-
-    /* {{{ Countable interface */
-
-    /**
-     * Returns the number of elements
-     *
-     * @return int
-     */
-    public function count() {
-
-        $it = $this->getIterator();
-        return $it->count();
-
-    }
-
-    /* }}} */
-
-    /* {{{ ArrayAccess Interface */
-
-
-    /**
-     * Checks if an item exists through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return bool
-     */
-    public function offsetExists($offset) {
-
-        $iterator = $this->getIterator();
-        return $iterator->offsetExists($offset);
-
-    }
-
-    /**
-     * Gets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return mixed
-     */
-    public function offsetGet($offset) {
-
-        $iterator = $this->getIterator();
-        return $iterator->offsetGet($offset);
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($offset,$value) {
-
-        $iterator = $this->getIterator();
-        $iterator->offsetSet($offset,$value);
-
-    // @codeCoverageIgnoreStart
-    //
-    // This method always throws an exception, so we ignore the closing
-    // brace
-    }
-    // @codeCoverageIgnoreEnd
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return void
-     */
-    public function offsetUnset($offset) {
-
-        $iterator = $this->getIterator();
-        $iterator->offsetUnset($offset);
-
-    // @codeCoverageIgnoreStart
-    //
-    // This method always throws an exception, so we ignore the closing
-    // brace
-    }
-    // @codeCoverageIgnoreEnd
-
-    /* }}} */
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php b/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php
deleted file mode 100644
index 87445179..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * VObject Parameter
- *
- * This class represents a parameter. A parameter is always tied to a property.
- * In the case of:
- *   DTSTART;VALUE=DATE:20101108
- * VALUE=DATE would be the parameter name and value.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Parameter extends Node {
-
-    /**
-     * Parameter name
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Parameter value
-     *
-     * @var string
-     */
-    public $value;
-
-    /**
-     * Sets up the object
-     *
-     * @param string $name
-     * @param string $value
-     */
-    public function __construct($name, $value = null) {
-
-        if (!is_scalar($value) && !is_null($value)) {
-            throw new \InvalidArgumentException('The value argument must be a scalar value or null');
-        }
-
-        $this->name = strtoupper($name);
-        $this->value = $value;
-
-    }
-
-    /**
-     * Returns the parameter's internal value.
-     *
-     * @return string
-     */
-    public function getValue() {
-
-        return $this->value;
-
-    }
-
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        if (is_null($this->value)) {
-            return $this->name;
-        }
-        $src = array(
-            '\\',
-            "\n",
-        );
-        $out = array(
-            '\\\\',
-            '\n',
-        );
-
-        // quote parameters according to RFC 5545, Section 3.2
-        $quotes = '';
-        if (preg_match('/[:;,]/', $this->value)) {
-            $quotes = '"';
-        }
-
-        return $this->name . '=' . $quotes . str_replace($src, $out, $this->value) . $quotes;
-
-    }
-
-    /**
-     * Called when this object is being cast to a string
-     *
-     * @return string
-     */
-    public function __toString() {
-
-        return $this->value;
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/ParseException.php b/plugins/libcalendaring/lib/Sabre/VObject/ParseException.php
deleted file mode 100644
index 66b49c60..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/ParseException.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * Exception thrown by Reader if an invalid object was attempted to be parsed.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class ParseException extends \Exception { }
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Property.php b/plugins/libcalendaring/lib/Sabre/VObject/Property.php
deleted file mode 100644
index 8be67e25..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Property.php
+++ /dev/null
@@ -1,453 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * VObject Property
- *
- * A property in VObject is usually in the form PARAMNAME:paramValue.
- * An example is : SUMMARY:Weekly meeting
- *
- * Properties can also have parameters:
- * SUMMARY;LANG=en:Weekly meeting.
- *
- * Parameters can be accessed using the ArrayAccess interface.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Property extends Node {
-
-    /**
-     * Propertyname
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Group name
-     *
-     * This may be something like 'HOME' for vcards.
-     *
-     * @var string
-     */
-    public $group;
-
-    /**
-     * Property parameters
-     *
-     * @var array
-     */
-    public $parameters = array();
-
-    /**
-     * Property value
-     *
-     * @var string
-     */
-    public $value;
-
-    /**
-     * If properties are added to this map, they will be automatically mapped
-     * to their respective classes, if parsed by the reader or constructed with
-     * the 'create' method.
-     *
-     * @var array
-     */
-    static public $classMap = array(
-        'COMPLETED'     => 'Sabre\\VObject\\Property\\DateTime',
-        'CREATED'       => 'Sabre\\VObject\\Property\\DateTime',
-        'DTEND'         => 'Sabre\\VObject\\Property\\DateTime',
-        'DTSTAMP'       => 'Sabre\\VObject\\Property\\DateTime',
-        'DTSTART'       => 'Sabre\\VObject\\Property\\DateTime',
-        'DUE'           => 'Sabre\\VObject\\Property\\DateTime',
-        'EXDATE'        => 'Sabre\\VObject\\Property\\MultiDateTime',
-        'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\DateTime',
-        'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\DateTime',
-        'TRIGGER'       => 'Sabre\\VObject\\Property\\DateTime',
-        'N'             => 'Sabre\\VObject\\Property\\Compound',
-        'ORG'           => 'Sabre\\VObject\\Property\\Compound',
-        'ADR'           => 'Sabre\\VObject\\Property\\Compound',
-        'CATEGORIES'    => 'Sabre\\VObject\\Property\\Compound',
-    );
-
-    /**
-     * Creates the new property by name, but in addition will also see if
-     * there's a class mapped to the property name.
-     *
-     * Parameters can be specified with the optional third argument. Parameters
-     * must be a key->value map of the parameter name, and value. If the value
-     * is specified as an array, it is assumed that multiple parameters with
-     * the same name should be added.
-     *
-     * @param string $name
-     * @param string $value
-     * @param array $parameters
-     * @return Property
-     */
-    static public function create($name, $value = null, array $parameters = array()) {
-
-        $name = strtoupper($name);
-        $shortName = $name;
-        $group = null;
-        if (strpos($shortName,'.')!==false) {
-            list($group, $shortName) = explode('.', $shortName);
-        }
-
-        if (isset(self::$classMap[$shortName])) {
-            return new self::$classMap[$shortName]($name, $value, $parameters);
-        } else {
-            return new self($name, $value, $parameters);
-        }
-
-    }
-
-    /**
-     * Creates a new property object
-     *
-     * Parameters can be specified with the optional third argument. Parameters
-     * must be a key->value map of the parameter name, and value. If the value
-     * is specified as an array, it is assumed that multiple parameters with
-     * the same name should be added.
-     *
-     * @param string $name
-     * @param string $value
-     * @param array $parameters
-     */
-    public function __construct($name, $value = null, array $parameters = array()) {
-
-        if (!is_scalar($value) && !is_null($value)) {
-            throw new \InvalidArgumentException('The value argument must be scalar or null');
-        }
-
-        $name = strtoupper($name);
-        $group = null;
-        if (strpos($name,'.')!==false) {
-            list($group, $name) = explode('.', $name);
-        }
-        $this->name = $name;
-        $this->group = $group;
-        $this->setValue($value);
-
-        foreach($parameters as $paramName => $paramValues) {
-
-            if (!is_array($paramValues)) {
-                $paramValues = array($paramValues);
-            }
-
-            foreach($paramValues as $paramValue) {
-                $this->add($paramName, $paramValue);
-            }
-
-        }
-
-    }
-
-    /**
-     * Updates the internal value
-     *
-     * @param string $value
-     * @return void
-     */
-    public function setValue($value) {
-
-        $this->value = $value;
-
-    }
-
-    /**
-     * Returns the internal value
-     *
-     * @param string $value
-     * @return string
-     */
-    public function getValue() {
-
-        return $this->value;
-
-    }
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        $str = $this->name;
-        if ($this->group) $str = $this->group . '.' . $this->name;
-
-        foreach($this->parameters as $param) {
-
-            $str.=';' . $param->serialize();
-
-        }
-
-        $src = array(
-            '\\',
-            "\n",
-            "\r",
-        );
-        $out = array(
-            '\\\\',
-            '\n',
-            '',
-        );
-
-        // avoid double-escaping of \, and \; from Compound properties
-        if (method_exists($this, 'setParts')) {
-            $src[] = '\\\\,';
-            $out[] = '\\,';
-            $src[] = '\\\\;';
-            $out[] = '\\;';
-        }
-
-        $str.=':' . str_replace($src, $out, $this->value);
-
-        $out = '';
-        while(strlen($str)>0) {
-            if (strlen($str)>75) {
-                $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
-                $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
-            } else {
-                $out.=$str . "\r\n";
-                $str='';
-                break;
-            }
-        }
-
-        return $out;
-
-    }
-
-    /**
-     * Adds a new componenten or element
-     *
-     * You can call this method with the following syntaxes:
-     *
-     * add(Parameter $element)
-     * add(string $name, $value)
-     *
-     * The first version adds an Parameter
-     * The second adds a property as a string.
-     *
-     * @param mixed $item
-     * @param mixed $itemValue
-     * @return void
-     */
-    public function add($item, $itemValue = null) {
-
-        if ($item instanceof Parameter) {
-            if (!is_null($itemValue)) {
-                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
-            }
-            $item->parent = $this;
-            $this->parameters[] = $item;
-        } elseif(is_string($item)) {
-
-            $parameter = new Parameter($item,$itemValue);
-            $parameter->parent = $this;
-            $this->parameters[] = $parameter;
-
-        } else {
-
-            throw new \InvalidArgumentException('The first argument must either be a Node a string');
-
-        }
-
-    }
-
-    /* ArrayAccess interface {{{ */
-
-    /**
-     * Checks if an array element exists
-     *
-     * @param mixed $name
-     * @return bool
-     */
-    public function offsetExists($name) {
-
-        if (is_int($name)) return parent::offsetExists($name);
-
-        $name = strtoupper($name);
-
-        foreach($this->parameters as $parameter) {
-            if ($parameter->name == $name) return true;
-        }
-        return false;
-
-    }
-
-    /**
-     * Returns a parameter, or parameter list.
-     *
-     * @param string $name
-     * @return Node
-     */
-    public function offsetGet($name) {
-
-        if (is_int($name)) return parent::offsetGet($name);
-        $name = strtoupper($name);
-
-        $result = array();
-        foreach($this->parameters as $parameter) {
-            if ($parameter->name == $name)
-                $result[] = $parameter;
-        }
-
-        if (count($result)===0) {
-            return null;
-        } elseif (count($result)===1) {
-            return $result[0];
-        } else {
-            $result[0]->setIterator(new ElementList($result));
-            return $result[0];
-        }
-
-    }
-
-    /**
-     * Creates a new parameter
-     *
-     * @param string $name
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($name, $value) {
-
-        if (is_int($name)) parent::offsetSet($name, $value);
-
-        if (is_scalar($value)) {
-            if (!is_string($name))
-                throw new \InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.');
-
-            $this->offsetUnset($name);
-            $parameter = new Parameter($name, $value);
-            $parameter->parent = $this;
-            $this->parameters[] = $parameter;
-
-        } elseif ($value instanceof Parameter) {
-            if (!is_null($name))
-                throw new \InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a \\Sabre\\VObject\\Parameter. Add using $array[]=$parameterObject.');
-
-            $value->parent = $this;
-            $this->parameters[] = $value;
-        } else {
-            throw new \InvalidArgumentException('You can only add parameters to the property object');
-        }
-
-    }
-
-    /**
-     * Removes one or more parameters with the specified name
-     *
-     * @param string $name
-     * @return void
-     */
-    public function offsetUnset($name) {
-
-        if (is_int($name)) parent::offsetUnset($name);
-        $name = strtoupper($name);
-
-        foreach($this->parameters as $key=>$parameter) {
-            if ($parameter->name == $name) {
-                $parameter->parent = null;
-                unset($this->parameters[$key]);
-            }
-
-        }
-
-    }
-
-    /* }}} */
-
-    /**
-     * Called when this object is being cast to a string
-     *
-     * @return string
-     */
-    public function __toString() {
-
-        return (string)$this->value;
-
-    }
-
-    /**
-     * This method is automatically called when the object is cloned.
-     * Specifically, this will ensure all child elements are also cloned.
-     *
-     * @return void
-     */
-    public function __clone() {
-
-        foreach($this->parameters as $key=>$child) {
-            $this->parameters[$key] = clone $child;
-            $this->parameters[$key]->parent = $this;
-        }
-
-    }
-
-    /**
-     * Validates the node for correctness.
-     *
-     * The following options are supported:
-     *   - Node::REPAIR - If something is broken, and automatic repair may
-     *                    be attempted.
-     *
-     * An array is returned with warnings.
-     *
-     * Every item in the array has the following properties:
-     *    * level - (number between 1 and 3 with severity information)
-     *    * message - (human readable message)
-     *    * node - (reference to the offending node)
-     *
-     * @param int $options
-     * @return array
-     */
-    public function validate($options = 0) {
-
-        $warnings = array();
-
-        // Checking if our value is UTF-8
-        if (!StringUtil::isUTF8($this->value)) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'Property is not valid UTF-8!',
-                'node' => $this,
-            );
-            if ($options & self::REPAIR) {
-                $this->value = StringUtil::convertToUTF8($this->value);
-            }
-        }
-
-        // Checking if the propertyname does not contain any invalid bytes.
-        if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
-            $warnings[] = array(
-                'level' => 1,
-                'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
-                'node' => $this,
-            );
-            if ($options & self::REPAIR) {
-                // Uppercasing and converting underscores to dashes.
-                $this->name = strtoupper(
-                    str_replace('_', '-', $this->name)
-                );
-                // Removing every other invalid character
-                $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
-
-            }
-
-        }
-
-        // Validating inner parameters
-        foreach($this->parameters as $param) {
-            $warnings = array_merge($warnings, $param->validate($options));
-        }
-
-        return $warnings;
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Property/Compound.php b/plugins/libcalendaring/lib/Sabre/VObject/Property/Compound.php
deleted file mode 100644
index 26f09006..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Property/Compound.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Property;
-
-use Sabre\VObject;
-
-/**
- * Compound property.
- *
- * This class adds (de)serialization of compound properties to/from arrays.
- *
- * Currently the following properties from RFC 6350 are mapped to use this
- * class:
- *
- *  N:          Section 6.2.2
- *  ADR:        Section 6.3.1
- *  ORG:        Section 6.6.4
- *  CATEGORIES: Section 6.7.1
- *
- * In order to use this correctly, you must call setParts and getParts to
- * retrieve and modify dates respectively.
- *
- * @author Thomas Tanghus (http://tanghus.net/)
- * @author Lars Kneschke
- * @author Evert Pot (http://evertpot.com/)
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Compound extends VObject\Property {
-
-    /**
-     * If property names are added to this map, they will be (de)serialised as arrays
-     * using the getParts() and setParts() methods.
-     * The keys are the property names, values are delimiter chars.
-     *
-     * @var array
-     */
-    static public $delimiterMap = array(
-        'N'           =>    ';',
-        'ADR'         =>    ';',
-        'ORG'         =>    ';',
-        'CATEGORIES'  =>    ',',
-    );
-
-    /**
-     * The currently used delimiter.
-     *
-     * @var string
-     */
-    protected $delimiter = null;
-
-    /**
-     * Get a compound value as an array.
-     *
-     * @param $name string
-     * @return array
-     */
-    public function getParts() {
-
-        if (is_null($this->value)) {
-            return array();
-        }
-
-        $delimiter = $this->getDelimiter();
-
-        // split by any $delimiter which is NOT prefixed by a slash.
-        // Note that this is not a a perfect solution. If a value is prefixed
-        // by two slashes, it should actually be split anyway.
-        //
-        // Hopefully we can fix this better in a future version, where we can
-        // break compatibility a bit.
-        $compoundValues = preg_split("/(?<!\\\)$delimiter/", $this->value);
-
-        // remove slashes from any semicolon and comma left escaped in the single values
-        $compoundValues = array_map(
-            function($val) {
-                return strtr($val, array('\,' => ',', '\;' => ';'));
-        }, $compoundValues);
-
-        return $compoundValues;
-
-    }
-
-    /**
-     * Returns the delimiter for this property.
-     *
-     * @return string
-     */
-    public function getDelimiter() {
-
-        if (!$this->delimiter) {
-            if (isset(self::$delimiterMap[$this->name])) {
-                $this->delimiter = self::$delimiterMap[$this->name];
-            } else {
-                // To be a bit future proof, we are going to default the
-                // delimiter to ;
-                $this->delimiter = ';';
-            }
-        }
-        return $this->delimiter;
-
-    }
-
-    /**
-     * Set a compound value as an array.
-     *
-     *
-     * @param $name string
-     * @return array
-     */
-    public function setParts(array $values) {
-
-        // add slashes to all semicolons and commas in the single values
-        $values = array_map(
-            function($val) {
-                return strtr($val, array(',' => '\,', ';' => '\;'));
-            }, $values);
-
-        $this->setValue(
-            implode($this->getDelimiter(), $values)
-        );
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Property/DateTime.php b/plugins/libcalendaring/lib/Sabre/VObject/Property/DateTime.php
deleted file mode 100644
index 95e9b020..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Property/DateTime.php
+++ /dev/null
@@ -1,245 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Property;
-
-use Sabre\VObject;
-
-/**
- * DateTime property
- *
- * This element is used for iCalendar properties such as the DTSTART property.
- * It basically provides a few helper functions that make it easier to deal
- * with these. It supports both DATE-TIME and DATE values.
- *
- * In order to use this correctly, you must call setDateTime and getDateTime to
- * retrieve and modify dates respectively.
- *
- * If you use the 'value' or properties directly, this object does not keep
- * reference and results might appear incorrectly.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class DateTime extends VObject\Property {
-
-    /**
-     * Local 'floating' time
-     */
-    const LOCAL = 1;
-
-    /**
-     * UTC-based time
-     */
-    const UTC = 2;
-
-    /**
-     * Local time plus timezone
-     */
-    const LOCALTZ = 3;
-
-    /**
-     * Only a date, time is ignored
-     */
-    const DATE = 4;
-
-    /**
-     * DateTime representation
-     *
-     * @var \DateTime
-     */
-    protected $dateTime;
-
-    /**
-     * dateType
-     *
-     * @var int
-     */
-    protected $dateType;
-
-    /**
-     * Updates the Date and Time.
-     *
-     * @param \DateTime $dt
-     * @param int $dateType
-     * @return void
-     */
-    public function setDateTime(\DateTime $dt, $dateType = self::LOCALTZ) {
-
-        switch($dateType) {
-
-            case self::LOCAL :
-                $this->setValue($dt->format('Ymd\\THis'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case self::UTC :
-                $dt->setTimeZone(new \DateTimeZone('UTC'));
-                $this->setValue($dt->format('Ymd\\THis\\Z'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case self::LOCALTZ :
-                $this->setValue($dt->format('Ymd\\THis'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                $this->offsetSet('TZID', $dt->getTimeZone()->getName());
-                break;
-            case self::DATE :
-                $this->setValue($dt->format('Ymd'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE');
-                break;
-            default :
-                throw new \InvalidArgumentException('You must pass a valid dateType constant');
-
-        }
-        $this->dateTime = $dt;
-        $this->dateType = $dateType;
-
-    }
-
-    /**
-     * Returns the current DateTime value.
-     *
-     * If no value was set, this method returns null.
-     *
-     * @return \DateTime|null
-     */
-    public function getDateTime() {
-
-        if ($this->dateTime)
-            return $this->dateTime;
-
-        list(
-            $this->dateType,
-            $this->dateTime
-        ) = self::parseData($this->value, $this);
-        return $this->dateTime;
-
-    }
-
-    /**
-     * Returns the type of Date format.
-     *
-     * This method returns one of the format constants. If no date was set,
-     * this method will return null.
-     *
-     * @return int|null
-     */
-    public function getDateType() {
-
-        if ($this->dateType)
-            return $this->dateType;
-
-        list(
-            $this->dateType,
-            $this->dateTime,
-        ) = self::parseData($this->value, $this);
-        return $this->dateType;
-
-    }
-
-    /**
-     * This method will return true, if the property had a date and a time, as
-     * opposed to only a date.
-     *
-     * @return bool
-     */
-    public function hasTime() {
-
-        return $this->getDateType()!==self::DATE;
-
-    }
-
-    /**
-     * Parses the internal data structure to figure out what the current date
-     * and time is.
-     *
-     * The returned array contains two elements:
-     *   1. A 'DateType' constant (as defined on this class), or null.
-     *   2. A DateTime object (or null)
-     *
-     * @param string|null $propertyValue The string to parse (yymmdd or
-     *                                   ymmddThhmmss, etc..)
-     * @param \Sabre\VObject\Property|null $property The instance of the
-     *                                              property we're parsing.
-     * @return array
-     */
-    static public function parseData($propertyValue, VObject\Property $property = null) {
-
-        if (is_null($propertyValue)) {
-            return array(null, null);
-        }
-
-        $date = '(?P<year>[1-2][0-9]{3})(?P<month>[0-1][0-9])(?P<date>[0-3][0-9])';
-        $time = '(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9])';
-        $regex = "/^$date(T$time(?P<isutc>Z)?)?$/";
-
-        if (!preg_match($regex, $propertyValue, $matches)) {
-            throw new \InvalidArgumentException($propertyValue . ' is not a valid \DateTime or Date string');
-        }
-
-        if (!isset($matches['hour'])) {
-            // Date-only
-            return array(
-                self::DATE,
-                new \DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00', new \DateTimeZone('UTC')),
-            );
-        }
-
-        $dateStr =
-            $matches['year'] .'-' .
-            $matches['month'] . '-' .
-            $matches['date'] . ' ' .
-            $matches['hour'] . ':' .
-            $matches['minute'] . ':' .
-            $matches['second'];
-
-        if (isset($matches['isutc'])) {
-            $dt = new \DateTime($dateStr,new \DateTimeZone('UTC'));
-            $dt->setTimeZone(new \DateTimeZone('UTC'));
-            return array(
-                self::UTC,
-                $dt
-            );
-        }
-
-        // Finding the timezone.
-        $tzid = $property['TZID'];
-        if (!$tzid) {
-            // This was a floating time string. This implies we use the
-            // timezone from date_default_timezone_set / date.timezone ini
-            // setting.
-            return array(
-                self::LOCAL,
-                new \DateTime($dateStr)
-            );
-        }
-
-        // To look up the timezone, we must first find the VCALENDAR component.
-        $root = $property;
-        while($root->parent) {
-            $root = $root->parent;
-        }
-        if ($root->name === 'VCALENDAR') {
-            $tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid, $root);
-        } else {
-            $tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid);
-        }
-
-        $dt = new \DateTime($dateStr, $tz);
-        $dt->setTimeZone($tz);
-
-        return array(
-            self::LOCALTZ,
-            $dt
-        );
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Property/MultiDateTime.php b/plugins/libcalendaring/lib/Sabre/VObject/Property/MultiDateTime.php
deleted file mode 100644
index f01491b6..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Property/MultiDateTime.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Property;
-
-use Sabre\VObject;
-
-/**
- * Multi-DateTime property
- *
- * This element is used for iCalendar properties such as the EXDATE property.
- * It basically provides a few helper functions that make it easier to deal
- * with these. It supports both DATE-TIME and DATE values.
- *
- * In order to use this correctly, you must call setDateTimes and getDateTimes
- * to retrieve and modify dates respectively.
- *
- * If you use the 'value' or properties directly, this object does not keep
- * reference and results might appear incorrectly.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class MultiDateTime extends VObject\Property {
-
-    /**
-     * DateTime representation
-     *
-     * @var DateTime[]
-     */
-    protected $dateTimes;
-
-    /**
-     * dateType
-     *
-     * This is one of the Sabre\VObject\Property\DateTime constants.
-     *
-     * @var int
-     */
-    protected $dateType;
-
-    /**
-     * Updates the value
-     *
-     * @param array $dt Must be an array of DateTime objects.
-     * @param int $dateType
-     * @return void
-     */
-    public function setDateTimes(array $dt, $dateType = VObject\Property\DateTime::LOCALTZ) {
-
-        foreach($dt as $i)
-            if (!$i instanceof \DateTime)
-                throw new \InvalidArgumentException('You must pass an array of DateTime objects');
-
-        $this->offsetUnset('VALUE');
-        $this->offsetUnset('TZID');
-        switch($dateType) {
-
-            case DateTime::LOCAL :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd\\THis');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case DateTime::UTC :
-                $val = array();
-                foreach($dt as $i) {
-                    $i->setTimeZone(new \DateTimeZone('UTC'));
-                    $val[] = $i->format('Ymd\\THis\\Z');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case DateTime::LOCALTZ :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd\\THis');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                $this->offsetSet('TZID', $dt[0]->getTimeZone()->getName());
-                break;
-            case DateTime::DATE :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE');
-                break;
-            default :
-                throw new \InvalidArgumentException('You must pass a valid dateType constant');
-
-        }
-        $this->dateTimes = $dt;
-        $this->dateType = $dateType;
-
-    }
-
-    /**
-     * Returns the current DateTime value.
-     *
-     * If no value was set, this method returns null.
-     *
-     * @return array|null
-     */
-    public function getDateTimes() {
-
-        if ($this->dateTimes)
-            return $this->dateTimes;
-
-        $dts = array();
-
-        if (!$this->value) {
-            $this->dateTimes = null;
-            $this->dateType = null;
-            return null;
-        }
-
-        foreach(explode(',',$this->value) as $val) {
-            list(
-                $type,
-                $dt
-            ) = DateTime::parseData($val, $this);
-            $dts[] = $dt;
-            $this->dateType = $type;
-        }
-        $this->dateTimes = $dts;
-        return $this->dateTimes;
-
-    }
-
-    /**
-     * Returns the type of Date format.
-     *
-     * This method returns one of the format constants. If no date was set,
-     * this method will return null.
-     *
-     * @return int|null
-     */
-    public function getDateType() {
-
-        if ($this->dateType)
-            return $this->dateType;
-
-        if (!$this->value) {
-            $this->dateTimes = null;
-            $this->dateType = null;
-            return null;
-        }
-
-        $dts = array();
-        foreach(explode(',',$this->value) as $val) {
-            list(
-                $type,
-                $dt
-            ) = DateTime::parseData($val, $this);
-            $dts[] = $dt;
-            $this->dateType = $type;
-        }
-        $this->dateTimes = $dts;
-        return $this->dateType;
-
-    }
-
-    /**
-     * This method will return true, if the property had a date and a time, as
-     * opposed to only a date.
-     *
-     * @return bool
-     */
-    public function hasTime() {
-
-        return $this->getDateType()!==DateTime::DATE;
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Reader.php b/plugins/libcalendaring/lib/Sabre/VObject/Reader.php
deleted file mode 100644
index a001b2bf..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Reader.php
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * VCALENDAR/VCARD reader
- *
- * This class reads the vobject file, and returns a full element tree.
- *
- * TODO: this class currently completely works 'statically'. This is pointless,
- * and defeats OOP principals. Needs refactoring in a future version.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Reader {
-
-    /**
-     * If this option is passed to the reader, it will be less strict about the
-     * validity of the lines.
-     *
-     * Currently using this option just means, that it will accept underscores
-     * in property names.
-     */
-    const OPTION_FORGIVING = 1;
-
-    /**
-     * If this option is turned on, any lines we cannot parse will be ignored
-     * by the reader.
-     */
-    const OPTION_IGNORE_INVALID_LINES = 2;
-
-    /**
-     * Parses the file and returns the top component
-     *
-     * The options argument is a bitfield. Pass any of the OPTIONS constant to
-     * alter the parsers' behaviour.
-     *
-     * @param string $data
-     * @param int $options
-     * @return Node
-     */
-    static function read($data, $options = 0) {
-
-        // Normalizing newlines
-        $data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
-
-        $lines = explode("\n", $data);
-
-        // Unfolding lines
-        $lines2 = array();
-        foreach($lines as $line) {
-
-            // Skipping empty lines
-            if (!$line) continue;
-
-            if ($line[0]===" " || $line[0]==="\t") {
-                $lines2[count($lines2)-1].=substr($line,1);
-            } else {
-                $lines2[] = $line;
-            }
-
-        }
-
-        unset($lines);
-
-        reset($lines2);
-
-        return self::readLine($lines2, $options);
-
-    }
-
-    /**
-     * Reads and parses a single line.
-     *
-     * This method receives the full array of lines. The array pointer is used
-     * to traverse.
-     *
-     * This method returns null if an invalid line was encountered, and the
-     * IGNORE_INVALID_LINES option was turned on.
-     *
-     * @param array $lines
-     * @param int $options See the OPTIONS constants.
-     * @return Node
-     */
-    static private function readLine(&$lines, $options = 0) {
-
-        $line = current($lines);
-        $lineNr = key($lines);
-        next($lines);
-
-        // Components
-        if (strtoupper(substr($line,0,6)) === "BEGIN:") {
-
-            $componentName = strtoupper(substr($line,6));
-            $obj = Component::create($componentName);
-
-            $nextLine = current($lines);
-
-            while(strtoupper(substr($nextLine,0,4))!=="END:") {
-
-                $parsedLine = self::readLine($lines, $options);
-                $nextLine = current($lines);
-
-                if (is_null($parsedLine)) {
-                    continue;
-                }
-                $obj->add($parsedLine);
-
-                if ($nextLine===false)
-                    throw new ParseException('Invalid VObject. Document ended prematurely.');
-
-            }
-
-            // Checking component name of the 'END:' line.
-            if (substr($nextLine,4)!==$obj->name) {
-                throw new ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"');
-            }
-            next($lines);
-
-            return $obj;
-
-        }
-
-        // Properties
-        //$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches);
-
-        if ($options & self::OPTION_FORGIVING) {
-            $token = '[A-Z0-9-\._]+';
-        } else {
-            $token = '[A-Z0-9-\.]+';
-        }
-        $parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
-        $regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
-
-        $result = preg_match($regex,$line,$matches);
-
-        if (!$result) {
-            if ($options & self::OPTION_IGNORE_INVALID_LINES) {
-                return null;
-            } else {
-                throw new ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format');
-            }
-        }
-
-        $propertyName = strtoupper($matches['name']);
-        $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n))#',function($matches) {
-            if ($matches[2]==='n' || $matches[2]==='N') {
-                return "\n";
-            } else {
-                return $matches[2];
-            }
-        }, $matches['value']);
-
-        $obj = Property::create($propertyName, $propertyValue);
-
-        if ($matches['parameters']) {
-
-            foreach(self::readParameters($matches['parameters']) as $param) {
-                $obj->add($param);
-            }
-
-        }
-
-        return $obj;
-
-
-    }
-
-    /**
-     * Reads a parameter list from a property
-     *
-     * This method returns an array of Parameter
-     *
-     * @param string $parameters
-     * @return array
-     */
-    static private function readParameters($parameters) {
-
-        $token = '[A-Z0-9-]+';
-
-        $paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")';
-
-        $regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
-        preg_match_all($regex, $parameters, $matches,  PREG_SET_ORDER);
-
-        $params = array();
-        foreach($matches as $match) {
-
-            if (!isset($match['paramValue'])) {
-
-                $value = null;
-
-            } else {
-
-                $value = $match['paramValue'];
-
-                if (isset($value[0]) && $value[0]==='"') {
-                    // Stripping quotes, if needed
-                    $value = substr($value,1,strlen($value)-2);
-                }
-
-                $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
-                    if ($matches[2]==='n' || $matches[2]==='N') {
-                        return "\n";
-                    } else {
-                        return $matches[2];
-                    }
-                }, $value);
-
-            }
-
-            $params[] = new Parameter($match['paramName'], $value);
-
-        }
-
-        return $params;
-
-    }
-
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php b/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php
deleted file mode 100644
index 8bd4ed22..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php
+++ /dev/null
@@ -1,1144 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * This class is used to determine new for a recurring event, when the next
- * events occur.
- *
- * This iterator may loop infinitely in the future, therefore it is important
- * that if you use this class, you set hard limits for the amount of iterations
- * you want to handle.
- *
- * Note that currently there is not full support for the entire iCalendar
- * specification, as it's very complex and contains a lot of permutations
- * that's not yet used very often in software.
- *
- * For the focus has been on features as they actually appear in Calendaring
- * software, but this may well get expanded as needed / on demand
- *
- * The following RRULE properties are supported
- *   * UNTIL
- *   * INTERVAL
- *   * COUNT
- *   * FREQ=DAILY
- *     * BYDAY
- *     * BYHOUR
- *   * FREQ=WEEKLY
- *     * BYDAY
- *     * BYHOUR
- *     * WKST
- *   * FREQ=MONTHLY
- *     * BYMONTHDAY
- *     * BYDAY
- *     * BYSETPOS
- *   * FREQ=YEARLY
- *     * BYMONTH
- *     * BYMONTHDAY (only if BYMONTH is also set)
- *     * BYDAY (only if BYMONTH is also set)
- *
- * Anything beyond this is 'undefined', which means that it may get ignored, or
- * you may get unexpected results. The effect is that in some applications the
- * specified recurrence may look incorrect, or is missing.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class RecurrenceIterator implements \Iterator {
-
-    /**
-     * The initial event date
-     *
-     * @var DateTime
-     */
-    public $startDate;
-
-    /**
-     * The end-date of the initial event
-     *
-     * @var DateTime
-     */
-    public $endDate;
-
-    /**
-     * The 'current' recurrence.
-     *
-     * This will be increased for every iteration.
-     *
-     * @var DateTime
-     */
-    public $currentDate;
-
-
-    /**
-     * List of dates that are excluded from the rules.
-     *
-     * This list contains the items that have been overriden by the EXDATE
-     * property.
-     *
-     * @var array
-     */
-    public $exceptionDates = array();
-
-    /**
-     * Base event
-     *
-     * @var Component\VEvent
-     */
-    public $baseEvent;
-
-    /**
-     * List of dates that are overridden by other events.
-     * Similar to $overriddenEvents, but this just contains the original dates.
-     *
-     * @var array
-     */
-    public $overriddenDates = array();
-
-    /**
-     * list of events that are 'overridden'.
-     *
-     * This is an array of Component\VEvent objects.
-     *
-     * @var array
-     */
-    public $overriddenEvents = array();
-
-    /**
-     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
-     * yearly.
-     *
-     * @var string
-     */
-    public $frequency;
-
-    /**
-     * The last instance of this recurrence, inclusively
-     *
-     * @var DateTime|null
-     */
-    public $until;
-
-    /**
-     * The number of recurrences, or 'null' if infinitely recurring.
-     *
-     * @var int
-     */
-    public $count;
-
-    /**
-     * The interval.
-     *
-     * If for example frequency is set to daily, interval = 2 would mean every
-     * 2 days.
-     *
-     * @var int
-     */
-    public $interval = 1;
-
-    /**
-     * Which seconds to recur.
-     *
-     * This is an array of integers (between 0 and 60)
-     *
-     * @var array
-     */
-    public $bySecond;
-
-    /**
-     * Which minutes to recur
-     *
-     * This is an array of integers (between 0 and 59)
-     *
-     * @var array
-     */
-    public $byMinute;
-
-    /**
-     * Which hours to recur
-     *
-     * This is an array of integers (between 0 and 23)
-     *
-     * @var array
-     */
-    public $byHour;
-
-    /**
-     * Which weekdays to recur.
-     *
-     * This is an array of weekdays
-     *
-     * This may also be preceeded by a positive or negative integer. If present,
-     * this indicates the nth occurrence of a specific day within the monthly or
-     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
-     * the month, or year.
-     *
-     * @var array
-     */
-    public $byDay;
-
-    /**
-     * Which days of the month to recur
-     *
-     * This is an array of days of the months (1-31). The value can also be
-     * negative. -5 for instance means the 5th last day of the month.
-     *
-     * @var array
-     */
-    public $byMonthDay;
-
-    /**
-     * Which days of the year to recur.
-     *
-     * This is an array with days of the year (1 to 366). The values can also
-     * be negative. For instance, -1 will always represent the last day of the
-     * year. (December 31st).
-     *
-     * @var array
-     */
-    public $byYearDay;
-
-    /**
-     * Which week numbers to recur.
-     *
-     * This is an array of integers from 1 to 53. The values can also be
-     * negative. -1 will always refer to the last week of the year.
-     *
-     * @var array
-     */
-    public $byWeekNo;
-
-    /**
-     * Which months to recur
-     *
-     * This is an array of integers from 1 to 12.
-     *
-     * @var array
-     */
-    public $byMonth;
-
-    /**
-     * Which items in an existing st to recur.
-     *
-     * These numbers work together with an existing by* rule. It specifies
-     * exactly which items of the existing by-rule to filter.
-     *
-     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
-     * used to recur the last workday of the month.
-     *
-     * This would be done by setting frequency to 'monthly', byDay to
-     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
-     *
-     * @var array
-     */
-    public $bySetPos;
-
-    /**
-     * When a week starts
-     *
-     * @var string
-     */
-    public $weekStart = 'MO';
-
-    /**
-     * The current item in the list
-     *
-     * @var int
-     */
-    public $counter = 0;
-
-    /**
-     * Simple mapping from iCalendar day names to day numbers
-     *
-     * @var array
-     */
-    private $dayMap = array(
-        'SU' => 0,
-        'MO' => 1,
-        'TU' => 2,
-        'WE' => 3,
-        'TH' => 4,
-        'FR' => 5,
-        'SA' => 6,
-    );
-
-    /**
-     * Mappings between the day number and english day name.
-     *
-     * @var array
-     */
-    private $dayNames = array(
-        0 => 'Sunday',
-        1 => 'Monday',
-        2 => 'Tuesday',
-        3 => 'Wednesday',
-        4 => 'Thursday',
-        5 => 'Friday',
-        6 => 'Saturday',
-    );
-
-    /**
-     * If the current iteration of the event is an overriden event, this
-     * property will hold the VObject
-     *
-     * @var Component
-     */
-    private $currentOverriddenEvent;
-
-    /**
-     * This property may contain the date of the next not-overridden event.
-     * This date is calculated sometimes a bit early, before overridden events
-     * are evaluated.
-     *
-     * @var DateTime
-     */
-    private $nextDate;
-
-    /**
-     * This counts the number of overridden events we've handled so far
-     *
-     * @var int
-     */
-    private $handledOverridden = 0;
-
-    /**
-     * Creates the iterator
-     *
-     * You should pass a VCALENDAR component, as well as the UID of the event
-     * we're going to traverse.
-     *
-     * @param Component $vcal
-     * @param string|null $uid
-     */
-    public function __construct(Component $vcal, $uid=null) {
-
-        if (is_null($uid)) {
-            if ($vcal->name === 'VCALENDAR') {
-                throw new \InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well');
-            }
-            $components = array($vcal);
-            $uid = (string)$vcal->uid;
-        } else {
-            $components = $vcal->select('VEVENT');
-        }
-        foreach($components as $component) {
-            if ((string)$component->uid == $uid) {
-                if (isset($component->{'RECURRENCE-ID'})) {
-                    $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component;
-                    $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime();
-                } else {
-                    $this->baseEvent = $component;
-                }
-            }
-        }
-
-        ksort($this->overriddenEvents);
-
-        if (!$this->baseEvent) {
-            throw new \InvalidArgumentException('Could not find a base event with uid: ' . $uid);
-        }
-
-        $this->startDate = clone $this->baseEvent->DTSTART->getDateTime();
-
-        $this->endDate = null;
-        if (isset($this->baseEvent->DTEND)) {
-            $this->endDate = clone $this->baseEvent->DTEND->getDateTime();
-        } else {
-            $this->endDate = clone $this->startDate;
-            if (isset($this->baseEvent->DURATION)) {
-                $this->endDate->add(DateTimeParser::parse($this->baseEvent->DURATION->value));
-            } elseif ($this->baseEvent->DTSTART->getDateType()===Property\DateTime::DATE) {
-                $this->endDate->modify('+1 day');
-            }
-        }
-        $this->currentDate = clone $this->startDate;
-
-        $rrule = (string)$this->baseEvent->RRULE;
-
-        $parts = explode(';', $rrule);
-
-        // If no rrule was specified, we create a default setting
-        if (!$rrule) {
-            $this->frequency = 'daily';
-            $this->count = 1;
-        } else foreach($parts as $part) {
-
-            list($key, $value) = explode('=', $part, 2);
-
-            switch(strtoupper($key)) {
-
-                case 'FREQ' :
-                    if (!in_array(
-                        strtolower($value),
-                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
-                    )) {
-                        throw new \InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
-
-                    }
-                    $this->frequency = strtolower($value);
-                    break;
-
-                case 'UNTIL' :
-                    $this->until = DateTimeParser::parse($value);
-
-                    // In some cases events are generated with an UNTIL=
-                    // parameter before the actual start of the event.
-                    //
-                    // Not sure why this is happening. We assume that the
-                    // intention was that the event only recurs once.
-                    //
-                    // So we are modifying the parameter so our code doesn't
-                    // break.
-                    if($this->until < $this->baseEvent->DTSTART->getDateTime()) {
-                        $this->until = $this->baseEvent->DTSTART->getDateTime();
-                    }
-                    break;
-
-                case 'COUNT' :
-                    $this->count = (int)$value;
-                    break;
-
-                case 'INTERVAL' :
-                    $this->interval = (int)$value;
-                    if ($this->interval < 1) {
-                        throw new \InvalidArgumentException('INTERVAL in RRULE must be a positive integer!');
-                    }
-                    break;
-
-                case 'BYSECOND' :
-                    $this->bySecond = explode(',', $value);
-                    break;
-
-                case 'BYMINUTE' :
-                    $this->byMinute = explode(',', $value);
-                    break;
-
-                case 'BYHOUR' :
-                    $this->byHour = explode(',', $value);
-                    break;
-
-                case 'BYDAY' :
-                    $this->byDay = explode(',', strtoupper($value));
-                    break;
-
-                case 'BYMONTHDAY' :
-                    $this->byMonthDay = explode(',', $value);
-                    break;
-
-                case 'BYYEARDAY' :
-                    $this->byYearDay = explode(',', $value);
-                    break;
-
-                case 'BYWEEKNO' :
-                    $this->byWeekNo = explode(',', $value);
-                    break;
-
-                case 'BYMONTH' :
-                    $this->byMonth = explode(',', $value);
-                    break;
-
-                case 'BYSETPOS' :
-                    $this->bySetPos = explode(',', $value);
-                    break;
-
-                case 'WKST' :
-                    $this->weekStart = strtoupper($value);
-                    break;
-
-            }
-
-        }
-
-        // Parsing exception dates
-        if (isset($this->baseEvent->EXDATE)) {
-            foreach($this->baseEvent->EXDATE as $exDate) {
-
-                foreach(explode(',', (string)$exDate) as $exceptionDate) {
-
-                    $this->exceptionDates[] =
-                        DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone());
-
-                }
-
-            }
-
-        }
-
-    }
-
-    /**
-     * Returns the current item in the list
-     *
-     * @return DateTime
-     */
-    public function current() {
-
-        if (!$this->valid()) return null;
-        return clone $this->currentDate;
-
-    }
-
-    /**
-     * This method returns the startdate for the current iteration of the
-     * event.
-     *
-     * @return DateTime
-     */
-    public function getDtStart() {
-
-        if (!$this->valid()) return null;
-        return clone $this->currentDate;
-
-    }
-
-    /**
-     * This method returns the enddate for the current iteration of the
-     * event.
-     *
-     * @return DateTime
-     */
-    public function getDtEnd() {
-
-        if (!$this->valid()) return null;
-        $dtEnd = clone $this->currentDate;
-        $dtEnd->add( $this->startDate->diff( $this->endDate ) );
-        return clone $dtEnd;
-
-    }
-
-    /**
-     * Returns a VEVENT object with the updated start and end date.
-     *
-     * Any recurrence information is removed, and this function may return an
-     * 'overridden' event instead.
-     *
-     * This method always returns a cloned instance.
-     *
-     * @return Component\VEvent
-     */
-    public function getEventObject() {
-
-        if ($this->currentOverriddenEvent) {
-            return clone $this->currentOverriddenEvent;
-        }
-        $event = clone $this->baseEvent;
-        unset($event->RRULE);
-        unset($event->EXDATE);
-        unset($event->RDATE);
-        unset($event->EXRULE);
-
-        $event->DTSTART->setDateTime($this->getDTStart(), $event->DTSTART->getDateType());
-        if (isset($event->DTEND)) {
-            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTSTART->getDateType());
-        }
-        if ($this->counter > 0) {
-            $event->{'RECURRENCE-ID'} = (string)$event->DTSTART;
-        }
-
-        return $event;
-
-    }
-
-    /**
-     * Returns the current item number
-     *
-     * @return int
-     */
-    public function key() {
-
-        return $this->counter;
-
-    }
-
-    /**
-     * Whether or not there is a 'next item'
-     *
-     * @return bool
-     */
-    public function valid() {
-
-        if (!is_null($this->count)) {
-            return $this->counter < $this->count;
-        }
-        if (!is_null($this->until) && $this->currentDate > $this->until) {
-
-            // Need to make sure there's no overridden events past the
-            // until date.
-            foreach($this->overriddenEvents as $overriddenEvent) {
-
-                if ($overriddenEvent->DTSTART->getDateTime() >= $this->currentDate) {
-
-                    return true;
-                }
-            }
-            return false;
-        }
-        return true;
-
-    }
-
-    /**
-     * Resets the iterator
-     *
-     * @return void
-     */
-    public function rewind() {
-
-        $this->currentDate = clone $this->startDate;
-        $this->counter = 0;
-
-    }
-
-    /**
-     * This method allows you to quickly go to the next occurrence after the
-     * specified date.
-     *
-     * Note that this checks the current 'endDate', not the 'stardDate'. This
-     * means that if you forward to January 1st, the iterator will stop at the
-     * first event that ends *after* January 1st.
-     *
-     * @param DateTime $dt
-     * @return void
-     */
-    public function fastForward(\DateTime $dt) {
-
-        while($this->valid() && $this->getDTEnd() <= $dt) {
-            $this->next();
-        }
-
-    }
-
-    /**
-     * Returns true if this recurring event never ends.
-     *
-     * @return bool
-     */
-    public function isInfinite() {
-
-        return !$this->count && !$this->until;
-
-    }
-
-    /**
-     * Goes on to the next iteration
-     *
-     * @return void
-     */
-    public function next() {
-
-        $previousStamp = $this->currentDate->getTimeStamp();
-
-        // Finding the next overridden event in line, and storing that for
-        // later use.
-        $overriddenEvent = null;
-        $overriddenDate = null;
-        $this->currentOverriddenEvent = null;
-
-        foreach($this->overriddenEvents as $index=>$event) {
-            if ($index > $previousStamp) {
-                $overriddenEvent = $event;
-                $overriddenDate = clone $event->DTSTART->getDateTime();
-                break;
-            }
-        }
-
-        // If we have a stored 'next date', we will use that.
-        if ($this->nextDate) {
-            if (!$overriddenDate || $this->nextDate < $overriddenDate) {
-                $this->currentDate = $this->nextDate;
-                $currentStamp = $this->currentDate->getTimeStamp();
-                $this->nextDate = null;
-            } else {
-                $this->currentDate = clone $overriddenDate;
-                $this->currentOverriddenEvent = $overriddenEvent;
-            }
-            $this->counter++;
-            return;
-        }
-
-        while(true) {
-
-            // Otherwise, we find the next event in the normal RRULE
-            // sequence.
-            switch($this->frequency) {
-
-                case 'hourly' :
-                    $this->nextHourly();
-                    break;
-
-                case 'daily' :
-                    $this->nextDaily();
-                    break;
-
-                case 'weekly' :
-                    $this->nextWeekly();
-                    break;
-
-                case 'monthly' :
-                    $this->nextMonthly();
-                    break;
-
-                case 'yearly' :
-                    $this->nextYearly();
-                    break;
-
-            }
-            $currentStamp = $this->currentDate->getTimeStamp();
-
-
-            // Checking exception dates
-            foreach($this->exceptionDates as $exceptionDate) {
-                if ($this->currentDate == $exceptionDate) {
-                    $this->counter++;
-                    continue 2;
-                }
-            }
-            foreach($this->overriddenDates as $check) {
-                if ($this->currentDate == $check) {
-                    continue 2;
-                }
-            }
-            break;
-
-        }
-
-
-
-        // Is the date we have actually higher than the next overiddenEvent?
-        if ($overriddenDate && $this->currentDate > $overriddenDate) {
-            $this->nextDate = clone $this->currentDate;
-            $this->currentDate = clone $overriddenDate;
-            $this->currentOverriddenEvent = $overriddenEvent;
-            $this->handledOverridden++;
-        }
-        $this->counter++;
-
-
-        /*
-         * If we have overridden events left in the queue, but our counter is
-         * running out, we should grab one of those.
-         */
-        if (!is_null($overriddenEvent) && !is_null($this->count) && count($this->overriddenEvents) - $this->handledOverridden >= ($this->count - $this->counter)) {
-
-            $this->currentOverriddenEvent = $overriddenEvent;
-            $this->currentDate = clone $overriddenDate;
-            $this->handledOverridden++;
-
-        }
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for hourly frequency.
-     *
-     * @return void
-     */
-    protected function nextHourly() {
-
-        if (!$this->byHour) {
-            $this->currentDate->modify('+' . $this->interval . ' hours');
-            return;
-        }
-    }
-
-    /**
-     * Does the processing for advancing the iterator for daily frequency.
-     *
-     * @return void
-     */
-    protected function nextDaily() {
-
-        if (!$this->byHour && !$this->byDay) {
-            $this->currentDate->modify('+' . $this->interval . ' days');
-            return;
-        }
-
-        if (isset($this->byHour)) {
-            $recurrenceHours = $this->getHours();
-        }
-
-        if (isset($this->byDay)) {
-            $recurrenceDays = $this->getDays();
-        }
-
-        do {
-
-            if ($this->byHour) {
-                if ($this->currentDate->format('G') == '23') {
-                    // to obey the interval rule
-                    $this->currentDate->modify('+' . $this->interval-1 . ' days');
-                }
-
-                $this->currentDate->modify('+1 hours');
-
-            } else {
-                $this->currentDate->modify('+' . $this->interval . ' days');
-
-            }
-
-            // Current day of the week
-            $currentDay = $this->currentDate->format('w');
-
-            // Current hour of the day
-            $currentHour = $this->currentDate->format('G');
-
-        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for weekly frequency.
-     *
-     * @return void
-     */
-    protected function nextWeekly() {
-
-        if (!$this->byHour && !$this->byDay) {
-            $this->currentDate->modify('+' . $this->interval . ' weeks');
-            return;
-        }
-
-        if ($this->byHour) {
-            $recurrenceHours = $this->getHours();
-        }
-
-        if ($this->byDay) {
-            $recurrenceDays = $this->getDays();
-        }
-
-        // First day of the week:
-        $firstDay = $this->dayMap[$this->weekStart];
-
-        do {
-
-            if ($this->byHour) {
-                $this->currentDate->modify('+1 hours');
-            } else {
-                $this->currentDate->modify('+1 days');
-            }
-
-            // Current day of the week
-            $currentDay = (int) $this->currentDate->format('w');
-
-            // Current hour of the day
-            $currentHour = (int) $this->currentDate->format('G');
-
-            // We need to roll over to the next week
-            if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
-                $this->currentDate->modify('+' . $this->interval-1 . ' weeks');
-
-                // We need to go to the first day of this week, but only if we
-                // are not already on this first day of this week.
-                if($this->currentDate->format('w') != $firstDay) {
-                    $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
-                }
-            }
-
-            // We have a match
-        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
-    }
-
-    /**
-     * Does the processing for advancing the iterator for monthly frequency.
-     *
-     * @return void
-     */
-    protected function nextMonthly() {
-
-        $currentDayOfMonth = $this->currentDate->format('j');
-        if (!$this->byMonthDay && !$this->byDay) {
-
-            // If the current day is higher than the 28th, rollover can
-            // occur to the next month. We Must skip these invalid
-            // entries.
-            if ($currentDayOfMonth < 29) {
-                $this->currentDate->modify('+' . $this->interval . ' months');
-            } else {
-                $increase = 0;
-                do {
-                    $increase++;
-                    $tempDate = clone $this->currentDate;
-                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
-                } while ($tempDate->format('j') != $currentDayOfMonth);
-                $this->currentDate = $tempDate;
-            }
-            return;
-        }
-
-        while(true) {
-
-            $occurrences = $this->getMonthlyOccurrences();
-
-            foreach($occurrences as $occurrence) {
-
-                // The first occurrence thats higher than the current
-                // day of the month wins.
-                if ($occurrence > $currentDayOfMonth) {
-                    break 2;
-                }
-
-            }
-
-            // If we made it all the way here, it means there were no
-            // valid occurrences, and we need to advance to the next
-            // month.
-            $this->currentDate->modify('first day of this month');
-            $this->currentDate->modify('+ ' . $this->interval . ' months');
-
-            // This goes to 0 because we need to start counting at hte
-            // beginning.
-            $currentDayOfMonth = 0;
-
-        }
-
-        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for yearly frequency.
-     *
-     * @return void
-     */
-    protected function nextYearly() {
-
-        $currentMonth = $this->currentDate->format('n');
-        $currentYear = $this->currentDate->format('Y');
-        $currentDayOfMonth = $this->currentDate->format('j');
-
-        // No sub-rules, so we just advance by year
-        if (!$this->byMonth) {
-
-            // Unless it was a leap day!
-            if ($currentMonth==2 && $currentDayOfMonth==29) {
-
-                $counter = 0;
-                do {
-                    $counter++;
-                    // Here we increase the year count by the interval, until
-                    // we hit a date that's also in a leap year.
-                    //
-                    // We could just find the next interval that's dividable by
-                    // 4, but that would ignore the rule that there's no leap
-                    // year every year that's dividable by a 100, but not by
-                    // 400. (1800, 1900, 2100). So we just rely on the datetime
-                    // functions instead.
-                    $nextDate = clone $this->currentDate;
-                    $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
-                } while ($nextDate->format('n')!=2);
-                $this->currentDate = $nextDate;
-
-                return;
-
-            }
-
-            // The easiest form
-            $this->currentDate->modify('+' . $this->interval . ' years');
-            return;
-
-        }
-
-        $currentMonth = $this->currentDate->format('n');
-        $currentYear = $this->currentDate->format('Y');
-        $currentDayOfMonth = $this->currentDate->format('j');
-
-        $advancedToNewMonth = false;
-
-        // If we got a byDay or getMonthDay filter, we must first expand
-        // further.
-        if ($this->byDay || $this->byMonthDay) {
-
-            while(true) {
-
-                $occurrences = $this->getMonthlyOccurrences();
-
-                foreach($occurrences as $occurrence) {
-
-                    // The first occurrence that's higher than the current
-                    // day of the month wins.
-                    // If we advanced to the next month or year, the first
-                    // occurrence is always correct.
-                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
-                        break 2;
-                    }
-
-                }
-
-                // If we made it here, it means we need to advance to
-                // the next month or year.
-                $currentDayOfMonth = 1;
-                $advancedToNewMonth = true;
-                do {
-
-                    $currentMonth++;
-                    if ($currentMonth>12) {
-                        $currentYear+=$this->interval;
-                        $currentMonth = 1;
-                    }
-                } while (!in_array($currentMonth, $this->byMonth));
-
-                $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
-
-            }
-
-            // If we made it here, it means we got a valid occurrence
-            $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
-            return;
-
-        } else {
-
-            // These are the 'byMonth' rules, if there are no byDay or
-            // byMonthDay sub-rules.
-            do {
-
-                $currentMonth++;
-                if ($currentMonth>12) {
-                    $currentYear+=$this->interval;
-                    $currentMonth = 1;
-                }
-            } while (!in_array($currentMonth, $this->byMonth));
-            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
-
-            return;
-
-        }
-
-    }
-
-    /**
-     * Returns all the occurrences for a monthly frequency with a 'byDay' or
-     * 'byMonthDay' expansion for the current month.
-     *
-     * The returned list is an array of integers with the day of month (1-31).
-     *
-     * @return array
-     */
-    protected function getMonthlyOccurrences() {
-
-        $startDate = clone $this->currentDate;
-
-        $byDayResults = array();
-
-        // Our strategy is to simply go through the byDays, advance the date to
-        // that point and add it to the results.
-        if ($this->byDay) foreach($this->byDay as $day) {
-
-            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
-
-            // Dayname will be something like 'wednesday'. Now we need to find
-            // all wednesdays in this month.
-            $dayHits = array();
-
-            $checkDate = clone $startDate;
-            $checkDate->modify('first day of this month');
-            $checkDate->modify($dayName);
-
-            do {
-                $dayHits[] = $checkDate->format('j');
-                $checkDate->modify('next ' . $dayName);
-            } while ($checkDate->format('n') === $startDate->format('n'));
-
-            // So now we have 'all wednesdays' for month. It is however
-            // possible that the user only really wanted the 1st, 2nd or last
-            // wednesday.
-            if (strlen($day)>2) {
-                $offset = (int)substr($day,0,-2);
-
-                if ($offset>0) {
-                    // It is possible that the day does not exist, such as a
-                    // 5th or 6th wednesday of the month.
-                    if (isset($dayHits[$offset-1])) {
-                        $byDayResults[] = $dayHits[$offset-1];
-                    }
-                } else {
-
-                    // if it was negative we count from the end of the array
-                    $byDayResults[] = $dayHits[count($dayHits) + $offset];
-                }
-            } else {
-                // There was no counter (first, second, last wednesdays), so we
-                // just need to add the all to the list).
-                $byDayResults = array_merge($byDayResults, $dayHits);
-
-            }
-
-        }
-
-        $byMonthDayResults = array();
-        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
-
-            // Removing values that are out of range for this month
-            if ($monthDay > $startDate->format('t') ||
-                $monthDay < 0-$startDate->format('t')) {
-                    continue;
-            }
-            if ($monthDay>0) {
-                $byMonthDayResults[] = $monthDay;
-            } else {
-                // Negative values
-                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
-            }
-        }
-
-        // If there was just byDay or just byMonthDay, they just specify our
-        // (almost) final list. If both were provided, then byDay limits the
-        // list.
-        if ($this->byMonthDay && $this->byDay) {
-            $result = array_intersect($byMonthDayResults, $byDayResults);
-        } elseif ($this->byMonthDay) {
-            $result = $byMonthDayResults;
-        } else {
-            $result = $byDayResults;
-        }
-        $result = array_unique($result);
-        sort($result, SORT_NUMERIC);
-
-        // The last thing that needs checking is the BYSETPOS. If it's set, it
-        // means only certain items in the set survive the filter.
-        if (!$this->bySetPos) {
-            return $result;
-        }
-
-        $filteredResult = array();
-        foreach($this->bySetPos as $setPos) {
-
-            if ($setPos<0) {
-                $setPos = count($result)-($setPos+1);
-            }
-            if (isset($result[$setPos-1])) {
-                $filteredResult[] = $result[$setPos-1];
-            }
-        }
-
-        sort($filteredResult, SORT_NUMERIC);
-        return $filteredResult;
-
-    }
-
-    protected function getHours()
-    {
-        $recurrenceHours = array();
-        foreach($this->byHour as $byHour) {
-            $recurrenceHours[] = $byHour;
-        }
-
-        return $recurrenceHours;
-    }
-
-    protected function getDays()
-    {
-        $recurrenceDays = array();
-        foreach($this->byDay as $byDay) {
-
-            // The day may be preceeded with a positive (+n) or
-            // negative (-n) integer. However, this does not make
-            // sense in 'weekly' so we ignore it here.
-            $recurrenceDays[] = $this->dayMap[substr($byDay,-2)];
-
-        }
-
-        return $recurrenceDays;
-    }
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/ICalendar.php b/plugins/libcalendaring/lib/Sabre/VObject/Splitter/ICalendar.php
deleted file mode 100644
index 657cfb81..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/ICalendar.php
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Splitter;
-
-use Sabre\VObject;
-
-/**
- * Splitter
- *
- * This class is responsible for splitting up iCalendar objects.
- *
- * This class expects a single VCALENDAR object with one or more
- * calendar-objects inside. Objects with identical UID's will be combined into
- * a single object.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Dominik Tobschall
- * @author Armin Hackmann
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class ICalendar implements SplitterInterface {
-
-    /**
-     * Timezones
-     *
-     * @var array
-     */
-    protected $vtimezones = array();
-
-    /**
-     * iCalendar objects
-     *
-     * @var array
-     */
-    protected $objects = array();
-
-    /**
-     * Constructor
-     *
-     * The splitter should receive an readable file stream as it's input.
-     *
-     * @param resource $input
-     */
-    public function __construct($input) {
-
-        $data = VObject\Reader::read(stream_get_contents($input));
-        $vtimezones = array();
-        $components = array();
-
-        foreach($data->children as $component) {
-            if (!$component instanceof VObject\Component) {
-                continue;
-            }
-
-            // Get all timezones
-            if ($component->name === 'VTIMEZONE') {
-                $this->vtimezones[(string)$component->TZID] = $component;
-                continue;
-            }
-
-            // Get component UID for recurring Events search
-            if($component->UID) {
-                $uid = (string)$component->UID;
-            } else {
-                // Generating a random UID
-                $uid = sha1(microtime()) . '-vobjectimport';
-            }
-
-            // Take care of recurring events
-            if (!array_key_exists($uid, $this->objects)) {
-                $this->objects[$uid] = VObject\Component::create('VCALENDAR');
-            }
-
-            $this->objects[$uid]->add(clone $component);
-        }
-
-    }
-
-    /**
-     * Every time getNext() is called, a new object will be parsed, until we
-     * hit the end of the stream.
-     *
-     * When the end is reached, null will be returned.
-     *
-     * @return Sabre\VObject\Component|null
-     */
-    public function getNext() {
-
-        if($object=array_shift($this->objects)) {
-
-            // create our baseobject
-            $object->version = '2.0';
-            $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
-            $object->calscale = 'GREGORIAN';
-
-            // add vtimezone information to obj (if we have it)
-            foreach ($this->vtimezones as $vtimezone) {
-                $object->add($vtimezone);
-            }
-
-            return $object;
-
-        } else {
-
-            return null;
-
-        }
-
-   }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/SplitterInterface.php b/plugins/libcalendaring/lib/Sabre/VObject/Splitter/SplitterInterface.php
deleted file mode 100644
index c0126883..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/SplitterInterface.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Splitter;
-
-/**
- * VObject splitter
- *
- * The splitter is responsible for reading a large vCard or iCalendar object,
- * and splitting it into multiple objects.
- *
- * This is for example for Card and CalDAV, which require every event and vcard
- * to exist in their own objects, instead of one large one.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Dominik Tobschall
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-interface SplitterInterface {
-
-    /**
-     * Constructor
-     *
-     * The splitter should receive an readable file stream as it's input.
-     *
-     * @param resource $input
-     */
-    function __construct($input);
-
-    /**
-     * Every time getNext() is called, a new object will be parsed, until we
-     * hit the end of the stream.
-     *
-     * When the end is reached, null will be returned.
-     *
-     * @return Sabre\VObject\Component|null
-     */
-    function getNext();
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/VCard.php b/plugins/libcalendaring/lib/Sabre/VObject/Splitter/VCard.php
deleted file mode 100644
index 7a8718c0..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Splitter/VCard.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-namespace Sabre\VObject\Splitter;
-
-use Sabre\VObject;
-
-/**
- * Splitter
- *
- * This class is responsible for splitting up VCard objects.
- *
- * It is assumed that the input stream contains 1 or more VCARD objects. This
- * class checks for BEGIN:VCARD and END:VCARD and parses each encountered
- * component individually.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Dominik Tobschall
- * @author Armin Hackmann
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class VCard implements SplitterInterface {
-
-    /**
-     * File handle
-     *
-     * @var resource
-     */
-    protected $input;
-
-    /**
-     * Constructor
-     *
-     * The splitter should receive an readable file stream as it's input.
-     *
-     * @param resource $input
-     */
-    public function __construct($input) {
-
-        $this->input = $input;
-
-    }
-
-    /**
-     * Every time getNext() is called, a new object will be parsed, until we
-     * hit the end of the stream.
-     *
-     * When the end is reached, null will be returned.
-     *
-     * @return Sabre\VObject\Component|null
-     */
-    public function getNext() {
-
-        $vcard = '';
-
-        do {
-
-            if (feof($this->input)) {
-                return false;
-            }
-
-            $line = fgets($this->input);
-            $vcard .= $line;
-
-        } while(strtoupper(substr($line,0,4))!=="END:");
-
-        $object = VObject\Reader::read($vcard);
-
-        if($object->name !== 'VCARD') {
-            throw new \InvalidArgumentException("Thats no vCard!", 1);
-        }
-
-        return $object;
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/StringUtil.php b/plugins/libcalendaring/lib/Sabre/VObject/StringUtil.php
deleted file mode 100644
index ea88e1e8..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/StringUtil.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * Useful utilities for working with various strings.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class StringUtil {
-
-    /**
-     * Returns true or false depending on if a string is valid UTF-8
-     *
-     * @param string $str
-     * @return bool
-     */
-    static function isUTF8($str) {
-
-        // First check.. mb_check_encoding
-        if (!mb_check_encoding($str, 'UTF-8')) {
-            return false;
-        }
-
-        // Control characters
-        if (preg_match('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', $str)) {
-            return false;
-        }
-
-        return true;
-
-    }
-
-    /**
-     * This method tries its best to convert the input string to UTF-8.
-     *
-     * Currently only ISO-5991-1 input and UTF-8 input is supported, but this
-     * may be expanded upon if we receive other examples.
-     *
-     * @param string $str
-     * @return string
-     */
-    static function convertToUTF8($str) {
-
-        $encoding = mb_detect_encoding($str , array('UTF-8','ISO-8859-1'), true);
-
-        if ($encoding === 'ISO-8859-1') {
-            $newStr = utf8_encode($str);
-        } else {
-            $newStr = $str;
-        }
-
-        // Removing any control characters
-        return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', '', $newStr));
-
-    }
-
-}
-
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/TimeZoneUtil.php b/plugins/libcalendaring/lib/Sabre/VObject/TimeZoneUtil.php
deleted file mode 100644
index 6f5b69f3..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/TimeZoneUtil.php
+++ /dev/null
@@ -1,482 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * Time zone name translation
- *
- * This file translates well-known time zone names into "Olson database" time zone names.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class TimeZoneUtil {
-
-    public static $map = array(
-
-        // from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
-        // snapshot taken on 2012/01/16
-
-        // windows
-        'AUS Central Standard Time'=>'Australia/Darwin',
-        'AUS Eastern Standard Time'=>'Australia/Sydney',
-        'Afghanistan Standard Time'=>'Asia/Kabul',
-        'Alaskan Standard Time'=>'America/Anchorage',
-        'Arab Standard Time'=>'Asia/Riyadh',
-        'Arabian Standard Time'=>'Asia/Dubai',
-        'Arabic Standard Time'=>'Asia/Baghdad',
-        'Argentina Standard Time'=>'America/Buenos_Aires',
-        'Armenian Standard Time'=>'Asia/Yerevan',
-        'Atlantic Standard Time'=>'America/Halifax',
-        'Azerbaijan Standard Time'=>'Asia/Baku',
-        'Azores Standard Time'=>'Atlantic/Azores',
-        'Bangladesh Standard Time'=>'Asia/Dhaka',
-        'Canada Central Standard Time'=>'America/Regina',
-        'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
-        'Caucasus Standard Time'=>'Asia/Yerevan',
-        'Cen. Australia Standard Time'=>'Australia/Adelaide',
-        'Central America Standard Time'=>'America/Guatemala',
-        'Central Asia Standard Time'=>'Asia/Almaty',
-        'Central Brazilian Standard Time'=>'America/Cuiaba',
-        'Central Europe Standard Time'=>'Europe/Budapest',
-        'Central European Standard Time'=>'Europe/Warsaw',
-        'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
-        'Central Standard Time'=>'America/Chicago',
-        'Central Standard Time (Mexico)'=>'America/Mexico_City',
-        'China Standard Time'=>'Asia/Shanghai',
-        'Dateline Standard Time'=>'Etc/GMT+12',
-        'E. Africa Standard Time'=>'Africa/Nairobi',
-        'E. Australia Standard Time'=>'Australia/Brisbane',
-        'E. Europe Standard Time'=>'Europe/Minsk',
-        'E. South America Standard Time'=>'America/Sao_Paulo',
-        'Eastern Standard Time'=>'America/New_York',
-        'Egypt Standard Time'=>'Africa/Cairo',
-        'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
-        'FLE Standard Time'=>'Europe/Kiev',
-        'Fiji Standard Time'=>'Pacific/Fiji',
-        'GMT Standard Time'=>'Europe/London',
-        'GTB Standard Time'=>'Europe/Istanbul',
-        'Georgian Standard Time'=>'Asia/Tbilisi',
-        'Greenland Standard Time'=>'America/Godthab',
-        'Greenwich Standard Time'=>'Atlantic/Reykjavik',
-        'Hawaiian Standard Time'=>'Pacific/Honolulu',
-        'India Standard Time'=>'Asia/Calcutta',
-        'Iran Standard Time'=>'Asia/Tehran',
-        'Israel Standard Time'=>'Asia/Jerusalem',
-        'Jordan Standard Time'=>'Asia/Amman',
-        'Kamchatka Standard Time'=>'Asia/Kamchatka',
-        'Korea Standard Time'=>'Asia/Seoul',
-        'Magadan Standard Time'=>'Asia/Magadan',
-        'Mauritius Standard Time'=>'Indian/Mauritius',
-        'Mexico Standard Time'=>'America/Mexico_City',
-        'Mexico Standard Time 2'=>'America/Chihuahua',
-        'Mid-Atlantic Standard Time'=>'Etc/GMT-2',
-        'Middle East Standard Time'=>'Asia/Beirut',
-        'Montevideo Standard Time'=>'America/Montevideo',
-        'Morocco Standard Time'=>'Africa/Casablanca',
-        'Mountain Standard Time'=>'America/Denver',
-        'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
-        'Myanmar Standard Time'=>'Asia/Rangoon',
-        'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
-        'Namibia Standard Time'=>'Africa/Windhoek',
-        'Nepal Standard Time'=>'Asia/Katmandu',
-        'New Zealand Standard Time'=>'Pacific/Auckland',
-        'Newfoundland Standard Time'=>'America/St_Johns',
-        'North Asia East Standard Time'=>'Asia/Irkutsk',
-        'North Asia Standard Time'=>'Asia/Krasnoyarsk',
-        'Pacific SA Standard Time'=>'America/Santiago',
-        'Pacific Standard Time'=>'America/Los_Angeles',
-        'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
-        'Pakistan Standard Time'=>'Asia/Karachi',
-        'Paraguay Standard Time'=>'America/Asuncion',
-        'Romance Standard Time'=>'Europe/Paris',
-        'Russian Standard Time'=>'Europe/Moscow',
-        'SA Eastern Standard Time'=>'America/Cayenne',
-        'SA Pacific Standard Time'=>'America/Bogota',
-        'SA Western Standard Time'=>'America/La_Paz',
-        'SE Asia Standard Time'=>'Asia/Bangkok',
-        'Samoa Standard Time'=>'Pacific/Apia',
-        'Singapore Standard Time'=>'Asia/Singapore',
-        'South Africa Standard Time'=>'Africa/Johannesburg',
-        'Sri Lanka Standard Time'=>'Asia/Colombo',
-        'Syria Standard Time'=>'Asia/Damascus',
-        'Taipei Standard Time'=>'Asia/Taipei',
-        'Tasmania Standard Time'=>'Australia/Hobart',
-        'Tokyo Standard Time'=>'Asia/Tokyo',
-        'Tonga Standard Time'=>'Pacific/Tongatapu',
-        'US Eastern Standard Time'=>'America/Indianapolis',
-        'US Mountain Standard Time'=>'America/Phoenix',
-        'UTC+12'=>'Etc/GMT-12',
-        'UTC-02'=>'Etc/GMT+2',
-        'UTC-11'=>'Etc/GMT+11',
-        'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
-        'Venezuela Standard Time'=>'America/Caracas',
-        'Vladivostok Standard Time'=>'Asia/Vladivostok',
-        'W. Australia Standard Time'=>'Australia/Perth',
-        'W. Central Africa Standard Time'=>'Africa/Lagos',
-        'W. Europe Standard Time'=>'Europe/Berlin',
-        'West Asia Standard Time'=>'Asia/Tashkent',
-        'West Pacific Standard Time'=>'Pacific/Port_Moresby',
-        'Yakutsk Standard Time'=>'Asia/Yakutsk',
-
-        // Microsoft exchange timezones
-        // Source:
-        // http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
-        //
-        // Correct timezones deduced with help from:
-        // http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
-        'Universal Coordinated Time' => 'UTC',
-        'Casablanca, Monrovia' => 'Africa/Casablanca',
-        'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
-        'Greenwich Mean Time; Dublin, Edinburgh, London' =>  'Europe/London',
-        'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
-        'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
-        'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
-        'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
-        'Prague, Central Europe' => 'Europe/Prague',
-        'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
-        'West Central Africa' => 'Africa/Luanda', // This was a best guess
-        'Athens, Istanbul, Minsk' => 'Europe/Athens',
-        'Bucharest' => 'Europe/Bucharest',
-        'Cairo' => 'Africa/Cairo',
-        'Harare, Pretoria' => 'Africa/Harare',
-        'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
-        'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
-        'Baghdad' => 'Asia/Baghdad',
-        'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
-        'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
-        'East Africa, Nairobi' => 'Africa/Nairobi',
-        'Tehran' => 'Asia/Tehran',
-        'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
-        'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
-        'Kabul' => 'Asia/Kabul',
-        'Ekaterinburg' => 'Asia/Yekaterinburg',
-        'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
-        'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
-        'Kathmandu, Nepal' => 'Asia/Kathmandu',
-        'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
-        'Astana, Dhaka' => 'Asia/Dhaka',
-        'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
-        'Rangoon' => 'Asia/Rangoon',
-        'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
-        'Krasnoyarsk' => 'Asia/Krasnoyarsk',
-        'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
-        'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
-        'Kuala Lumpur, Singapore' => 'Asia/Singapore',
-        'Perth, Western Australia' => 'Australia/Perth',
-        'Taipei' => 'Asia/Taipei',
-        'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
-        'Seoul, Korea Standard time' => 'Asia/Seoul',
-        'Yakutsk' => 'Asia/Yakutsk',
-        'Adelaide, Central Australia' => 'Australia/Adelaide',
-        'Darwin' => 'Australia/Darwin',
-        'Brisbane, East Australia' => 'Australia/Brisbane',
-        'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
-        'Guam, Port Moresby' => 'Pacific/Guam',
-        'Hobart, Tasmania' => 'Australia/Hobart',
-        'Vladivostok' => 'Asia/Vladivostok',
-        'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
-        'Auckland, Wellington' => 'Pacific/Auckland',
-        'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
-        'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
-        'Azores' => 'Atlantic/Azores',
-        'Cape Verde Is.' => 'Atlantic/Cape_Verde',
-        'Mid-Atlantic' => 'America/Noronha',
-        'Brasilia' => 'America/Sao_Paulo', // Best guess
-        'Buenos Aires' => 'America/Argentina/Buenos_Aires',
-        'Greenland' => 'America/Godthab',
-        'Newfoundland' => 'America/St_Johns',
-        'Atlantic Time (Canada)' => 'America/Halifax',
-        'Caracas, La Paz' => 'America/Caracas',
-        'Santiago' => 'America/Santiago',
-        'Bogota, Lima, Quito' => 'America/Bogota',
-        'Eastern Time (US & Canada)' => 'America/New_York',
-        'Indiana (East)' => 'America/Indiana/Indianapolis',
-        'Central America' => 'America/Guatemala',
-        'Central Time (US & Canada)' => 'America/Chicago',
-        'Mexico City, Tegucigalpa' => 'America/Mexico_City',
-        'Saskatchewan' => 'America/Edmonton',
-        'Arizona' => 'America/Phoenix',
-        'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
-        'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
-        'Alaska' => 'America/Anchorage',
-        'Hawaii' => 'Pacific/Honolulu',
-        'Midway Island, Samoa' => 'Pacific/Midway',
-        'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
-
-        // The following list are timezone names that could be generated by
-        // Lotus / Domino
-        'Dateline'               => 'Etc/GMT-12',
-        'Samoa'                  => 'Pacific/Apia',
-        'Hawaiian'               => 'Pacific/Honolulu',
-        'Alaskan'                => 'America/Anchorage',
-        'Pacific'                => 'America/Los_Angeles',
-        'Pacific Standard Time'  => 'America/Los_Angeles',
-        'Mexico Standard Time 2' => 'America/Chihuahua',
-        'Mountain'               => 'America/Denver',
-        'Mountain Standard Time' => 'America/Chihuahua',
-        'US Mountain'            => 'America/Phoenix',
-        'Canada Central'         => 'America/Edmonton',
-        'Central America'        => 'America/Guatemala',
-        'Central'                => 'America/Chicago',
-        'Central Standard Time'  => 'America/Mexico_City',
-        'Mexico'                 => 'America/Mexico_City',
-        'Eastern'                => 'America/New_York',
-        'SA Pacific'             => 'America/Bogota',
-        'US Eastern'             => 'America/Indiana/Indianapolis',
-        'Venezuela'              => 'America/Caracas',
-        'Atlantic'               => 'America/Halifax',
-        'Central Brazilian'      => 'America/Manaus',
-        'Pacific SA'             => 'America/Santiago',
-        'SA Western'             => 'America/La_Paz',
-        'Newfoundland'           => 'America/St_Johns',
-        'Argentina'              => 'America/Argentina/Buenos_Aires',
-        'E. South America'       => 'America/Belem',
-        'Greenland'              => 'America/Godthab',
-        'Montevideo'             => 'America/Montevideo',
-        'SA Eastern'             => 'America/Belem',
-        'Mid-Atlantic'           => 'Etc/GMT-2',
-        'Azores'                 => 'Atlantic/Azores',
-        'Cape Verde'             => 'Atlantic/Cape_Verde',
-        'Greenwich'              => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
-        'Morocco'                => 'Africa/Casablanca',
-        'Central Europe'         => 'Europe/Prague',
-        'Central European'       => 'Europe/Sarajevo',
-        'Romance'                => 'Europe/Paris',
-        'W. Central Africa'      => 'Africa/Lagos', // Best guess
-        'W. Europe'              => 'Europe/Amsterdam',
-        'E. Europe'              => 'Europe/Minsk',
-        'Egypt'                  => 'Africa/Cairo',
-        'FLE'                    => 'Europe/Helsinki',
-        'GTB'                    => 'Europe/Athens',
-        'Israel'                 => 'Asia/Jerusalem',
-        'Jordan'                 => 'Asia/Amman',
-        'Middle East'            => 'Asia/Beirut',
-        'Namibia'                => 'Africa/Windhoek',
-        'South Africa'           => 'Africa/Harare',
-        'Arab'                   => 'Asia/Kuwait',
-        'Arabic'                 => 'Asia/Baghdad',
-        'E. Africa'              => 'Africa/Nairobi',
-        'Georgian'               => 'Asia/Tbilisi',
-        'Russian'                => 'Europe/Moscow',
-        'Iran'                   => 'Asia/Tehran',
-        'Arabian'                => 'Asia/Muscat',
-        'Armenian'               => 'Asia/Yerevan',
-        'Azerbijan'              => 'Asia/Baku',
-        'Caucasus'               => 'Asia/Yerevan',
-        'Mauritius'              => 'Indian/Mauritius',
-        'Afghanistan'            => 'Asia/Kabul',
-        'Ekaterinburg'           => 'Asia/Yekaterinburg',
-        'Pakistan'               => 'Asia/Karachi',
-        'West Asia'              => 'Asia/Tashkent',
-        'India'                  => 'Asia/Calcutta',
-        'Sri Lanka'              => 'Asia/Colombo',
-        'Nepal'                  => 'Asia/Kathmandu',
-        'Central Asia'           => 'Asia/Dhaka',
-        'N. Central Asia'        => 'Asia/Almaty',
-        'Myanmar'                => 'Asia/Rangoon',
-        'North Asia'             => 'Asia/Krasnoyarsk',
-        'SE Asia'                => 'Asia/Bangkok',
-        'China'                  => 'Asia/Shanghai',
-        'North Asia East'        => 'Asia/Irkutsk',
-        'Singapore'              => 'Asia/Singapore',
-        'Taipei'                 => 'Asia/Taipei',
-        'W. Australia'           => 'Australia/Perth',
-        'Korea'                  => 'Asia/Seoul',
-        'Tokyo'                  => 'Asia/Tokyo',
-        'Yakutsk'                => 'Asia/Yakutsk',
-        'AUS Central'            => 'Australia/Darwin',
-        'Cen. Australia'         => 'Australia/Adelaide',
-        'AUS Eastern'            => 'Australia/Sydney',
-        'E. Australia'           => 'Australia/Brisbane',
-        'Tasmania'               => 'Australia/Hobart',
-        'Vladivostok'            => 'Asia/Vladivostok',
-        'West Pacific'           => 'Pacific/Guam',
-        'Central Pacific'        => 'Asia/Magadan',
-        'Fiji'                   => 'Pacific/Fiji',
-        'New Zealand'            => 'Pacific/Auckland',
-        'Tonga'                  => 'Pacific/Tongatapu',
-    );
-
-    /**
-     * List of microsoft exchange timezone ids.
-     *
-     * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
-     */
-    public static $microsoftExchangeMap = array(
-        0  => 'UTC',
-        31 => 'Africa/Casablanca',
-
-        // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
-        // I'm not even kidding.. We handle this special case in the
-        // getTimeZone method.
-        2  => 'Europe/Lisbon',
-        1  => 'Europe/London',
-        4  => 'Europe/Berlin',
-        6  => 'Europe/Prague',
-        3  => 'Europe/Paris',
-        69 => 'Africa/Luanda', // This was a best guess
-        7  => 'Europe/Athens',
-        5  => 'Europe/Bucharest',
-        49 => 'Africa/Cairo',
-        50 => 'Africa/Harare',
-        59 => 'Europe/Helsinki',
-        27 => 'Asia/Jerusalem',
-        26 => 'Asia/Baghdad',
-        74 => 'Asia/Kuwait',
-        51 => 'Europe/Moscow',
-        56 => 'Africa/Nairobi',
-        25 => 'Asia/Tehran',
-        24 => 'Asia/Muscat', // Best guess
-        54 => 'Asia/Baku',
-        48 => 'Asia/Kabul',
-        58 => 'Asia/Yekaterinburg',
-        47 => 'Asia/Karachi',
-        23 => 'Asia/Calcutta',
-        62 => 'Asia/Kathmandu',
-        46 => 'Asia/Almaty',
-        71 => 'Asia/Dhaka',
-        66 => 'Asia/Colombo',
-        61 => 'Asia/Rangoon',
-        22 => 'Asia/Bangkok',
-        64 => 'Asia/Krasnoyarsk',
-        45 => 'Asia/Shanghai',
-        63 => 'Asia/Irkutsk',
-        21 => 'Asia/Singapore',
-        73 => 'Australia/Perth',
-        75 => 'Asia/Taipei',
-        20 => 'Asia/Tokyo',
-        72 => 'Asia/Seoul',
-        70 => 'Asia/Yakutsk',
-        19 => 'Australia/Adelaide',
-        44 => 'Australia/Darwin',
-        18 => 'Australia/Brisbane',
-        76 => 'Australia/Sydney',
-        43 => 'Pacific/Guam',
-        42 => 'Australia/Hobart',
-        68 => 'Asia/Vladivostok',
-        41 => 'Asia/Magadan',
-        17 => 'Pacific/Auckland',
-        40 => 'Pacific/Fiji',
-        67 => 'Pacific/Tongatapu',
-        29 => 'Atlantic/Azores',
-        53 => 'Atlantic/Cape_Verde',
-        30 => 'America/Noronha',
-         8 => 'America/Sao_Paulo', // Best guess
-        32 => 'America/Argentina/Buenos_Aires',
-        60 => 'America/Godthab',
-        28 => 'America/St_Johns',
-         9 => 'America/Halifax',
-        33 => 'America/Caracas',
-        65 => 'America/Santiago',
-        35 => 'America/Bogota',
-        10 => 'America/New_York',
-        34 => 'America/Indiana/Indianapolis',
-        55 => 'America/Guatemala',
-        11 => 'America/Chicago',
-        37 => 'America/Mexico_City',
-        36 => 'America/Edmonton',
-        38 => 'America/Phoenix',
-        12 => 'America/Denver', // Best guess
-        13 => 'America/Los_Angeles', // Best guess
-        14 => 'America/Anchorage',
-        15 => 'Pacific/Honolulu',
-        16 => 'Pacific/Midway',
-        39 => 'Pacific/Kwajalein',
-    );
-
-    /**
-     * This method will try to find out the correct timezone for an iCalendar
-     * date-time value.
-     *
-     * You must pass the contents of the TZID parameter, as well as the full
-     * calendar.
-     *
-     * If the lookup fails, this method will return the default PHP timezone
-     * (as configured using date_default_timezone_set, or the date.timezone ini
-     * setting).
-     *
-     * Alternatively, if $failIfUncertain is set to true, it will throw an
-     * exception if we cannot accurately determine the timezone.
-     *
-     * @param string $tzid
-     * @param Sabre\VObject\Component $vcalendar
-     * @return DateTimeZone
-     */
-    static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
-
-        // First we will just see if the tzid is a support timezone identifier.
-        try {
-            return new \DateTimeZone($tzid);
-        } catch (\Exception $e) {
-        }
-
-        // Next, we check if the tzid is somewhere in our tzid map.
-        if (isset(self::$map[$tzid])) {
-            return new \DateTimeZone(self::$map[$tzid]);
-        }
-
-        // Maybe the author was hyper-lazy and just included an offset. We
-        // support it, but we aren't happy about it.
-        if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
-            return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
-        }
-
-        if ($vcalendar) {
-
-            // If that didn't work, we will scan VTIMEZONE objects
-            foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
-
-                if ((string)$vtimezone->TZID === $tzid) {
-
-                    // Some clients add 'X-LIC-LOCATION' with the olson name.
-                    if (isset($vtimezone->{'X-LIC-LOCATION'})) {
-
-                        $lic = (string)$vtimezone->{'X-LIC-LOCATION'};
-
-                        // Libical generators may specify strings like
-                        // "SystemV/EST5EDT". For those we must remove the
-                        // SystemV part.
-                        if (substr($lic,0,8)==='SystemV/') {
-                            $lic = substr($lic,8);
-                        }
-
-                        try {
-                            return new \DateTimeZone($lic);
-                        } catch (\Exception $e) {
-                        }
-
-                    }
-                    // Microsoft may add a magic number, which we also have an
-                    // answer for.
-                    if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
-                        $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value;
-
-                        // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
-                        if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
-                            return new \DateTimeZone('Europe/Sarajevo');
-                        }
-
-                        if (isset(self::$microsoftExchangeMap[$cdoId])) {
-                            return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
-                        }
-                    }
-
-                }
-
-            }
-
-        }
-
-        if ($failIfUncertain) {
-            throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
-        }
-
-        // If we got all the way here, we default to UTC.
-        return new \DateTimeZone(date_default_timezone_get());
-
-    }
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Version.php b/plugins/libcalendaring/lib/Sabre/VObject/Version.php
deleted file mode 100644
index 2a64140f..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/Version.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace Sabre\VObject;
-
-/**
- * This class contains the version number for the VObject package
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Version {
-
-    /**
-     * Full version number
-     */
-    const VERSION = '2.1.3';
-
-    /**
-     * Stability : alpha, beta, stable
-     */
-    const STABILITY = 'stable';
-
-}
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/includes.php b/plugins/libcalendaring/lib/Sabre/VObject/includes.php
deleted file mode 100644
index d15329a6..00000000
--- a/plugins/libcalendaring/lib/Sabre/VObject/includes.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * Includes file
- *
- * This file includes the entire VObject library in one go.
- * The benefit is that an autoloader is not needed, which is often faster.
- *
- * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
- * @author Evert Pot (http://evertpot.com/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-
-// Begin includes
-include __DIR__ . '/DateTimeParser.php';
-include __DIR__ . '/ElementList.php';
-include __DIR__ . '/FreeBusyGenerator.php';
-include __DIR__ . '/Node.php';
-include __DIR__ . '/Parameter.php';
-include __DIR__ . '/ParseException.php';
-include __DIR__ . '/Property.php';
-include __DIR__ . '/Reader.php';
-include __DIR__ . '/RecurrenceIterator.php';
-include __DIR__ . '/Splitter/SplitterInterface.php';
-include __DIR__ . '/StringUtil.php';
-include __DIR__ . '/TimeZoneUtil.php';
-include __DIR__ . '/Version.php';
-include __DIR__ . '/Splitter/VCard.php';
-include __DIR__ . '/Component.php';
-include __DIR__ . '/Document.php';
-include __DIR__ . '/Property/Compound.php';
-include __DIR__ . '/Property/DateTime.php';
-include __DIR__ . '/Property/MultiDateTime.php';
-include __DIR__ . '/Splitter/ICalendar.php';
-include __DIR__ . '/Component/VAlarm.php';
-include __DIR__ . '/Component/VCalendar.php';
-include __DIR__ . '/Component/VEvent.php';
-include __DIR__ . '/Component/VFreeBusy.php';
-include __DIR__ . '/Component/VJournal.php';
-include __DIR__ . '/Component/VTodo.php';
-// End includes
diff --git a/plugins/libcalendaring/lib/get_sabre_vobject.sh b/plugins/libcalendaring/lib/get_sabre_vobject.sh
deleted file mode 100755
index f8c88c81..00000000
--- a/plugins/libcalendaring/lib/get_sabre_vobject.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-# Download and install the Sabre\Vobject library for this plugin
-
-wget 'https://github.com/fruux/sabre-vobject/archive/2.1.0.tar.gz' -O sabre-vobject-2.1.0.tar.gz
-tar xf sabre-vobject-2.1.0.tar.gz
-
-mv sabre-vobject-2.1.0/lib/* .
-rm -rf sabre-vobject-2.1.0
-
-cd lib/Sabre/VObject && wget --no-check-certificate -O Property.php https://raw2.github.com/thomascube/sabre-vobject/84b64c65f9a94f7ec5a5e327bab3cc1335dd613c/lib/Sabre/VObject/Property.php
-
diff --git a/plugins/libcalendaring/lib/sabre-vobject-2.1.0.tar.gz b/plugins/libcalendaring/lib/sabre-vobject-2.1.0.tar.gz
deleted file mode 100644
index 339a992b..00000000
Binary files a/plugins/libcalendaring/lib/sabre-vobject-2.1.0.tar.gz and /dev/null differ
diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index 0dad1aac..bedb52eb 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -1,1362 +1,1383 @@
 <?php
 
 /**
  * iCalendar functions for the libcalendaring plugin
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2013-2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 use \Sabre\VObject;
-
-// load Sabre\VObject classes
-if (!class_exists('\Sabre\VObject\Reader')) {
-    require_once __DIR__ . '/lib/Sabre/VObject/includes.php';
-}
+use \Sabre\VObject\DateTimeParser;
 
 /**
  * Class to parse and build vCalendar (iCalendar) files
  *
- * Uses the SabreTooth VObject library, version 2.1.
- *
- * Download from https://github.com/fruux/sabre-vobject/archive/2.1.0.zip
- * and place the lib files in this plugin's lib directory
+ * Uses the Sabre VObject library, version 3.x.
  *
  */
 class libvcalendar implements Iterator
 {
     private $timezone;
     private $attach_uri = null;
     private $prodid = '-//Roundcube libcalendaring//Sabre//Sabre VObject//EN';
     private $type_component_map = array('event' => 'VEVENT', 'task' => 'VTODO');
-    private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE',
-        'cutype' => 'CUTYPE', 'rsvp' => 'RSVP', 'delegated-from' => 'DELEGATED-FROM', 'delegated-to' => 'DELEGATED-TO');
+    private $attendee_keymap = array(
+        'name'   => 'CN',
+        'status' => 'PARTSTAT',
+        'role'   => 'ROLE',
+        'cutype' => 'CUTYPE',
+        'rsvp'   => 'RSVP',
+        'delegated-from'  => 'DELEGATED-FROM',
+        'delegated-to'    => 'DELEGATED-TO',
+        'schedule-status' => 'SCHEDULE-STATUS',
+    );
     private $iteratorkey = 0;
     private $charset;
     private $forward_exceptions;
     private $vhead;
     private $fp;
     private $vtimezones = array();
 
     public $method;
     public $agent = '';
     public $objects = array();
     public $freebusy = array();
 
 
     /**
      * Default constructor
      */
     function __construct($tz = null)
     {
         $this->timezone = $tz;
         $this->prodid = '-//Roundcube libcalendaring ' . RCUBE_VERSION . '//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
     }
 
     /**
      * Setter for timezone information
      */
     public function set_timezone($tz)
     {
         $this->timezone = $tz;
     }
 
     /**
      * Setter for URI template for attachment links
      */
     public function set_attach_uri($uri)
     {
         $this->attach_uri = $uri;
     }
 
     /**
      * Setter for a custom PRODID attribute
      */
     public function set_prodid($prodid)
     {
         $this->prodid = $prodid;
     }
 
     /**
      * Setter for a user-agent string to tweak input/output accordingly
      */
     public function set_agent($agent)
     {
         $this->agent = $agent;
     }
 
     /**
      * Free resources by clearing member vars
      */
     public function reset()
     {
         $this->vhead = '';
         $this->method = '';
         $this->objects = array();
         $this->freebusy = array();
         $this->vtimezones = array();
         $this->iteratorkey = 0;
 
         if ($this->fp) {
             fclose($this->fp);
             $this->fp = null;
         }
     }
 
     /**
     * Import events from iCalendar format
     *
     * @param  string vCalendar input
     * @param  string Input charset (from envelope)
     * @param  boolean True if parsing exceptions should be forwarded to the caller
     * @return array List of events extracted from the input
     */
     public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false, $memcheck = true)
     {
         // TODO: convert charset to UTF-8 if other
 
         try {
             // estimate the memory usage and try to avoid fatal errors when allowed memory gets exhausted
             if ($memcheck) {
                 $count = substr_count($vcal, 'BEGIN:VEVENT') + substr_count($vcal, 'BEGIN:VTODO');
                 $expected_memory = $count * 70*1024;  // assume ~ 70K per event (empirically determined)
 
                 if (!rcube_utils::mem_check($expected_memory)) {
                     throw new Exception("iCal file too big");
                 }
             }
 
             $vobject = VObject\Reader::read($vcal, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES);
             if ($vobject)
                 return $this->import_from_vobject($vobject);
         }
         catch (Exception $e) {
             if ($forward_exceptions) {
                 throw $e;
             }
             else {
                 rcube::raise_error(array(
                     'code' => 600, 'type' => 'php',
                     'file' => __FILE__, 'line' => __LINE__,
                     'message' => "iCal data parse error: " . $e->getMessage()),
                     true, false);
             }
         }
 
         return array();
     }
 
     /**
     * Read iCalendar events from a file
     *
     * @param  string File path to read from
     * @param  string Input charset (from envelope)
     * @param  boolean True if parsing exceptions should be forwarded to the caller
     * @return array List of events extracted from the file
     */
     public function import_from_file($filepath, $charset = 'UTF-8', $forward_exceptions = false)
     {
         if ($this->fopen($filepath, $charset, $forward_exceptions)) {
             while ($this->_parse_next(false)) {
                 // nop
             }
 
             fclose($this->fp);
             $this->fp = null;
         }
 
         return $this->objects;
     }
 
     /**
      * Open a file to read iCalendar events sequentially
      *
      * @param  string File path to read from
      * @param  string Input charset (from envelope)
      * @param  boolean True if parsing exceptions should be forwarded to the caller
      * @return boolean True if file contents are considered valid
      */
     public function fopen($filepath, $charset = 'UTF-8', $forward_exceptions = false)
     {
         $this->reset();
 
         // just to be sure...
         @ini_set('auto_detect_line_endings', true);
 
         $this->charset = $charset;
         $this->forward_exceptions = $forward_exceptions;
         $this->fp = fopen($filepath, 'r');
 
         // check file content first
         $begin = fread($this->fp, 1024);
         if (!preg_match('/BEGIN:VCALENDAR/i', $begin)) {
             return false;
         }
 
         fseek($this->fp, 0);
         return $this->_parse_next();
     }
 
     /**
      * Parse the next event/todo/freebusy object from the input file
      */
     private function _parse_next($reset = true)
     {
         if ($reset) {
             $this->iteratorkey = 0;
             $this->objects = array();
             $this->freebusy = array();
         }
 
         $next = $this->_next_component();
         $buffer = $next;
 
         // load the next component(s) too, as they could contain recurrence exceptions
         while (preg_match('/(RRULE|RECURRENCE-ID)[:;]/i', $next)) {
             $next = $this->_next_component();
             $buffer .= $next;
         }
 
         // parse the vevent block surrounded with the vcalendar heading
         if (strlen($buffer) && preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $buffer)) {
             try {
                 $this->import($this->vhead . $buffer . "END:VCALENDAR", $this->charset, true, false);
             }
             catch (Exception $e) {
                 if ($this->forward_exceptions) {
                     throw new VObject\ParseException($e->getMessage() . " in\n" . $buffer);
                 }
                 else {
                     // write the failing section to error log
                     rcube::raise_error(array(
                         'code' => 600, 'type' => 'php',
                         'file' => __FILE__, 'line' => __LINE__,
                         'message' => $e->getMessage() . " in\n" . $buffer),
                         true, false);
                 }
 
                 // advance to next
                 return $this->_parse_next($reset);
             }
 
             return count($this->objects) > 0;
         }
 
         return false;
     }
 
     /**
      * Helper method to read the next calendar component from the file
      */
     private function _next_component()
     {
         $buffer = '';
         $vcalendar_head = false;
         while (($line = fgets($this->fp, 1024)) !== false) {
             // ignore END:VCALENDAR lines
             if (preg_match('/END:VCALENDAR/i', $line)) {
                 continue;
             }
             // read vcalendar header (with timezone defintion)
             if (preg_match('/BEGIN:VCALENDAR/i', $line)) {
                 $this->vhead = '';
                 $vcalendar_head = true;
             }
 
             // end of VCALENDAR header part
             if ($vcalendar_head && preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $line)) {
                 $vcalendar_head = false;
             }
 
             if ($vcalendar_head) {
                 $this->vhead .= $line;
             }
             else {
                 $buffer .= $line;
                 if (preg_match('/END:(VEVENT|VTODO|VFREEBUSY)/i', $line)) {
                     break;
                 }
             }
         }
 
         return $buffer;
     }
 
     /**
      * Import objects from an already parsed Sabre\VObject\Component object
      *
      * @param object Sabre\VObject\Component to read from
      * @return array List of events extracted from the file
      */
     public function import_from_vobject($vobject)
     {
         $seen = array();
         $exceptions = array();
 
         if ($vobject->name == 'VCALENDAR') {
             $this->method = strval($vobject->METHOD);
             $this->agent  = strval($vobject->PRODID);
 
-            foreach ($vobject->getBaseComponents() ?: $vobject->getComponents() as $ve) {
+            foreach ($vobject->getComponents() as $ve) {
                 if ($ve->name == 'VEVENT' || $ve->name == 'VTODO') {
                     // convert to hash array representation
                     $object = $this->_to_array($ve);
 
                     // temporarily store this as exception
                     if ($object['recurrence_date']) {
                         $exceptions[] = $object;
                     }
                     else if (!$seen[$object['uid']]++) {
                         $this->objects[] = $object;
                     }
                 }
                 else if ($ve->name == 'VFREEBUSY') {
                     $this->objects[] = $this->_parse_freebusy($ve);
                 }
             }
 
             // add exceptions to the according master events
             foreach ($exceptions as $exception) {
                 $uid = $exception['uid'];
 
                 // make this exception the master
                 if (!$seen[$uid]++) {
                     $this->objects[] = $exception;
                 }
                 else {
                     foreach ($this->objects as $i => $object) {
                         // add as exception to existing entry with a matching UID
                         if ($object['uid'] == $uid) {
                             $this->objects[$i]['exceptions'][] = $exception;
 
                             if (!empty($object['recurrence'])) {
                                 $this->objects[$i]['recurrence']['EXCEPTIONS'] = &$this->objects[$i]['exceptions'];
                             }
                             break;
                         }
                     }
                 }
             }
         }
 
         return $this->objects;
     }
 
     /**
      * Getter for free-busy periods
      */
     public function get_busy_periods()
     {
         $out = array();
         foreach ((array)$this->freebusy['periods'] as $period) {
             if ($period[2] != 'FREE') {
                 $out[] = $period;
             }
         }
 
         return $out;
     }
 
     /**
      * Helper method to determine whether the connected client is an Apple device
      */
     private function is_apple()
     {
         return stripos($this->agent, 'Apple') !== false
             || stripos($this->agent, 'Mac OS X') !== false
             || stripos($this->agent, 'iOS/') !== false;
     }
 
     /**
      * Convert the given VEvent object to a libkolab compatible array representation
      *
      * @param object Vevent object to convert
      * @return array Hash array with object properties
      */
     private function _to_array($ve)
     {
         $event = array(
             'uid'     => self::convert_string($ve->UID),
             'title'   => self::convert_string($ve->SUMMARY),
             '_type'   => $ve->name == 'VTODO' ? 'task' : 'event',
             // set defaults
             'priority' => 0,
             'attendees' => array(),
             'x-custom' => array(),
         );
 
         // Catch possible exceptions when date is invalid (Bug #2144)
         // We can skip these fields, they aren't critical
         foreach (array('CREATED' => 'created', 'LAST-MODIFIED' => 'changed', 'DTSTAMP' => 'changed') as $attr => $field) {
             try {
                 if (!$event[$field] && $ve->{$attr}) {
                     $event[$field] = $ve->{$attr}->getDateTime();
                 }
             } catch (Exception $e) {}
         }
 
         // map other attributes to internal fields
         foreach ($ve->children as $prop) {
             if (!($prop instanceof VObject\Property))
                 continue;
 
+            $value = strval($prop);
+
             switch ($prop->name) {
             case 'DTSTART':
             case 'DTEND':
             case 'DUE':
                 $propmap = array('DTSTART' => 'start', 'DTEND' => 'end', 'DUE' => 'due');
                 $event[$propmap[$prop->name]] =  self::convert_datetime($prop);
                 break;
 
             case 'TRANSP':
-                $event['free_busy'] = $prop->value == 'TRANSPARENT' ? 'free' : 'busy';
+                $event['free_busy'] = strval($prop) == 'TRANSPARENT' ? 'free' : 'busy';
                 break;
 
             case 'STATUS':
-                if ($prop->value == 'TENTATIVE')
+                if ($value == 'TENTATIVE')
                     $event['free_busy'] = 'tentative';
-                else if ($prop->value == 'CANCELLED')
+                else if ($value == 'CANCELLED')
                     $event['cancelled'] = true;
-                else if ($prop->value == 'COMPLETED')
+                else if ($value == 'COMPLETED')
                     $event['complete'] = 100;
 
-                $event['status'] = strval($prop->value);
+                $event['status'] = $value;
                 break;
 
             case 'PRIORITY':
-                if (is_numeric($prop->value))
-                    $event['priority'] = $prop->value;
+                if (is_numeric($value))
+                    $event['priority'] = $value;
                 break;
 
             case 'RRULE':
                 $params = is_array($event['recurrence']) ? $event['recurrence'] : array();
                 // parse recurrence rule attributes
-                foreach (explode(';', $prop->value) as $par) {
-                    list($k, $v) = explode('=', $par);
-                    $params[$k] = $v;
+                foreach ($prop->getParts() as $k => $v) {
+                    $params[strtoupper($k)] = $v;
                 }
                 if ($params['UNTIL'])
                     $params['UNTIL'] = date_create($params['UNTIL']);
                 if (!$params['INTERVAL'])
                     $params['INTERVAL'] = 1;
 
                 $event['recurrence'] = array_filter($params);
                 break;
 
             case 'EXDATE':
-                if (!empty($prop->value))
-                    $event['recurrence']['EXDATE'] = array_merge((array)$event['recurrence']['EXDATE'], self::convert_datetime($prop, true));
+                if (!empty($value)) {
+                    $exdates = array_map(function($_) { return is_array($_) ? $_[0] : $_; }, self::convert_datetime($prop, true));
+                    $event['recurrence']['EXDATE'] = array_merge((array)$event['recurrence']['EXDATE'], $exdates);
+                }
                 break;
 
             case 'RDATE':
-                if (!empty($prop->value))
-                    $event['recurrence']['RDATE'] = array_merge((array)$event['recurrence']['RDATE'], self::convert_datetime($prop, true));
+                if (!empty($value)) {
+                    $rdates = array_map(function($_) { return is_array($_) ? $_[0] : $_; }, self::convert_datetime($prop, true));
+                    $event['recurrence']['RDATE'] = array_merge((array)$event['recurrence']['RDATE'], $rdates);
+                }
                 break;
 
             case 'RECURRENCE-ID':
                 $event['recurrence_date'] = self::convert_datetime($prop);
                 if ($prop->offsetGet('RANGE') == 'THISANDFUTURE' || $prop->offsetGet('THISANDFUTURE') !== null) {
                     $event['thisandfuture'] = true;
                 }
                 break;
 
             case 'RELATED-TO':
                 $reltype = $prop->offsetGet('RELTYPE');
                 if ($reltype == 'PARENT' || $reltype === null) {
-                    $event['parent_id'] = $prop->value;
+                    $event['parent_id'] = $value;
                 }
                 break;
 
             case 'SEQUENCE':
-                $event['sequence'] = intval($prop->value);
+                $event['sequence'] = intval($value);
                 break;
 
             case 'PERCENT-COMPLETE':
-                $event['complete'] = intval($prop->value);
+                $event['complete'] = intval($value);
                 break;
 
             case 'LOCATION':
             case 'DESCRIPTION':
             case 'URL':
             case 'COMMENT':
                 $event[strtolower($prop->name)] = self::convert_string($prop);
                 break;
 
             case 'CATEGORY':
             case 'CATEGORIES':
                 $event['categories'] = array_merge((array)$event['categories'], $prop->getParts());
                 break;
 
             case 'CLASS':
             case 'X-CALENDARSERVER-ACCESS':
-                $event['sensitivity'] = strtolower($prop->value);
+                $event['sensitivity'] = strtolower($value);
                 break;
 
             case 'X-MICROSOFT-CDO-BUSYSTATUS':
-                if ($prop->value == 'OOF')
+                if ($value == 'OOF')
                     $event['free_busy'] = 'outofoffice';
-                else if (in_array($prop->value, array('FREE', 'BUSY', 'TENTATIVE')))
-                    $event['free_busy'] = strtolower($prop->value);
+                else if (in_array($value, array('FREE', 'BUSY', 'TENTATIVE')))
+                    $event['free_busy'] = strtolower($value);
                 break;
 
             case 'ATTENDEE':
             case 'ORGANIZER':
                 $params = array('rsvp' => false);
-                foreach ($prop->parameters as $param) {
-                    switch ($param->name) {
-                        case 'RSVP': $params[$param->name] = strtolower($param->value) == 'true'; break;
-                        default:     $params[$param->name] = $param->value; break;
+                foreach ($prop->parameters() as $pname => $pvalue) {
+                    switch ($pname) {
+                        case 'RSVP': $params[$pname] = strtolower($pvalue) == 'true'; break;
+                        case 'CN':   $params[$pname] = self::unescape($pvalue); break;
+                        default:     $params[$pname] = strval($pvalue); break;
                     }
                 }
                 $attendee = self::map_keys($params, array_flip($this->attendee_keymap));
-                $attendee['email'] = preg_replace('/^mailto:/i', '', $prop->value);
+                $attendee['email'] = preg_replace('!^mailto:!i', '', $value);
 
                 if ($prop->name == 'ORGANIZER') {
                     $attendee['role'] = 'ORGANIZER';
                     $attendee['status'] = 'ACCEPTED';
                     $event['organizer'] = $attendee;
                 }
                 else if ($attendee['email'] != $event['organizer']['email']) {
                     $event['attendees'][] = $attendee;
                 }
                 break;
 
             case 'ATTACH':
                 $params = self::parameters_array($prop);
-                if (substr($prop->value, 0, 4) == 'http' && !strpos($prop->value, ':attachment:')) {
-                    $event['links'][] = $prop->value;
+                if (substr($value, 0, 4) == 'http' && !strpos($value, ':attachment:')) {
+                    $event['links'][] = $value;
                 }
-                else if (strlen($prop->value) && strtoupper($params['VALUE']) == 'BINARY') {
+                else if (strlen($value) && strtoupper($params['VALUE']) == 'BINARY') {
                     $attachment = self::map_keys($params, array('FMTTYPE' => 'mimetype', 'X-LABEL' => 'name'));
-                    $attachment['data'] = base64_decode($prop->value);
-                    $attachment['size'] = strlen($attachment['data']);
+                    $attachment['data'] = $value;
+                    $attachment['size'] = strlen($value);
                     $event['attachments'][] = $attachment;
                 }
                 break;
 
             default:
                 if (substr($prop->name, 0, 2) == 'X-')
-                    $event['x-custom'][] = array($prop->name, strval($prop->value));
+                    $event['x-custom'][] = array($prop->name, strval($value));
                 break;
             }
         }
 
         // check DURATION property if no end date is set
         if (empty($event['end']) && $ve->DURATION) {
             try {
                 $duration = new DateInterval(strval($ve->DURATION));
                 $end = clone $event['start'];
                 $end->add($duration);
                 $event['end'] = $end;
             }
             catch (\Exception $e) {
                 trigger_error(strval($e), E_USER_WARNING);
             }
         }
 
         // validate event dates
         if ($event['_type'] == 'event') {
             // check for all-day dates
             if ($event['start']->_dateonly) {
                 $event['allday'] = true;
             }
 
             // all-day events may lack the DTEND property
             if ($event['allday'] && empty($event['end'])) {
                 $event['end'] = clone $event['start'];
             }
             // shift end-date by one day (except Thunderbird)
             else if ($event['allday'] && is_object($event['end'])) {
                 $event['end']->sub(new \DateInterval('PT23H'));
             }
 
             // sanity-check and fix end date
             if (!empty($event['end']) && $event['end'] < $event['start']) {
                 $event['end'] = clone $event['start'];
             }
         }
 
         // make organizer part of the attendees list for compatibility reasons
         if (!empty($event['organizer']) && is_array($event['attendees']) && $event['_type'] == 'event') {
             array_unshift($event['attendees'], $event['organizer']);
         }
 
         // find alarms
         foreach ($ve->select('VALARM') as $valarm) {
             $action = 'DISPLAY';
             $trigger = null;
             $alarm = array();
 
             foreach ($valarm->children as $prop) {
+                $value = strval($prop);
+
                 switch ($prop->name) {
                 case 'TRIGGER':
-                    foreach ($prop->parameters as $param) {
-                        if ($param->name == 'VALUE' && $param->value == 'DATE-TIME') {
-                            $trigger = '@' . $prop->getDateTime()->format('U');
-                            $alarm['trigger'] = $prop->getDateTime();
-                        }
+                    if ($prop['VALUE'] == 'DATE-TIME') {
+                        $trigger = '@' . $prop->getDateTime()->format('U');
+                        $alarm['trigger'] = $prop->getDateTime();
                     }
-                    if (!$trigger && ($values = libcalendaring::parse_alarm_value($prop->value))) {
+                    if (!$trigger && ($values = libcalendaring::parse_alarm_value($value))) {
                         $trigger = $values[2];
                     }
 
                     if (!$alarm['trigger']) {
-                        $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $prop->value), 'T');
+                        $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $value), 'T');
                         // if all 0-values have been stripped, assume 'at time'
                         if ($alarm['trigger'] == 'P')
                             $alarm['trigger'] = 'PT0S';
                     }
                     break;
 
                 case 'ACTION':
-                    $action = $alarm['action'] = strtoupper($prop->value);
+                    $action = $alarm['action'] = strtoupper($value);
                     break;
 
                 case 'SUMMARY':
                 case 'DESCRIPTION':
                 case 'DURATION':
                     $alarm[strtolower($prop->name)] = self::convert_string($prop);
                     break;
 
                 case 'REPEAT':
-                    $alarm['repeat'] = intval($prop->value);
+                    $alarm['repeat'] = intval($value);
                     break;
 
                 case 'ATTENDEE':
-                    $alarm['attendees'][] = preg_replace('/^mailto:/i', '', $prop->value);
+                    $alarm['attendees'][] = preg_replace('!^mailto:!i', '', $value);
                     break;
 
                 case 'ATTACH':
                     $params = self::parameters_array($prop);
-                    if (strlen($prop->value) && (preg_match('/^[a-z]+:/', $prop->value) || strtoupper($params['VALUE']) == 'URI')) {
+                    if (strlen($value) && (preg_match('/^[a-z]+:/', $value) || strtoupper($params['VALUE']) == 'URI')) {
                         // we only support URI-type of attachments here
-                        $alarm['uri'] = $prop->value;
+                        $alarm['uri'] = $value;
                     }
                     break;
                 }
             }
 
             if ($action != 'NONE') {
                 if ($trigger && !$event['alarms']) // store first alarm in legacy property
                     $event['alarms'] = $trigger . ':' . $action;
 
                 if ($alarm['trigger'])
                     $event['valarms'][] = $alarm;
             }
         }
 
         // assign current timezone to event start/end
         if ($event['start'] instanceof DateTime) {
             if ($this->timezone)
                 $event['start']->setTimezone($this->timezone);
         }
         else {
             unset($event['start']);
         }
 
         if ($event['end'] instanceof DateTime) {
             if ($this->timezone)
                 $event['end']->setTimezone($this->timezone);
         }
         else {
             unset($event['end']);
         }
 
+        // some iTip CANCEL messages only contain the start date
+        if (!$event['end'] && $event['start'] && $this->method == 'CANCEL') {
+            $event['end'] = clone $event['start'];
+        }
+
         // minimal validation
         if (empty($event['uid']) || ($event['_type'] == 'event' && empty($event['start']) != empty($event['end']))) {
             throw new VObject\ParseException('Object validation failed: missing mandatory object properties');
         }
 
         return $event;
     }
 
     /**
      * Parse the given vfreebusy component into an array representation
      */
     private function _parse_freebusy($ve)
     {
         $this->freebusy = array('_type' => 'freebusy', 'periods' => array());
         $seen = array();
 
         foreach ($ve->children as $prop) {
             if (!($prop instanceof VObject\Property))
                 continue;
 
+            $value = strval($prop);
+
             switch ($prop->name) {
             case 'CREATED':
             case 'LAST-MODIFIED':
             case 'DTSTAMP':
             case 'DTSTART':
             case 'DTEND':
                 $propmap = array('DTSTART' => 'start', 'DTEND' => 'end', 'CREATED' => 'created', 'LAST-MODIFIED' => 'changed', 'DTSTAMP' => 'changed');
-                $this->freebusy[$propmap[$prop->name]] =  self::convert_datetime($prop);
+                $this->freebusy[$propmap[$prop->name]] = self::convert_datetime($prop);
                 break;
 
             case 'ORGANIZER':
-                $this->freebusy['organizer'] = preg_replace('/^mailto:/i', '', $prop->value);
+                $this->freebusy['organizer'] = preg_replace('!^mailto:!i', '', $value);
                 break;
 
             case 'FREEBUSY':
                 // The freebusy component can hold more than 1 value, separated by commas.
-                $periods = explode(',', $prop->value);
+                $periods = explode(',', $value);
                 $fbtype = strval($prop['FBTYPE']) ?: 'BUSY';
 
                 // skip dupes
-                if ($seen[$prop->value.':'.$fbtype]++)
+                if ($seen[$value.':'.$fbtype]++)
                     continue;
 
                 foreach ($periods as $period) {
                     // Every period is formatted as [start]/[end]. The start is an
                     // absolute UTC time, the end may be an absolute UTC time, or
                     // duration (relative) value.
                     list($busyStart, $busyEnd) = explode('/', $period);
 
-                    $busyStart = VObject\DateTimeParser::parse($busyStart);
-                    $busyEnd = VObject\DateTimeParser::parse($busyEnd);
+                    $busyStart = DateTimeParser::parse($busyStart);
+                    $busyEnd = DateTimeParser::parse($busyEnd);
                     if ($busyEnd instanceof \DateInterval) {
                         $tmp = clone $busyStart;
                         $tmp->add($busyEnd);
                         $busyEnd = $tmp;
                     }
 
                     if ($busyEnd && $busyEnd > $busyStart)
                         $this->freebusy['periods'][] = array($busyStart, $busyEnd, $fbtype);
                 }
                 break;
 
             case 'COMMENT':
-                $this->freebusy['comment'] = $prop->value;
+                $this->freebusy['comment'] = $value;
             }
         }
 
         return $this->freebusy;
     }
 
     /**
      *
      */
     public static function convert_string($prop)
     {
-        return str_replace('\,', ',', strval($prop->value));
+        return strval($prop);
+    }
+
+    /**
+     *
+     */
+    public static function unescape($prop)
+    {
+        return str_replace('\,', ',', strval($prop));
     }
 
     /**
      * Helper method to correctly interpret an all-day date value
      */
     public static function convert_datetime($prop, $as_array = false)
     {
         if (empty($prop)) {
             return $as_array ? array() : null;
         }
-        else if ($prop instanceof VObject\Property\MultiDateTime) {
-            $dt = array();
-            $dateonly = ($prop->getDateType() & VObject\Property\DateTime::DATE);
-            foreach ($prop->getDateTimes() as $item) {
-                $item->_dateonly = $dateonly;
-                $dt[] = $item;
-            }
-        }
-        else if ($prop instanceof VObject\Property\DateTime) {
-            $dt = $prop->getDateTime();
-            if ($prop->getDateType() & VObject\Property\DateTime::DATE) {
-                $dt->_dateonly = true;
-            }
-        }
-        else if ($prop instanceof VObject\Property && ($prop['VALUE'] == 'DATE' || $prop['VALUE'] == 'DATE-TIME')) {
-            try {
-                list($type, $dt) = VObject\Property\DateTime::parseData($prop->value, $prop);
-                $dt->_dateonly = ($type & VObject\Property\DateTime::DATE);
+
+        else if ($prop instanceof VObject\Property\iCalendar\DateTime) {
+            if (count($prop->getDateTimes()) > 1) {
+                $dt = array();
+                $dateonly = !$prop->hasTime();
+                foreach ($prop->getDateTimes() as $item) {
+                    $item->_dateonly = $dateonly;
+                    $dt[] = $item;
+                }
             }
-            catch (Exception $e) {
-                // ignore date parse errors
+            else {
+                $dt = $prop->getDateTime();
+                if (!$prop->hasTime()) {
+                    $dt->_dateonly = true;
+                }
             }
         }
-        else if ($prop instanceof VObject\Property && $prop['VALUE'] == 'PERIOD') {
+        else if ($prop instanceof VObject\Property\iCalendar\Period) {
             $dt = array();
-            foreach(explode(',', $prop->value) as $val) {
+            foreach ($prop->getParts() as $val) {
                 try {
                     list($start, $end) = explode('/', $val);
-                    list($type, $item) = VObject\Property\DateTime::parseData($start, $prop);
-                    $item->_dateonly = ($type & VObject\Property\DateTime::DATE);
-                    $dt[] = $item;
+                    $start = DateTimeParser::parseDateTime($start);
+
+                    // This is a duration value.
+                    if ($end[0] === 'P') {
+                        $dur = DateTimeParser::parseDuration($end);
+                        $end = clone $start;
+                        $end->add($dur);
+                    }
+                    else {
+                        $end = DateTimeParser::parseDateTime($end);
+                    }
+                    $dt[] = array($start, $end);
                 }
                 catch (Exception $e) {
                     // ignore single date parse errors
                 }
             }
         }
-        else if ($prop instanceof DateTime) {
+        else if ($prop instanceof \DateTime) {
             $dt = $prop;
         }
 
         // force return value to array if requested
         if ($as_array && !is_array($dt)) {
             $dt = empty($dt) ? array() : array($dt);
         }
 
         return $dt;
     }
 
 
     /**
      * Create a Sabre\VObject\Property instance from a PHP DateTime object
      *
-     * @param string Property name
-     * @param object DateTime
+     * @param object  VObject\Document parent node to create property for
+     * @param string  Property name
+     * @param object  DateTime
+     * @param boolean Set as UTC date
+     * @param boolean Set as VALUE=DATE property
      */
-    public function datetime_prop($name, $dt, $utc = false, $dateonly = null)
+    public function datetime_prop($cal, $name, $dt, $utc = false, $dateonly = null, $set_type = false)
     {
-        $is_utc = $utc || (($tz = $dt->getTimezone()) && in_array($tz->getName(), array('UTC','GMT','Z')));
+        if ($utc) {
+            $dt->setTimeZone(new \DateTimeZone('UTC'));
+            $is_utc = true;
+        }
+        else {
+            $is_utc = ($tz = $dt->getTimezone()) && in_array($tz->getName(), array('UTC','GMT','Z'));
+        }
         $is_dateonly = $dateonly === null ? (bool)$dt->_dateonly : (bool)$dateonly;
-        $vdt = new VObject\Property\DateTime($name);
-        $vdt->setDateTime($dt, $is_dateonly ? VObject\Property\DateTime::DATE :
-            ($is_utc ? VObject\Property\DateTime::UTC : VObject\Property\DateTime::LOCALTZ));
+        $vdt = $cal->createProperty($name, $dt, null, $is_dateonly ? 'DATE' : 'DATE-TIME');
+
+        if ($is_dateonly) {
+            $vdt['VALUE'] = 'DATE';
+        }
+        else if ($set_type) {
+            $vdt['VALUE'] = 'DATE-TIME';
+        }
 
         // register timezone for VTIMEZONE block
         if (!$is_utc && !$dateonly && $tz && ($tzname = $tz->getName())) {
             $ts = $dt->format('U');
             if (is_array($this->vtimezones[$tzname])) {
                 $this->vtimezones[$tzname][0] = min($this->vtimezones[$tzname][0], $ts);
                 $this->vtimezones[$tzname][1] = max($this->vtimezones[$tzname][1], $ts);
             }
             else {
                 $this->vtimezones[$tzname] = array($ts, $ts);
             }
         }
 
         return $vdt;
     }
 
     /**
      * Copy values from one hash array to another using a key-map
      */
     public static function map_keys($values, $map)
     {
         $out = array();
         foreach ($map as $from => $to) {
             if (isset($values[$from]))
                 $out[$to] = is_array($values[$from]) ? join(',', $values[$from]) : $values[$from];
         }
         return $out;
     }
 
     /**
      *
      */
     private static function parameters_array($prop)
     {
         $params = array();
-        foreach ($prop->parameters as $param) {
-            $params[strtoupper($param->name)] = $param->value;
+        foreach ($prop->parameters() as $name => $value) {
+            $params[strtoupper($name)] = $value;
         }
         return $params;
     }
 
 
     /**
      * Export events to iCalendar format
      *
      * @param  array   Events as array
      * @param  string  VCalendar method to advertise
      * @param  boolean Directly send data to stdout instead of returning
      * @param  callable Callback function to fetch attachment contents, false if no attachment export
      * @param  boolean Add VTIMEZONE block with timezone definitions for the included events
      * @return string  Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
      */
     public function export($objects, $method = null, $write = false, $get_attachment = false, $with_timezones = true)
     {
         $this->method = $method;
 
         // encapsulate in VCALENDAR container
-        $vcal = VObject\Component::create('VCALENDAR');
-        $vcal->version = '2.0';
-        $vcal->prodid = $this->prodid;
-        $vcal->calscale = 'GREGORIAN';
+        $vcal = new VObject\Component\VCalendar();
+        $vcal->VERSION = '2.0';
+        $vcal->PRODID = $this->prodid;
+        $vcal->CALSCALE = 'GREGORIAN';
 
         if (!empty($method)) {
             $vcal->METHOD = $method;
         }
 
         // write vcalendar header
         if ($write) {
             echo preg_replace('/END:VCALENDAR[\r\n]*$/m', '', $vcal->serialize());
         }
 
         foreach ($objects as $object) {
             $this->_to_ical($object, !$write?$vcal:false, $get_attachment);
         }
 
         // include timezone information
         if ($with_timezones || !empty($method)) {
             foreach ($this->vtimezones as $tzid => $range) {
-                $vt = self::get_vtimezone($tzid, $range[0], $range[1]);
+                $vt = self::get_vtimezone($tzid, $range[0], $range[1], $vcal);
                 if (empty($vt)) {
                     continue;  // no timezone information found
                 }
 
                 if ($write) {
                     echo $vt->serialize();
                 }
                 else {
                     $vcal->add($vt);
                 }
             }
         }
 
         if ($write) {
             echo "END:VCALENDAR\r\n";
             return true;
         }
         else {
             return $vcal->serialize();
         }
     }
 
     /**
      * Build a valid iCal format block from the given event
      *
      * @param  array    Hash array with event/task properties from libkolab
      * @param  object   VCalendar object to append event to or false for directly sending data to stdout
      * @param  callable Callback function to fetch attachment contents, false if no attachment export
      * @param  object   RECURRENCE-ID property when serializing a recurrence exception
      */
     private function _to_ical($event, $vcal, $get_attachment, $recurrence_id = null)
     {
         $type = $event['_type'] ?: 'event';
-        $ve = VObject\Component::create($this->type_component_map[$type]);
-        $ve->add('UID', $event['uid']);
+
+        $cal = $vcal ?: new VObject\Component\VCalendar();
+        $ve = $cal->create($this->type_component_map[$type]);
+        $ve->UID = $event['uid'];
 
         // set DTSTAMP according to RFC 5545, 3.8.7.2.
-        $dtstamp = !empty($event['changed']) && !empty($this->method) ? $event['changed'] : new DateTime();
-        $ve->add($this->datetime_prop('DTSTAMP', $dtstamp, true));
+        $dtstamp = !empty($event['changed']) && !empty($this->method) ? $event['changed'] : new DateTime('now', new \DateTimeZone('UTC'));
+        $ve->DTSTAMP = $dtstamp;
 
         // all-day events end the next day
         if ($event['allday'] && !empty($event['end'])) {
             $event['end'] = clone $event['end'];
             $event['end']->add(new \DateInterval('P1D'));
             $event['end']->_dateonly = true;
         }
         if (!empty($event['created']))
-            $ve->add($this->datetime_prop('CREATED', $event['created'], true));
+            $ve->add($this->datetime_prop($cal, 'CREATED', $event['created'], true));
         if (!empty($event['changed']))
-            $ve->add($this->datetime_prop('LAST-MODIFIED', $event['changed'], true));
+            $ve->add($this->datetime_prop($cal, 'LAST-MODIFIED', $event['changed'], true));
         if (!empty($event['start']))
-            $ve->add($this->datetime_prop('DTSTART', $event['start'], false, (bool)$event['allday']));
+            $ve->add($this->datetime_prop($cal, 'DTSTART', $event['start'], false, (bool)$event['allday']));
         if (!empty($event['end']))
-            $ve->add($this->datetime_prop('DTEND',   $event['end'], false, (bool)$event['allday']));
+            $ve->add($this->datetime_prop($cal, 'DTEND',   $event['end'], false, (bool)$event['allday']));
         if (!empty($event['due']))
-            $ve->add($this->datetime_prop('DUE',   $event['due'], false));
+            $ve->add($this->datetime_prop($cal, 'DUE',   $event['due'], false));
 
         // we're exporting a recurrence instance only
         if (!$recurrence_id && $event['recurrence_date'] && $event['recurrence_date'] instanceof DateTime) {
-            $recurrence_id = $this->datetime_prop('RECURRENCE-ID', $event['recurrence_date'], false, (bool)$event['allday']);
+            $recurrence_id = $this->datetime_prop($cal, 'RECURRENCE-ID', $event['recurrence_date'], false, (bool)$event['allday']);
             if ($event['thisandfuture'])
                 $recurrence_id->add('RANGE', 'THISANDFUTURE');
         }
 
-        if ($recurrence_id)
+        if ($recurrence_id) {
             $ve->add($recurrence_id);
+        }
 
         $ve->add('SUMMARY', $event['title']);
 
         if ($event['location'])
-            $ve->add($this->is_apple() ? new vobject_location_property('LOCATION', $event['location']) : new VObject\Property('LOCATION', $event['location']));
+            $ve->add($this->is_apple() ? new vobject_location_property($cal, 'LOCATION', $event['location']) : $cal->create('LOCATION', $event['location']));
         if ($event['description'])
             $ve->add('DESCRIPTION', strtr($event['description'], array("\r\n" => "\n", "\r" => "\n"))); // normalize line endings
 
         if (isset($event['sequence']))
             $ve->add('SEQUENCE', $event['sequence']);
 
         if ($event['recurrence'] && !$recurrence_id) {
             $exdates = $rdates = null;
             if (isset($event['recurrence']['EXDATE'])) {
                 $exdates = $event['recurrence']['EXDATE'];
                 unset($event['recurrence']['EXDATE']);  // don't serialize EXDATEs into RRULE value
             }
             if (isset($event['recurrence']['RDATE'])) {
                 $rdates = $event['recurrence']['RDATE'];
                 unset($event['recurrence']['RDATE']);  // don't serialize RDATEs into RRULE value
             }
 
             if ($event['recurrence']['FREQ']) {
                 $ve->add('RRULE', libcalendaring::to_rrule($event['recurrence'], (bool)$event['allday']));
             }
 
             // add EXDATEs each one per line (for Thunderbird Lightning)
             if (is_array($exdates)) {
                 foreach ($exdates as $ex) {
                     if ($ex instanceof \DateTime) {
                         $exd = clone $event['start'];
                         $exd->setDate($ex->format('Y'), $ex->format('n'), $ex->format('j'));
                         $exd->setTimeZone(new \DateTimeZone('UTC'));
-                        $ve->add(new VObject\Property('EXDATE', $exd->format('Ymd\\THis\\Z')));
+                        $ve->add($this->datetime_prop($cal, 'EXDATE', $exd, true));
                     }
                 }
             }
             // add RDATEs
-            if (is_array($rdates) && !empty($rdates)) {
-                $sample = $this->datetime_prop('RDATE', $rdates[0]);
-                $rdprop = new VObject\Property\MultiDateTime('RDATE', null);
-                $rdprop->setDateTimes($rdates, $sample->getDateType());
-                $ve->add($rdprop);
+            if (!empty($rdates)) {
+                foreach ((array)$rdates as $rdate) {
+                    $ve->add($this->datetime_prop($cal, 'RDATE', $rdate));
+                }
             }
         }
 
         if ($event['categories']) {
-            $cat = VObject\Property::create('CATEGORIES');
+            $cat = $cal->create('CATEGORIES');
             $cat->setParts((array)$event['categories']);
             $ve->add($cat);
         }
 
         if (!empty($event['free_busy'])) {
             $ve->add('TRANSP', $event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE');
 
             // for Outlook clients we provide the X-MICROSOFT-CDO-BUSYSTATUS property
             if (stripos($this->agent, 'outlook') !== false) {
                 $ve->add('X-MICROSOFT-CDO-BUSYSTATUS', $event['free_busy'] == 'outofoffice' ? 'OOF' : strtoupper($event['free_busy']));
             }
         }
 
         if ($event['priority'])
           $ve->add('PRIORITY', $event['priority']);
 
         if ($event['cancelled'])
             $ve->add('STATUS', 'CANCELLED');
         else if ($event['free_busy'] == 'tentative')
             $ve->add('STATUS', 'TENTATIVE');
         else if ($event['complete'] == 100)
             $ve->add('STATUS', 'COMPLETED');
         else if (!empty($event['status']))
             $ve->add('STATUS', $event['status']);
 
         if (!empty($event['sensitivity']))
             $ve->add('CLASS', strtoupper($event['sensitivity']));
 
         if (!empty($event['complete'])) {
             $ve->add('PERCENT-COMPLETE', intval($event['complete']));
             // Apple iCal required the COMPLETED date to be set in order to consider a task complete
             if ($event['complete'] == 100)
-                $ve->add($this->datetime_prop('COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
+                $ve->add($this->datetime_prop($cal, 'COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
         }
 
         if ($event['valarms']) {
             foreach ($event['valarms'] as $alarm) {
-                $va = VObject\Component::create('VALARM');
+                $va = $cal->createComponent('VALARM');
                 $va->action = $alarm['action'];
                 if ($alarm['trigger'] instanceof DateTime) {
-                    $va->add($this->datetime_prop('TRIGGER', $alarm['trigger'], true));
+                    $va->add($this->datetime_prop($cal, 'TRIGGER', $alarm['trigger'], true, null, true));
                 }
                 else {
                     $va->add('TRIGGER', $alarm['trigger']);
                 }
 
                 if ($alarm['action'] == 'EMAIL') {
                     foreach ((array)$alarm['attendees'] as $attendee) {
                         $va->add('ATTENDEE', 'mailto:' . $attendee);
                     }
                 }
                 if ($alarm['description']) {
                     $va->add('DESCRIPTION', $alarm['description'] ?: $event['title']);
                 }
                 if ($alarm['summary']) {
                     $va->add('SUMMARY', $alarm['summary']);
                 }
                 if ($alarm['duration']) {
                     $va->add('DURATION', $alarm['duration']);
                     $va->add('REPEAT', intval($alarm['repeat']));
                 }
                 if ($alarm['uri']) {
                     $va->add('ATTACH', $alarm['uri'], array('VALUE' => 'URI'));
                 }
                 $ve->add($va);
             }
         }
         // legacy support
         else if ($event['alarms']) {
-            $va = VObject\Component::create('VALARM');
+            $va = $cal->createComponent('VALARM');
             list($trigger, $va->action) = explode(':', $event['alarms']);
             $val = libcalendaring::parse_alarm_value($trigger);
             if ($val[3])
                 $va->add('TRIGGER', $val[3]);
             else if ($val[0] instanceof DateTime)
-                $va->add($this->datetime_prop('TRIGGER', $val[0]));
+                $va->add($this->datetime_prop($cal, 'TRIGGER', $val[0], true, null, true));
             $ve->add($va);
         }
 
         foreach ((array)$event['attendees'] as $attendee) {
             if ($attendee['role'] == 'ORGANIZER') {
                 if (empty($event['organizer']))
                     $event['organizer'] = $attendee;
             }
             else if (!empty($attendee['email'])) {
                 if (isset($attendee['rsvp']))
                     $attendee['rsvp'] = $attendee['rsvp'] ? 'TRUE' : null;
-                $ve->add('ATTENDEE', 'mailto:' . $attendee['email'], array_filter(self::map_keys($attendee, $this->attendee_keymap)));
+                $ve->add('ATTENDEE', 'mailto:' . $attendee['email'],
+                    array_filter(self::map_keys($attendee, $this->attendee_keymap)));
             }
         }
 
         if ($event['organizer']) {
-            $ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'], self::map_keys($event['organizer'], array('name' => 'CN')));
+            $ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'],
+                array_filter(self::map_keys($event['organizer'], array('name' => 'CN'))));
         }
 
         foreach ((array)$event['url'] as $url) {
             if (!empty($url)) {
                 $ve->add('URL', $url);
             }
         }
 
         if (!empty($event['parent_id'])) {
             $ve->add('RELATED-TO', $event['parent_id'], array('RELTYPE' => 'PARENT'));
         }
 
         if ($event['comment'])
             $ve->add('COMMENT', $event['comment']);
 
         $memory_limit = parse_bytes(ini_get('memory_limit'));
 
         // export attachments
         if (!empty($event['attachments'])) {
             foreach ((array)$event['attachments'] as $attach) {
                 // check available memory and skip attachment export if we can't buffer it
                 // @todo: use rcube_utils::mem_check()
                 if (is_callable($get_attachment) && $memory_limit > 0 && ($memory_used = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024)
                     && $attach['size'] && $memory_used + $attach['size'] * 3 > $memory_limit) {
                     continue;
                 }
                 // embed attachments using the given callback function
                 if (is_callable($get_attachment) && ($data = call_user_func($get_attachment, $attach['id'], $event))) {
                     // embed attachments for iCal
                     $ve->add('ATTACH',
-                        base64_encode($data),
+                        $data,
                         array_filter(array('VALUE' => 'BINARY', 'ENCODING' => 'BASE64', 'FMTTYPE' => $attach['mimetype'], 'X-LABEL' => $attach['name'])));
                     unset($data);  // attempt to free memory
                 }
                 // list attachments as absolute URIs
                 else if (!empty($this->attach_uri)) {
                     $ve->add('ATTACH',
                         strtr($this->attach_uri, array(
                             '{{id}}'       => urlencode($attach['id']),
                             '{{name}}'     => urlencode($attach['name']),
                             '{{mimetype}}' => urlencode($attach['mimetype']),
                         )),
                         array('FMTTYPE' => $attach['mimetype'], 'VALUE' => 'URI'));
                 }
             }
         }
 
         foreach ((array)$event['links'] as $uri) {
             $ve->add('ATTACH', $uri);
         }
 
         // add custom properties
         foreach ((array)$event['x-custom'] as $prop) {
             $ve->add($prop[0], $prop[1]);
         }
 
         // append to vcalendar container
         if ($vcal) {
             $vcal->add($ve);
         }
         else {   // serialize and send to stdout
             echo $ve->serialize();
         }
 
         // append recurrence exceptions
         if (is_array($event['recurrence']) && $event['recurrence']['EXCEPTIONS']) {
             foreach ($event['recurrence']['EXCEPTIONS'] as $ex) {
                 $exdate = $ex['recurrence_date'] ?: $ex['start'];
-                $recurrence_id = $this->datetime_prop('RECURRENCE-ID', $exdate, false, (bool)$event['allday']);
+                $recurrence_id = $this->datetime_prop($cal, 'RECURRENCE-ID', $exdate, false, (bool)$event['allday']);
                 if ($ex['thisandfuture'])
                     $recurrence_id->add('RANGE', 'THISANDFUTURE');
                 $this->_to_ical($ex, $vcal, $get_attachment, $recurrence_id);
             }
         }
     }
 
     /**
      * Returns a VTIMEZONE component for a Olson timezone identifier
      * with daylight transitions covering the given date range.
      *
      * @param string Timezone ID as used in PHP's Date functions
      * @param integer Unix timestamp with first date/time in this timezone
      * @param integer Unix timestap with last date/time in this timezone
      *
      * @return mixed A Sabre\VObject\Component object representing a VTIMEZONE definition
      *               or false if no timezone information is available
      */
-    public static function get_vtimezone($tzid, $from = 0, $to = 0)
+    public static function get_vtimezone($tzid, $from = 0, $to = 0, $cal = null)
     {
         if (!$from) $from = time();
         if (!$to)   $to = $from;
+        if (!$cal)  $cal = new VObject\Component\VCalendar();
 
         if (is_string($tzid)) {
             try {
                 $tz = new \DateTimeZone($tzid);
             }
             catch (\Exception $e) {
                 return false;
             }
         }
         else if (is_a($tzid, '\\DateTimeZone')) {
             $tz = $tzid;
         }
 
         if (!is_a($tz, '\\DateTimeZone')) {
             return false;
         }
 
         $year = 86400 * 360;
         $transitions = $tz->getTransitions($from - $year, $to + $year);
 
-        $vt = new VObject\Component('VTIMEZONE');
+        $vt = $cal->createComponent('VTIMEZONE');
         $vt->TZID = $tz->getName();
 
         $std = null; $dst = null;
         foreach ($transitions as $i => $trans) {
             $cmp = null;
 
             if ($i == 0) {
                 $tzfrom = $trans['offset'] / 3600;
                 continue;
             }
 
             if ($trans['isdst']) {
                 $t_dst = $trans['ts'];
-                $dst = new VObject\Component('DAYLIGHT');
+                $dst = $cal->createComponent('DAYLIGHT');
                 $cmp = $dst;
             }
             else {
                 $t_std = $trans['ts'];
-                $std = new VObject\Component('STANDARD');
+                $std = $cal->createComponent('STANDARD');
                 $cmp = $std;
             }
 
             if ($cmp) {
                 $dt = new DateTime($trans['time']);
                 $offset = $trans['offset'] / 3600;
 
                 $cmp->DTSTART = $dt->format('Ymd\THis');
                 $cmp->TZOFFSETFROM = sprintf('%s%02d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom), ($tzfrom - floor($tzfrom)) * 60);
                 $cmp->TZOFFSETTO   = sprintf('%s%02d%02d', $offset >= 0 ? '+' : '', floor($offset), ($offset - floor($offset)) * 60);
 
                 if (!empty($trans['abbr'])) {
                     $cmp->TZNAME = $trans['abbr'];
                 }
 
                 $tzfrom = $offset;
                 $vt->add($cmp);
             }
 
             // we covered the entire date range
             if ($std && $dst && min($t_std, $t_dst) < $from && max($t_std, $t_dst) > $to) {
                 break;
             }
         }
 
         // add X-MICROSOFT-CDO-TZID if available
         $microsoftExchangeMap = array_flip(VObject\TimeZoneUtil::$microsoftExchangeMap);
         if (array_key_exists($tz->getName(), $microsoftExchangeMap)) {
             $vt->add('X-MICROSOFT-CDO-TZID', $microsoftExchangeMap[$tz->getName()]);
         }
 
         return $vt;
     }
 
 
     /*** Implement PHP 5 Iterator interface to make foreach work ***/
 
     function current()
     {
         return $this->objects[$this->iteratorkey];
     }
 
     function key()
     {
         return $this->iteratorkey;
     }
 
     function next()
     {
         $this->iteratorkey++;
 
         // read next chunk if we're reading from a file
         if (!$this->objects[$this->iteratorkey] && $this->fp) {
             $this->_parse_next(true);
         }
 
         return $this->valid();
     }
 
     function rewind()
     {
         $this->iteratorkey = 0;
     }
 
     function valid()
     {
         return !empty($this->objects[$this->iteratorkey]);
     }
 
 }
 
 
 /**
- * Override Sabre\VObject\Property that quotes commas in the location property
+ * Override Sabre\VObject\Property\Text that quotes commas in the location property
  * because Apple clients treat that property as list.
  */
-class vobject_location_property extends VObject\Property
+class vobject_location_property extends VObject\Property\Text
 {
     /**
-     * Turns the object back into a serialized blob.
+     * List of properties that are considered 'structured'.
      *
-     * @return string
+     * @var array
      */
-    public function serialize()
-    {
-        $str = $this->name;
+    protected $structuredValues = array(
+        // vCard
+        'N',
+        'ADR',
+        'ORG',
+        'GENDER',
+        'LOCATION',
+        // iCalendar
+        'REQUEST-STATUS',
+    );
 
-        foreach ($this->parameters as $param) {
-            $str.=';' . $param->serialize();
-        }
-
-        $src = array(
-            '\\',
-            "\n",
-            ',',
-        );
-        $out = array(
-            '\\\\',
-            '\n',
-            '\,',
-        );
-        $str.=':' . str_replace($src, $out, $this->value);
-
-        $out = '';
-        while (strlen($str) > 0) {
-            if (strlen($str) > 75) {
-                $out.= mb_strcut($str, 0, 75, 'utf-8') . "\r\n";
-                $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8');
-            } else {
-                $out.= $str . "\r\n";
-                $str = '';
-                break;
-            }
-        }
-
-        return $out;
-    }
 }
diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php
index e0e87636..d401a960 100644
--- a/plugins/libcalendaring/tests/libvcalendar.php
+++ b/plugins/libcalendaring/tests/libvcalendar.php
@@ -1,562 +1,587 @@
 <?php
 
 /**
  * libcalendaring plugin's iCalendar functions tests
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
- * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
+ * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 class libvcalendar_test extends PHPUnit_Framework_TestCase
 {
     function setUp()
     {
         require_once __DIR__ . '/../libvcalendar.php';
         require_once __DIR__ . '/../libcalendaring.php';
     }
 
     /**
      * Simple iCal parsing test
      */
     function test_import()
     {
         $ical = new libvcalendar();
         $ics = file_get_contents(__DIR__ . '/resources/snd.ics');
         $events = $ical->import($ics, 'UTF-8');
 
         $this->assertEquals(1, count($events));
         $event = $events[0];
 
         $this->assertInstanceOf('DateTime', $event['created'], "'created' property is DateTime object");
         $this->assertInstanceOf('DateTime', $event['changed'], "'changed' property is DateTime object");
         $this->assertEquals('UTC', $event['created']->getTimezone()->getName(), "'created' date is in UTC");
 
         $this->assertInstanceOf('DateTime', $event['start'], "'start' property is DateTime object");
         $this->assertInstanceOf('DateTime', $event['end'], "'end' property is DateTime object");
         $this->assertEquals('08-01', $event['start']->format('m-d'), "Start date is August 1st");
         $this->assertTrue($event['allday'], "All-day event flag");
 
         $this->assertEquals('B968B885-08FB-40E5-B89E-6DA05F26AA79', $event['uid'], "Event UID");
         $this->assertEquals('Swiss National Day', $event['title'], "Event title");
         $this->assertEquals('http://en.wikipedia.org/wiki/Swiss_National_Day', $event['url'], "URL property");
         $this->assertEquals(2, $event['sequence'], "Sequence number");
 
         $desclines = explode("\n", $event['description']);
         $this->assertEquals(4, count($desclines), "Multiline description");
         $this->assertEquals("French: Fête nationale Suisse", rtrim($desclines[1]), "UTF-8 encoding");
     }
 
     /**
      * Test parsing from files
      */
     function test_import_from_file()
     {
         $ical = new libvcalendar();
 
         $events = $ical->import_from_file(__DIR__ . '/resources/multiple.ics', 'UTF-8');
         $this->assertEquals(2, count($events));
 
         $events = $ical->import_from_file(__DIR__ . '/resources/invalid.txt', 'UTF-8');
         $this->assertEmpty($events);
     }
 
     /**
      * Test parsing from files with multiple VCALENDAR blocks (#2884)
      */
     function test_import_from_file_multiple()
     {
         $ical = new libvcalendar();
         $ical->fopen(__DIR__ . '/resources/multiple-rdate.ics', 'UTF-8');
         $events = array();
         foreach ($ical as $event) {
             $events[] = $event;
         }
 
         $this->assertEquals(2, count($events));
         $this->assertEquals("AAAA6A8C3CCE4EE2C1257B5C00FFFFFF-Lotus_Notes_Generated", $events[0]['uid']);
         $this->assertEquals("AAAA1C572093EC3FC125799C004AFFFF-Lotus_Notes_Generated", $events[1]['uid']);
     }
 
     function test_invalid_dates()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/invalid-dates.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals(1, count($events), "Import event data");
-        $this->assertFalse(array_key_exists('created', $event), "No created date field");
+        $this->assertInstanceOf('DateTime', $event['created'], "Created date field");
         $this->assertFalse(array_key_exists('changed', $event), "No changed date field");
     }
 
     function test_invalid_vevent()
     {
         $this->setExpectedException('\Sabre\VObject\ParseException');
 
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/invalid-event.ics', 'UTF-8', true);
     }
 
     /**
      * Test some extended ical properties such as attendees, recurrence rules, alarms and attachments
      */
     function test_extended()
     {
         $ical = new libvcalendar();
 
         $events = $ical->import_from_file(__DIR__ . '/resources/itip.ics', 'UTF-8');
         $event = $events[0];
         $this->assertEquals('REQUEST', $ical->method, "iTip method");
 
         // attendees
-        $this->assertEquals(2, count($event['attendees']), "Attendees list (including organizer)");
+        $this->assertEquals(3, count($event['attendees']), "Attendees list (including organizer)");
         $organizer = $event['attendees'][0];
         $this->assertEquals('ORGANIZER', $organizer['role'], 'Organizer ROLE');
         $this->assertEquals('Rolf Test', $organizer['name'], 'Organizer name');
 
         $attendee = $event['attendees'][1];
         $this->assertEquals('REQ-PARTICIPANT', $attendee['role'], 'Attendee ROLE');
         $this->assertEquals('NEEDS-ACTION', $attendee['status'], 'Attendee STATUS');
         $this->assertEquals('rolf2@mykolab.com', $attendee['email'], 'Attendee mailto:');
+        $this->assertEquals('carl@mykolab.com', $attendee['delegated-from'], 'Attendee delegated-from');
         $this->assertTrue($attendee['rsvp'], 'Attendee RSVP');
 
+        $delegator = $event['attendees'][2];
+        $this->assertEquals('NON-PARTICIPANT',   $delegator['role'], 'Delegator ROLE');
+        $this->assertEquals('DELEGATED',         $delegator['status'], 'Delegator STATUS');
+        $this->assertEquals('INDIVIDUAL',        $delegator['cutype'], 'Delegator CUTYPE');
+        $this->assertEquals('carl@mykolab.com',  $delegator['email'], 'Delegator mailto:');
+        $this->assertEquals('rolf2@mykolab.com', $delegator['delegated-to'], 'Delegator delegated-to');
+        $this->assertFalse($delegator['rsvp'],   'Delegator RSVP');
+
         // attachments
         $this->assertEquals(1, count($event['attachments']), "Embedded attachments");
         $attachment = $event['attachments'][0];
         $this->assertEquals('text/html',                 $attachment['mimetype'], "Attachment mimetype attribute");
         $this->assertEquals('calendar.html',             $attachment['name'],     "Attachment filename (X-LABEL) attribute");
         $this->assertContains('<title>Kalender</title>', $attachment['data'],     "Attachment content (decoded)");
 
         // recurrence rules
         $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertTrue(is_array($event['recurrence']), 'Recurrences rule as hash array');
         $rrule = $event['recurrence'];
         $this->assertEquals('MONTHLY',      $rrule['FREQ'],     "Recurrence frequency");
         $this->assertEquals('1',            $rrule['INTERVAL'], "Recurrence interval");
         $this->assertEquals('3WE',          $rrule['BYDAY'],    "Recurrence frequency");
         $this->assertInstanceOf('DateTime', $rrule['UNTIL'],    "Recurrence end date");
 
         $this->assertEquals(2, count($rrule['EXDATE']),          "Recurrence EXDATEs");
         $this->assertInstanceOf('DateTime', $rrule['EXDATE'][0], "Recurrence EXDATE as DateTime");
 
+        $this->assertTrue(is_array($rrule['EXCEPTIONS']));
+        $this->assertEquals(1, count($rrule['EXCEPTIONS']), "Recurrence Exceptions");
+
+        $exception = $rrule['EXCEPTIONS'][0];
+        $this->assertEquals($event['uid'],  $event['uid'], "Exception UID");
+        $this->assertEquals('Recurring Test (Exception)',  $exception['title'], "Exception title");
+        $this->assertInstanceOf('DateTime', $exception['start'], "Exception start");
+
         // categories, class
         $this->assertEquals('libcalendaring tests', join(',', (array)$event['categories']), "Event categories");
         $this->assertEquals('confidential', $event['sensitivity'], "Class/sensitivity = confidential");
 
         // parse a recurrence chain instance
         $events = $ical->import_from_file(__DIR__ . '/resources/recurrence-id.ics', 'UTF-8');
         $this->assertEquals(1, count($events), "Fall back to Component::getComponents() when getBaseComponents() is empty");
         $this->assertInstanceOf('DateTime', $events[0]['recurrence_date'], "Recurrence-ID as date");
         $this->assertTrue($events[0]['thisandfuture'], "Range=THISANDFUTURE");
 
         $this->assertEquals(count($events[0]['exceptions']), 1, "Second VEVENT as exception");
         $this->assertEquals($events[0]['exceptions'][0]['uid'], $events[0]['uid'], "Exception UID match");
         $this->assertEquals($events[0]['exceptions'][0]['sequence'], '2', "Exception sequence");
     }
 
     /**
      * 
      */
     function test_alarms()
     {
         $ical = new libvcalendar();
 
         $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals('-12H:DISPLAY', $event['alarms'], "Serialized alarms string");
         $alarm = libcalendaring::parse_alarm_value($event['alarms']);
         $this->assertEquals('12', $alarm[0], "Alarm value");
         $this->assertEquals('-H', $alarm[1], "Alarm unit");
 
         $this->assertEquals('DISPLAY', $event['valarms'][0]['action'],  "Full alarm item (action)");
         $this->assertEquals('-PT12H',  $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
 
         // alarm trigger with 0 values
         $events = $ical->import_from_file(__DIR__ . '/resources/alarms.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals('-30M:DISPLAY', $event['alarms'], "Stripped alarm string");
         $alarm = libcalendaring::parse_alarm_value($event['alarms']);
         $this->assertEquals('30', $alarm[0], "Alarm value");
         $this->assertEquals('-M', $alarm[1], "Alarm unit");
         $this->assertEquals('-30M', $alarm[2], "Alarm string");
         $this->assertEquals('-PT30M', $alarm[3], "Unified alarm string (stripped zero-values)");
 
         $this->assertEquals('DISPLAY', $event['valarms'][0]['action'],  "First alarm action");
         $this->assertEquals('This is the first event reminder', $event['valarms'][0]['description'],  "First alarm text");
 
-        $this->assertEquals(2, count($event['valarms']), "List all VALARM blocks");
+        $this->assertEquals(3, count($event['valarms']), "List all VALARM blocks");
 
         $valarm = $event['valarms'][1];
         $this->assertEquals(1, count($valarm['attendees']), "Email alarm attendees");
         $this->assertEquals('EMAIL', $valarm['action'],  "Second alarm item (action)");
         $this->assertEquals('-P1D',  $valarm['trigger'], "Second alarm item (trigger)");
         $this->assertEquals('This is the reminder message',  $valarm['summary'], "Email alarm text");
+        $this->assertInstanceOf('DateTime', $event['valarms'][2]['trigger'], "Absolute trigger date/time");
 
         // test alarms export
         $ics = $ical->export(array($event));
         $this->assertContains('ACTION:DISPLAY',   $ics, "Display alarm block");
         $this->assertContains('ACTION:EMAIL',     $ics, "Email alarm block");
         $this->assertContains('DESCRIPTION:This is the first event reminder',    $ics, "Alarm description");
         $this->assertContains('SUMMARY:This is the reminder message',            $ics, "Email alarm summary");
         $this->assertContains('ATTENDEE:mailto:reminder-recipient@example.org',  $ics, "Email alarm recipient");
+        $this->assertContains('TRIGGER;VALUE=DATE-TIME:20130812',  $ics, "Date-Time trigger");
     }
 
     /**
      * @depends test_import_from_file
      */
     function test_attachment()
     {
         $ical = new libvcalendar();
 
         $events = $ical->import_from_file(__DIR__ . '/resources/attachment.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals(2, count($events));
         $this->assertEquals(1, count($event['attachments']));
         $this->assertEquals('image/png', $event['attachments'][0]['mimetype']);
         $this->assertEquals('500px-Opensource.svg.png', $event['attachments'][0]['name']);
     }
 
     /**
      * @depends test_import
      */
     function test_apple_alarms()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/apple-alarms.ics', 'UTF-8');
         $event = $events[0];
 
         // alarms
         $this->assertEquals('-45M:AUDIO', $event['alarms'], "Relative alarm string");
         $alarm = libcalendaring::parse_alarm_value($event['alarms']);
         $this->assertEquals('45', $alarm[0], "Alarm value");
         $this->assertEquals('-M', $alarm[1], "Alarm unit");
 
         $this->assertEquals(1, count($event['valarms']), "Ignore invalid alarm blocks");
         $this->assertEquals('AUDIO', $event['valarms'][0]['action'],   "Full alarm item (action)");
         $this->assertEquals('-PT45M', $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
         $this->assertEquals('Basso',  $event['valarms'][0]['uri'],     "Full alarm item (attachment)");
     }
 
     /**
      * 
      */
     function test_escaped_values()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/escaped.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals("House, Street, Zip Place", $event['location'], "Decode escaped commas in location value");
         $this->assertEquals("Me, meets Them\nThem, meet Me", $event['description'], "Decode description value");
         $this->assertEquals("Kolab, Thomas", $event['attendees'][3]['name'], "Unescaped");
 
         $ics = $ical->export($events);
         $this->assertContains('ATTENDEE;CN="Kolab, Thomas";PARTSTAT=', $ics, "Quoted attendee parameters");
     }
 
     /**
      * Parse RDATE properties (#2885)
      */
     function test_rdate()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/multiple-rdate.ics', 'UTF-8');
         $event = $events[0];
 
         $this->assertEquals(9, count($event['recurrence']['RDATE']));
         $this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][0]);
+        $this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][1]);
     }
 
     /**
      * @depends test_import
      */
     function test_freebusy()
     {
         $ical = new libvcalendar();
         $ical->import_from_file(__DIR__ . '/resources/freebusy.ifb', 'UTF-8');
         $freebusy = $ical->freebusy;
 
         $this->assertInstanceOf('DateTime', $freebusy['start'], "'start' property is DateTime object");
         $this->assertInstanceOf('DateTime', $freebusy['end'], "'end' property is DateTime object");
         $this->assertEquals(11, count($freebusy['periods']), "Number of freebusy periods defined");
-        $this->assertEquals(9, count($ical->get_busy_periods()), "Number of busy periods found");
+        $periods = $ical->get_busy_periods();
+        $this->assertEquals(9, count($periods), "Number of busy periods found");
+        $this->assertEquals('BUSY-TENTATIVE', $periods[8][2], "FBTYPE=BUSY-TENTATIVE");
     }
 
     /**
      * @depends test_import
      */
     function test_freebusy_dummy()
     {
         $ical = new libvcalendar();
         $ical->import_from_file(__DIR__ . '/resources/dummy.ifb', 'UTF-8');
         $freebusy = $ical->freebusy;
 
         $this->assertEquals(0, count($freebusy['periods']), "Ignore 0-length freebudy periods");
         $this->assertContains('dummy', $freebusy['comment'], "Parse comment");
     }
 
     function test_vtodo()
     {
         $ical = new libvcalendar();
         $tasks = $ical->import_from_file(__DIR__ . '/resources/vtodo.ics', 'UTF-8', true);
         $task = $tasks[0];
 
         $this->assertInstanceOf('DateTime', $task['start'],   "'start' property is DateTime object");
         $this->assertInstanceOf('DateTime', $task['due'],     "'due' property is DateTime object");
         $this->assertEquals('-1D:DISPLAY',  $task['alarms'],  "Taks alarm value");
         $this->assertEquals('IN-PROCESS',   $task['status'],  "Task status property");
         $this->assertEquals(1, count($task['x-custom']),      "Custom properties");
         $this->assertEquals(4, count($task['categories']));
         $this->assertEquals('1234567890-12345678-PARENT', $task['parent_id'], "Parent Relation");
     }
 
     /**
      * Test for iCal export from internal hash array representation
      *
      * 
      */
     function test_export()
     {
         $ical = new libvcalendar();
 
         $events = $ical->import_from_file(__DIR__ . '/resources/itip.ics', 'UTF-8');
         $event = $events[0];
         $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8');
         $event += $events[0];
 
         $this->attachment_data = $event['attachments'][0]['data'];
         unset($event['attachments'][0]['data']);
         $event['attachments'][0]['id'] = '1';
         $event['description'] = '*Exported by libvcalendar*';
 
         $event['start']->setTimezone(new DateTimezone('Europe/Berlin'));
         $event['end']->setTimezone(new DateTimezone('Europe/Berlin'));
 
         $ics = $ical->export(array($event), 'REQUEST', false, array($this, 'get_attachment_data'), true);
 
         $this->assertContains('BEGIN:VCALENDAR',    $ics, "VCALENDAR encapsulation BEGIN");
 
         $this->assertContains('BEGIN:VTIMEZONE', $ics, "VTIMEZONE encapsulation BEGIN");
         $this->assertContains('TZID:Europe/Berlin', $ics, "Timezone ID");
         $this->assertContains('TZOFFSETFROM:+0100', $ics, "Timzone transition FROM");
         $this->assertContains('TZOFFSETTO:+0200', $ics, "Timzone transition TO");
         $this->assertContains('END:VTIMEZONE', $ics, "VTIMEZONE encapsulation END");
 
         $this->assertContains('BEGIN:VEVENT',       $ics, "VEVENT encapsulation BEGIN");
         $this->assertContains('UID:ac6b0aee-2519-4e5c-9a25-48c57064c9f0', $ics, "Event UID");
         $this->assertContains('SEQUENCE:' . $event['sequence'],           $ics, "Export Sequence number");
         $this->assertContains('CLASS:CONFIDENTIAL',                       $ics, "Sensitivity => Class");
         $this->assertContains('DESCRIPTION:*Exported by',                 $ics, "Export Description");
-        $this->assertContains('ORGANIZER;CN=Rolf Test:mailto:rolf@',    $ics, "Export organizer");
+        $this->assertContains('ORGANIZER;CN=Rolf Test:mailto:rolf@',      $ics, "Export organizer");
         $this->assertRegExp('/ATTENDEE.*;ROLE=REQ-PARTICIPANT/',          $ics, "Export Attendee ROLE");
         $this->assertRegExp('/ATTENDEE.*;PARTSTAT=NEEDS-ACTION/',         $ics, "Export Attendee Status");
         $this->assertRegExp('/ATTENDEE.*;RSVP=TRUE/',                     $ics, "Export Attendee RSVP");
-        $this->assertRegExp('/ATTENDEE.*:mailto:rolf2@/',                 $ics, "Export Attendee mailto:");
+        $this->assertRegExp('/:mailto:rolf2@/',                           $ics, "Export Attendee mailto:");
 
         $rrule = $event['recurrence'];
         $this->assertRegExp('/RRULE:.*FREQ='.$rrule['FREQ'].'/',          $ics, "Export Recurrence Frequence");
         $this->assertRegExp('/RRULE:.*INTERVAL='.$rrule['INTERVAL'].'/',  $ics, "Export Recurrence Interval");
         $this->assertRegExp('/RRULE:.*UNTIL=20140718T215959Z/',           $ics, "Export Recurrence End date");
         $this->assertRegExp('/RRULE:.*BYDAY='.$rrule['BYDAY'].'/',        $ics, "Export Recurrence BYDAY");
         $this->assertRegExp('/EXDATE.*:20131218/',     $ics, "Export Recurrence EXDATE");
 
         $this->assertContains('BEGIN:VALARM',   $ics, "Export VALARM");
         $this->assertContains('TRIGGER:-PT12H', $ics, "Export Alarm trigger");
 
         $this->assertRegExp('/ATTACH.*;VALUE=BINARY/',                    $ics, "Embed attachment");
         $this->assertRegExp('/ATTACH.*;ENCODING=BASE64/',                 $ics, "Attachment B64 encoding");
         $this->assertRegExp('!ATTACH.*;FMTTYPE=text/html!',               $ics, "Attachment mimetype");
         $this->assertRegExp('!ATTACH.*;X-LABEL=calendar.html!',           $ics, "Attachment filename with X-LABEL");
 
         $this->assertContains('END:VEVENT',     $ics, "VEVENT encapsulation END");
         $this->assertContains('END:VCALENDAR',  $ics, "VCALENDAR encapsulation END");
     }
 
     /**
      * @depends test_extended
      * @depends test_export
      */
     function test_export_multiple()
     {
         $ical = new libvcalendar();
         $events = array_merge(
             $ical->import_from_file(__DIR__ . '/resources/snd.ics', 'UTF-8'),
             $ical->import_from_file(__DIR__ . '/resources/multiple.ics', 'UTF-8')
         );
 
         $num = count($events);
         $ics = $ical->export($events, null, false);
 
         $this->assertContains('BEGIN:VCALENDAR', $ics, "VCALENDAR encapsulation BEGIN");
         $this->assertContains('END:VCALENDAR',   $ics, "VCALENDAR encapsulation END");
         $this->assertEquals($num, substr_count($ics, 'BEGIN:VEVENT'), "VEVENT encapsulation BEGIN");
         $this->assertEquals($num, substr_count($ics, 'END:VEVENT'),   "VEVENT encapsulation END");
     }
 
     /**
      * @depends test_export
      */
     function test_export_recurrence_exceptions()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8');
 
         // add exceptions
         $event = $events[0];
+        unset($event['recurrence']['EXCEPTIONS']);
+
         $exception1 = $event;
         $exception1['start'] = clone $event['start'];
         $exception1['start']->setDate(2013, 8, 14);
         $exception1['end'] = clone $event['end'];
         $exception1['end']->setDate(2013, 8, 14);
 
         $exception2 = $event;
         $exception2['start'] = clone $event['start'];
         $exception2['start']->setDate(2013, 11, 13);
         $exception2['end'] = clone $event['end'];
         $exception2['end']->setDate(2013, 11, 13);
         $exception2['title'] = 'Recurring Exception';
 
         $events[0]['recurrence']['EXCEPTIONS'] = array($exception1, $exception2);
 
         $ics = $ical->export($events, null, false);
 
         $num = count($events[0]['recurrence']['EXCEPTIONS']) + 1;
         $this->assertEquals($num, substr_count($ics, 'BEGIN:VEVENT'),       "VEVENT encapsulation BEGIN");
         $this->assertEquals($num, substr_count($ics, 'UID:'.$event['uid']), "Recurrence Exceptions with same UID");
         $this->assertEquals($num, substr_count($ics, 'END:VEVENT'),         "VEVENT encapsulation END");
 
-        $this->assertContains('RECURRENCE-ID;VALUE=DATE-TIME;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date");
-        $this->assertContains('RECURRENCE-ID;VALUE=DATE-TIME;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date");
+        $this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date");
+        $this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date");
         $this->assertContains('SUMMARY:'.$exception2['title'], $ics, "Exception title");
     }
 
     function test_export_valid_rrules()
     {
         $event = array(
             'uid' => '1234567890',
             'start' => new DateTime('now'),
             'end' => new DateTime('now + 30min'),
             'title' => 'test_export_valid_rrules',
             'recurrence' => array(
                 'FREQ' => 'DAILY',
                 'COUNT' => 5,
                 'EXDATE' => array(),
                 'RDATE' => array(),
             ),
         );
         $ical = new libvcalendar();
         $ics = $ical->export(array($event), null, false, null, false);
 
         $this->assertNotContains('EXDATE=', $ics);
         $this->assertNotContains('RDATE=', $ics);
     }
 
     /**
      *
      */
     function test_export_rdate()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/multiple-rdate.ics', 'UTF-8');
         $ics = $ical->export($events, null, false);
 
-        $this->assertContains('RDATE;VALUE=DATE-TIME:20140520T020000Z', $ics, "VALUE=PERIOD is translated into single DATE-TIME values");
+        $this->assertContains('RDATE:20140520T020000Z', $ics, "VALUE=PERIOD is translated into single DATE-TIME values");
     }
 
     /**
      * @depends test_export
      */
     function test_export_direct()
     {
         $ical = new libvcalendar();
         $events = $ical->import_from_file(__DIR__ . '/resources/multiple.ics', 'UTF-8');
         $num = count($events);
 
         ob_start();
         $return = $ical->export($events, null, true);
         $output = ob_get_contents();
         ob_end_clean();
 
         $this->assertTrue($return, "Return true on successful writing");
         $this->assertContains('BEGIN:VCALENDAR', $output, "VCALENDAR encapsulation BEGIN");
         $this->assertContains('END:VCALENDAR',   $output, "VCALENDAR encapsulation END");
         $this->assertEquals($num, substr_count($output, 'BEGIN:VEVENT'), "VEVENT encapsulation BEGIN");
         $this->assertEquals($num, substr_count($output, 'END:VEVENT'),   "VEVENT encapsulation END");
     }
 
     function test_datetime()
     {
         $ical = new libvcalendar();
-        $localtime = $ical->datetime_prop('DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('Europe/Berlin')));
-        $localdate = $ical->datetime_prop('DTSTART', new DateTime('2013-09-01', new DateTimeZone('Europe/Berlin')), false, true);
-        $utctime   = $ical->datetime_prop('DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('UTC')));
-        $asutctime = $ical->datetime_prop('DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('Europe/Berlin')), true);
+        $cal  = new \Sabre\VObject\Component\VCalendar();
+        $localtime = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('Europe/Berlin')));
+        $localdate = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01', new DateTimeZone('Europe/Berlin')), false, true);
+        $utctime   = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('UTC')));
+        $asutctime = $ical->datetime_prop($cal, 'DTSTART', new DateTime('2013-09-01 12:00:00', new DateTimeZone('Europe/Berlin')), true);
 
         $this->assertContains('TZID=Europe/Berlin', $localtime->serialize());
         $this->assertContains('VALUE=DATE', $localdate->serialize());
         $this->assertContains('20130901T120000Z', $utctime->serialize());
         $this->assertContains('20130901T100000Z', $asutctime->serialize());
     }
 
     function test_get_vtimezone()
     {
         $vtz = libvcalendar::get_vtimezone('Europe/Berlin', strtotime('2014-08-22T15:00:00+02:00'));
         $this->assertInstanceOf('\Sabre\VObject\Component', $vtz, "VTIMEZONE is a Component object");
         $this->assertEquals('Europe/Berlin', $vtz->TZID);
         $this->assertEquals('4', $vtz->{'X-MICROSOFT-CDO-TZID'});
 
         // check for transition to daylight saving time which is BEFORE the given date
         $dst = reset($vtz->select('DAYLIGHT'));
         $this->assertEquals('DAYLIGHT', $dst->name);
         $this->assertEquals('20140330T010000', $dst->DTSTART);
         $this->assertEquals('+0100', $dst->TZOFFSETFROM);
         $this->assertEquals('+0200', $dst->TZOFFSETTO);
         $this->assertEquals('CEST', $dst->TZNAME);
 
         // check (last) transition to standard time which is AFTER the given date
         $std = end($vtz->select('STANDARD'));
         $this->assertEquals('STANDARD', $std->name);
         $this->assertEquals('20141026T010000', $std->DTSTART);
         $this->assertEquals('+0200', $std->TZOFFSETFROM);
         $this->assertEquals('+0100', $std->TZOFFSETTO);
         $this->assertEquals('CET', $std->TZNAME);
 
         // unknown timezone
         $vtz = libvcalendar::get_vtimezone('America/Foo Bar');
         $this->assertEquals(false, $vtz);
 
         // invalid input data
         $vtz = libvcalendar::get_vtimezone(new DateTime());
         $this->assertEquals(false, $vtz);
 
         // DateTimezone as input data
         $vtz = libvcalendar::get_vtimezone(new DateTimezone('Pacific/Chatham'));
         $this->assertInstanceOf('\Sabre\VObject\Component', $vtz);
         $this->assertContains('TZOFFSETFROM:+1245', $vtz->serialize());
         $this->assertContains('TZOFFSETTO:+1345', $vtz->serialize());
     }
 
     function get_attachment_data($id, $event)
     {
         return $this->attachment_data;
     }
 }
 
diff --git a/plugins/libcalendaring/tests/resources/alarms.ics b/plugins/libcalendaring/tests/resources/alarms.ics
index d6f9d547..9d588543 100644
--- a/plugins/libcalendaring/tests/resources/alarms.ics
+++ b/plugins/libcalendaring/tests/resources/alarms.ics
@@ -1,51 +1,56 @@
 BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//Apple Inc.//iCal 5.0.3//EN
 CALSCALE:GREGORIAN
 BEGIN:VTIMEZONE
 TZID:Europe/Zurich
 BEGIN:DAYLIGHT
 TZOFFSETFROM:+0100
 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
 DTSTART:19810329T020000
 TZNAME:CEST
 TZOFFSETTO:+0200
 END:DAYLIGHT
 BEGIN:STANDARD
 TZOFFSETFROM:+0200
 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
 DTSTART:19961027T030000
 TZNAME:CET
 TZOFFSETTO:+0100
 END:STANDARD
 END:VTIMEZONE
 
 BEGIN:VEVENT
 UID:1dq52u617gkfqrr4uo1i2uh70
 CREATED:20130924T221822Z
 DESCRIPTION:
 DTSTART:20130818T230000Z
 DTEND:20130819T010000Z
 DTSTAMP:20130824T235608Z
 LAST-MODIFIED:20130924T222118Z
 LOCATION:
 SEQUENCE:2
 STATUS:CONFIRMED
 SUMMARY:Alarms test
 TRANSP:OPAQUE
 BEGIN:VALARM
 ACTION:DISPLAY
 DESCRIPTION:This is the first event reminder
 TRIGGER:-P0DT0H30M0S
 END:VALARM
 BEGIN:VALARM
 ACTION:EMAIL
 DESCRIPTION:This is an event reminder
 TRIGGER:-P1D
 ATTENDEE:mailto:reminder-recipient@example.org
 SUMMARY:This is the reminder message
 DESCRIPTION:This is the second event reminder
 END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:An absolute reminder
+TRIGGER;VALUE=DATE-TIME:20130812T160000Z
+END:VALARM
 END:VEVENT
 
 END:VCALENDAR
diff --git a/plugins/libcalendaring/tests/resources/itip.ics b/plugins/libcalendaring/tests/resources/itip.ics
index af283e37..50eb4ee9 100644
--- a/plugins/libcalendaring/tests/resources/itip.ics
+++ b/plugins/libcalendaring/tests/resources/itip.ics
@@ -1,149 +1,151 @@
 BEGIN:VCALENDAR
 PRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN
 VERSION:2.0
 X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0
 METHOD:REQUEST
 BEGIN:VEVENT
 ORGANIZER;CN="Rolf Test":MAILTO:rolf@mykolab.com
 DTSTAMP:20130628T190056Z
 ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;
- X-UID=208889384:mailto:rolf2@mykolab.com
+ DELEGATED-FROM=carl@mykolab.com;X-UID=208889384:mailto:rolf2@mykolab.com
+ATTENDEE;RSVP=FALSE;PARTSTAT=DELEGATED;ROLE=NON-PARTICIPANT;CUTYPE=INDIVIDUAL;
+ DELEGATED-TO=rolf2@mykolab.com:mailto:carl@mykolab.com
 CREATED:20130628T190032Z
 UID:ac6b0aee-2519-4e5c-9a25-48c57064c9f0
 LAST-MODIFIED:20130628T190032Z
 SUMMARY:iTip Test
 ATTACH;VALUE=BINARY;FMTTYPE=text/html;ENCODING=BASE64;
  X-LABEL=calendar.html:
  PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgVHJhbnNpdGlvbm
  FsLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXRyYW5zaXRp
  b25hbC5kdGQiPgo8aHRtbD48aGVhZD4KICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LVR5cG
  UiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD1VVEYtOCIgLz4KICA8dGl0bGU+S2FsZW5k
  ZXI8L3RpdGxlPgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBib2R5IHsgYmFja2dyb3
  VuZC1jb2xvcjp3aGl0ZTsgY29sb3I6YmxhY2sgfQogICAgdGQgeyB0ZXh0LWFsaWduOmNlbnRl
  cjsgYmFja2dyb3VuZC1jb2xvcjojZWVlIH0KICAgIHRoIHsgdGV4dC1hbGlnbjpjZW50ZXI7IG
  JhY2tncm91bmQtY29sb3I6IzIyODsgY29sb3I6d2hpdGUgfQogICAgdGQuc3VtIHsgdGV4dC1h
  bGlnbjpsZWZ0IH0KICAgIHRkLnN1bWRvbmUgeyB0ZXh0LWFsaWduOmxlZnQ7IGJhY2tncm91bm
  QtY29sb3I6I2NjYyB9CiAgICB0ZC5kb25lIHsgYmFja2dyb3VuZC1jb2xvcjojY2NjIH0KICAg
  IHRkLnN1YmhlYWQgeyB0ZXh0LWFsaWduOmNlbnRlcjsgYmFja2dyb3VuZC1jb2xvcjojY2NmIH
  0KICAgIHRkLmRhdGVoZWFkIHsgdGV4dC1hbGlnbjpjZW50ZXI7IGJhY2tncm91bmQtY29sb3I6
  I2NjZiB9CiAgICB0ZC5zcGFjZSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGUgfQogICAgdGQuZG
  F0ZSB7IHRleHQtYWxpZ246bGVmdCB9CiAgICB0ZC5kYXRlaG9saWRheSB7IHRleHQtYWxpZ246
  bGVmdDsgY29sb3I6cmVkIH0KICA8L3N0eWxlPgo8L2hlYWQ+PGJvZHk+CjxoMT5LYWxlbmRlcj
  wvaDE+CjxoMj5KdW5pIDIwMTM8L2gyPgo8dGFibGUgYm9yZGVyPSIxIj4KICA8dHI+PHRoPk1v
  bnRhZzwvdGg+PHRoPkRpZW5zdGFnPC90aD48dGg+TWl0dHdvY2g8L3RoPjx0aD5Eb25uZXJzdG
  FnPC90aD48dGg+RnJlaXRhZzwvdGg+PHRoPlNhbXN0YWc8L3RoPjx0aD5Tb25udGFnPC90aD48
  L3RyPgogIDx0cj4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPj
  x0ZCBjbGFzcz0iZGF0ZSI+Mjc8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+PC90ZD48
  L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj0iMC
  I+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+Mjg8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+
  PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcm
  Rlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+Mjk8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249
  InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYm
  xlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MzA8L3RkPjwvdHI+PHRyPjx0ZCB2
  YWxpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcC
  I+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MzE8L3RkPjwvdHI+PHRy
  Pjx0ZCB2YWxpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ2
  49InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MTwvdGQ+PC90
  cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIH
  ZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlaG9saWRh
  eSI+MjwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3
  RkPgogIDwvdHI+CiAgPHRyPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIw
  Ij48dHI+PHRkIGNsYXNzPSJkYXRlIj4zPC90ZD48L3RyPjx0cj48dGQgdmFsaWduPSJ0b3AiPj
  wvdGQ+PC90cj48L3RhYmxlPjwvdGQ+CiAgICA8dGQgdmFsaWduPSJ0b3AiPjx0YWJsZSBib3Jk
  ZXI9IjAiPjx0cj48dGQgY2xhc3M9ImRhdGUiPjQ8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249In
  RvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxl
  IGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+NTwvdGQ+PC90cj48dHI+PHRkIHZhbG
  lnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48
  dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj42PC90ZD48L3RyPjx0cj48dG
  QgdmFsaWduPSJ0b3AiPjwvdGQ+PC90cj48L3RhYmxlPjwvdGQ+CiAgICA8dGQgdmFsaWduPSJ0
  b3AiPjx0YWJsZSBib3JkZXI9IjAiPjx0cj48dGQgY2xhc3M9ImRhdGUiPjc8L3RkPjwvdHI+PH
  RyPjx0ZCB2YWxpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxp
  Z249InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+ODwvdGQ+PC
  90cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRk
  IHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlaG9saW
  RheSI+OTwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48
  L3RkPgogIDwvdHI+CiAgPHRyPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPS
  IwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4xMDwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9w
  Ij48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm
  9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4xMTwvdGQ+PC90cj48dHI+PHRkIHZhbGln
  bj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48dG
  FibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4xMjwvdGQ+PC90cj48dHI+PHRk
  IHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG
  9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4xMzwvdGQ+PC90cj48
  dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbG
  lnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4xNDwvdGQ+
  PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPH
  RkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4x
  NTwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj48L3RkPjwvdHI+PC90YWJsZT48L3RkPg
  ogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJk
  YXRlaG9saWRheSI+MTY8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+PC90ZD48L3RyPj
  wvdGFibGU+PC90ZD4KICA8L3RyPgogIDx0cj4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxl
  IGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MTc8L3RkPjwvdHI+PHRyPjx0ZCB2YW
  xpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+
  PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MTg8L3RkPjwvdHI+PHRyPj
  x0ZCB2YWxpZ249InRvcCI+PHRhYmxlPiAgPHRyPgogICAgPHRkPiZuYnNwOzwvdGQ+PHRkPiZu
  YnNwOzwvdGQ+CiAgICA8dGQgY2xhc3M9InN1bSI+CiAgICAgIDxiPnRlcm1pbmJldHJlZmYxPC
  9iPgogICAgPC90ZD4KICA8dGQ+CiAgICAmbmJzcDsKICA8L3RkPgogIDx0ZD4KICAgICZuYnNw
  OwogIDwvdGQ+CiAgPC90cj4KPC90YWJsZT48L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPH
  RkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4x
  OTwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj4mbmJzcDs8L3RkPjwvdHI+PC90YWJsZT
  48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNs
  YXNzPSJkYXRlIj4yMDwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj4mbmJzcDs8L3RkPj
  wvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFibGUgYm9yZGVyPSIw
  Ij48dHI+PHRkIGNsYXNzPSJkYXRlIj4yMTwvdGQ+PC90cj48dHI+PHRkIHZhbGlnbj0idG9wIj
  4mbmJzcDs8L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGlnbj0idG9wIj48dGFi
  bGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlIj4yMjwvdGQ+PC90cj48dHI+PHRkIH
  ZhbGlnbj0idG9wIj4mbmJzcDs8L3RkPjwvdHI+PC90YWJsZT48L3RkPgogICAgPHRkIHZhbGln
  bj0idG9wIj48dGFibGUgYm9yZGVyPSIwIj48dHI+PHRkIGNsYXNzPSJkYXRlaG9saWRheSI+Mj
  M8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+Jm5ic3A7PC90ZD48L3RyPjwvdGFibGU+
  PC90ZD4KICA8L3RyPgogIDx0cj4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj
  0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+MjQ8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRv
  cCI+PHRhYmxlPiAgPHRyPgogICAgPHRkIHZhbGlnbj0idG9wIj4xNTowMDwvdGQ+CiAgICA8dG
  QgdmFsaWduPSJ0b3AiPjAwOjMwPC90ZD4KICAgIDx0ZCBjbGFzcz0ic3VtIj4KICAgICAgPGI+
  dGVzdDwvYj4KICAgIDwvdGQ+CiAgPHRkPgogICAgJm5ic3A7CiAgPC90ZD4KICA8dGQ+CiAgIC
  AmbmJzcDsKICA8L3RkPgogIDwvdHI+CjwvdGFibGU+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4K
  ICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZG
  F0ZSI+MjU8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+Jm5ic3A7PC90ZD48L3RyPjwv
  dGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPj
  x0ZCBjbGFzcz0iZGF0ZSI+MjY8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+Jm5ic3A7
  PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcCI+PHRhYmxlIGJvcm
  Rlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+Mjc8L3RkPjwvdHI+PHRyPjx0ZCB2YWxpZ249
  InRvcCI+Jm5ic3A7PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ249InRvcC
  I+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+Mjg8L3RkPjwvdHI+PHRy
  Pjx0ZCB2YWxpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZCB2YWxpZ2
  49InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZSI+Mjk8L3RkPjwv
  dHI+PHRyPjx0ZCB2YWxpZ249InRvcCI+PC90ZD48L3RyPjwvdGFibGU+PC90ZD4KICAgIDx0ZC
  B2YWxpZ249InRvcCI+PHRhYmxlIGJvcmRlcj0iMCI+PHRyPjx0ZCBjbGFzcz0iZGF0ZWhvbGlk
  YXkiPjMwPC90ZD48L3RyPjx0cj48dGQgdmFsaWduPSJ0b3AiPjwvdGQ+PC90cj48L3RhYmxlPj
  wvdGQ+CiAgPC90cj4KPC90YWJsZT4KPGgxPkF1ZmdhYmVubGlzdGU8L2gxPgo8dGFibGUgYm9y
  ZGVyPSIwIiBjZWxscGFkZGluZz0iMyIgY2VsbHNwYWNpbmc9IjMiPgogIDx0cj4KICAgIDx0aC
  BjbGFzcz0ic3VtIj5BdWZnYWJlPC90aD4KICAgIDx0aD5Qcmlvcml0w6R0PC90aD4KICAgIDx0
  aD5BYmdlc2NobG9zc2VuPC90aD4KICAgIDx0aD5Gw6RsbGlna2VpdHNkYXR1bTwvdGg+CiAgIC
  A8dGg+T3J0PC90aD4KICAgIDx0aD5LYXRlZ29yaWVuPC90aD4KICA8L3RyPgo8dHI+CiAgPHRk
  IGNsYXNzPSJzdW0iPgogICAgPGEgbmFtZT0iYzJmZjM1NGQtMjhlYi00YjBmLWIwYWItZDQxMT
  g1Y2JlNGM3Ij48L2E+CiAgICA8Yj5UZXN0YXVmZ2FiZTE8L2I+CiAgICA8cD5BdWZnYWJlbmlu
  aGFsdDE8L3A+CiAgICA8ZGl2IGFsaWduPSJyaWdodCI+PGEgaHJlZj0iI3N1YmMyZmYzNTRkLT
  I4ZWItNGIwZi1iMGFiLWQ0MTE4NWNiZTRjNyI+VGVpbGF1ZmdhYmVuPC9hPjwvZGl2PgogIDwv
  dGQ+CiAgPHRkPgogICAgMAogIDwvdGQ+CiAgPHRkPgogICAgMCAlCiAgPC90ZD4KICA8dGQ+Ci
  AgICAyNS4wNi4yMDEzCiAgPC90ZD4KICA8dGQ+CiAgICAmbmJzcDsKICA8L3RkPgogIDx0ZD4K
  ICAgICZuYnNwOwogIDwvdGQ+CjwvdHI+Cjx0cj4KICA8dGQgY2xhc3M9InN1bSI+CiAgICA8YS
  BuYW1lPSI5MTc4NjRkNi1lN2ZiLTQwYTEtODEzOS05ODU3MzM0MDI3ZGYiPjwvYT4KICAgIDxi
  PlRlc3RhdWZnYWJlNDg8L2I+CiAgPC90ZD4KICA8dGQ+CiAgICAwCiAgPC90ZD4KICA8dGQ+Ci
  AgICAwICUKICA8L3RkPgogIDx0ZD4KICAgIDI2LjA2LjIwMTMKICA8L3RkPgogIDx0ZD4KICAg
  ICZuYnNwOwogIDwvdGQ+CiAgPHRkPgogICAgdGVzdDQ4CiAgPC90ZD4KPC90cj4KICA8dHI+Ci
  AgICA8dGQgY2xhc3M9InN1YmhlYWQiIGNvbHNwYW49IjYiPjxhIG5hbWU9InN1YmMyZmYzNTRk
  LTI4ZWItNGIwZi1iMGFiLWQ0MTE4NWNiZTRjNyI+PC9hPlRlaWxhdWZnYWJlbiB2b246IDxhIG
  hyZWY9IiNjMmZmMzU0ZC0yOGViLTRiMGYtYjBhYi1kNDExODVjYmU0YzciPjxiPlRlc3RhdWZn
  YWJlMTwvYj48L2E+PC90ZD4KICA8L3RyPgo8dHI+CiAgPHRkIGNsYXNzPSJzdW0iPgogICAgPG
  EgbmFtZT0iYWFlNzA2NDItNTRlMy00MTdhLTg1OGEtZmFiYjcwZTBhYmZiIj48L2E+CiAgICA8
  Yj5VbnRlcmF1ZmdhYmU8L2I+CiAgPC90ZD4KICA8dGQ+CiAgICAwCiAgPC90ZD4KICA8dGQ+Ci
  AgICAwICUKICA8L3RkPgogIDx0ZD4KICAgIDI1LjA2LjIwMTMKICA8L3RkPgogIDx0ZD4KICAg
  ICZuYnNwOwogIDwvdGQ+CiAgPHRkPgogICAgJm5ic3A7CiAgPC90ZD4KPC90cj4KPC90YWJsZT
  4KPHA+RGllc2UgU2VpdGUgd3VyZGUgZXJzdGVsbHQgdm9uIFJvbGYgVGVzdCAobWFpbHRvOnJv
  bGZAbXlrb2xhYi5jb20pIG1pdCBLT3JnYW5pemVyIChodHRwOi8va29yZ2FuaXplci5rZGUub3
  JnKTwvcD4KPC9ib2R5PjwvaHRtbD4K
 DTSTART:20130703T183000Z
 DTEND:20130703T203000Z
 TRANSP:OPAQUE
 END:VEVENT
 END:VCALENDAR
diff --git a/plugins/libcalendaring/tests/resources/recurring.ics b/plugins/libcalendaring/tests/resources/recurring.ics
index 92db9e28..85860be2 100644
--- a/plugins/libcalendaring/tests/resources/recurring.ics
+++ b/plugins/libcalendaring/tests/resources/recurring.ics
@@ -1,43 +1,51 @@
 BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//Apple Inc.//iCal 5.0.3//EN
 CALSCALE:GREGORIAN
 BEGIN:VTIMEZONE
 TZID:Europe/Zurich
 BEGIN:DAYLIGHT
 TZOFFSETFROM:+0100
 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
 DTSTART:19810329T020000
 TZNAME:CEST
 TZOFFSETTO:+0200
 END:DAYLIGHT
 BEGIN:STANDARD
 TZOFFSETFROM:+0200
 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
 DTSTART:19961027T030000
 TZNAME:CET
 TZOFFSETTO:+0100
 END:STANDARD
 END:VTIMEZONE
 BEGIN:VEVENT
 PRIORITY:3
 DTEND;TZID=Europe/Zurich:20130717T130000
 TRANSP:TRANSPARENT
 UID:7e93e8e8eef16f28aa33b78cd73613eb
 DTSTAMP:20130718T082032Z
 SEQUENCE:6
 CLASS:CONFIDENTIAL
 CATEGORIES:libcalendaring tests
 SUMMARY:Recurring Test
 LAST-MODIFIED:20120621
 DTSTART;TZID=Europe/Zurich:20130717T080000
 CREATED:20081223T232600Z
 RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20140718T215959Z;BYDAY=3WE
 EXDATE;TZID=Europe/Zurich:20131218T080000
 EXDATE;TZID=Europe/Zurich:20140415T080000
 BEGIN:VALARM
 TRIGGER:-PT12H
 ACTION:DISPLAY
 END:VALARM
 END:VEVENT
+BEGIN:VEVENT
+DTSTART;TZID="Europe/Zurich":20140521T100000
+DTEND;TZID="Europe/Zurich":20140521T150000
+RECURRENCE-ID:20140521T080000Z
+UID:7e93e8e8eef16f28aa33b78cd73613eb
+DTSTAMP:20130718T082032Z
+SUMMARY:Recurring Test (Exception)
+END:VEVENT
 END:VCALENDAR
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index bc38adc2..e0a2d01f 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -1,1580 +1,1582 @@
 <?php
 
 /**
  * Kolab storage class providing static methods to access groupware objects on a Kolab server.
  *
  * @version @package_version@
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  * @author Aleksander Machniak <machniak@kolabsys.com>
  *
  * Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 class kolab_storage
 {
     const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
     const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type';
     const COLOR_KEY_SHARED  = '/shared/vendor/kolab/color';
     const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color';
     const NAME_KEY_SHARED   = '/shared/vendor/kolab/displayname';
     const NAME_KEY_PRIVATE  = '/private/vendor/kolab/displayname';
     const UID_KEY_SHARED    = '/shared/vendor/kolab/uniqueid';
     const UID_KEY_PRIVATE   = '/private/vendor/kolab/uniqueid';
     const UID_KEY_CYRUS     = '/shared/vendor/cmu/cyrus-imapd/uniqueid';
 
     const ERROR_IMAP_CONN      = 1;
     const ERROR_CACHE_DB       = 2;
     const ERROR_NO_PERMISSION  = 3;
     const ERROR_INVALID_FOLDER = 4;
 
     public static $version = '3.0';
     public static $last_error;
     public static $encode_ids = false;
 
     private static $ready = false;
     private static $with_tempsubs = true;
     private static $subscriptions;
     private static $typedata = array();
     private static $states;
     private static $config;
     private static $imap;
     private static $ldap;
 
     // Default folder names
     private static $default_folders = array(
         'event'         => 'Calendar',
         'contact'       => 'Contacts',
         'task'          => 'Tasks',
         'note'          => 'Notes',
         'file'          => 'Files',
         'configuration' => 'Configuration',
         'journal'       => 'Journal',
         'mail.inbox'       => 'INBOX',
         'mail.drafts'      => 'Drafts',
         'mail.sentitems'   => 'Sent',
         'mail.wastebasket' => 'Trash',
         'mail.outbox'      => 'Outbox',
         'mail.junkemail'   => 'Junk',
     );
 
 
     /**
      * Setup the environment needed by the libs
      */
     public static function setup()
     {
         if (self::$ready)
             return true;
 
         $rcmail = rcube::get_instance();
         self::$config = $rcmail->config;
         self::$version = strval($rcmail->config->get('kolab_format_version', self::$version));
         self::$imap = $rcmail->get_storage();
         self::$ready = class_exists('kolabformat') &&
             (self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
 
         if (self::$ready) {
             // set imap options
             self::$imap->set_options(array(
                 'skip_deleted' => true,
                 'threading' => false,
             ));
         }
         else if (!class_exists('kolabformat')) {
             rcube::raise_error(array(
                 'code' => 900, 'type' => 'php',
                 'message' => "required kolabformat module not found"
             ), true);
         }
         else {
             rcube::raise_error(array(
                 'code' => 900, 'type' => 'php',
                 'message' => "IMAP server doesn't support METADATA or ANNOTATEMORE"
             ), true);
         }
 
         // adjust some configurable settings
         if ($event_scheduling_prop = $rcmail->config->get('kolab_event_scheduling_properties', null)) {
             kolab_format_event::$scheduling_properties = (array)$event_scheduling_prop;
         }
         // adjust some configurable settings
         if ($task_scheduling_prop = $rcmail->config->get('kolab_task_scheduling_properties', null)) {
             kolab_format_task::$scheduling_properties = (array)$task_scheduling_prop;
         }
 
         return self::$ready;
     }
 
     /**
      * Initializes LDAP object to resolve Kolab users
      */
     public static function ldap()
     {
         if (self::$ldap) {
             return self::$ldap;
         }
 
         self::setup();
 
         $config = self::$config->get('kolab_users_directory', self::$config->get('kolab_auth_addressbook'));
 
         if (!is_array($config)) {
             $ldap_config = (array)self::$config->get('ldap_public');
             $config = $ldap_config[$config];
         }
 
         if (empty($config)) {
             return null;
         }
 
         // overwrite filter option
         if ($filter = self::$config->get('kolab_users_filter')) {
             self::$config->set('kolab_auth_filter', $filter);
         }
 
         // re-use the LDAP wrapper class from kolab_auth plugin
         require_once rtrim(RCUBE_PLUGINS_DIR, '/') . '/kolab_auth/kolab_auth_ldap.php';
 
         self::$ldap = new kolab_auth_ldap($config);
 
         return self::$ldap;
     }
 
     /**
      * Get a list of storage folders for the given data type
      *
      * @param string Data type to list folders for (contact,distribution-list,event,task,note)
      * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
      *
      * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP)
      */
     public static function get_folders($type, $subscribed = null)
     {
         $folders = $folderdata = array();
 
         if (self::setup()) {
             foreach ((array)self::list_folders('', '*', $type, $subscribed, $folderdata) as $foldername) {
                 $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
             }
         }
 
         return $folders;
     }
 
     /**
      * Getter for the storage folder for the given type
      *
      * @param string Data type to list folders for (contact,distribution-list,event,task,note)
      * @return object kolab_storage_folder  The folder object
      */
     public static function get_default_folder($type)
     {
         if (self::setup()) {
             foreach ((array)self::list_folders('', '*', $type . '.default', false, $folderdata) as $foldername) {
                 return new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
             }
         }
 
         return null;
     }
 
     /**
      * Getter for a specific storage folder
      *
      * @param string IMAP folder to access (UTF7-IMAP)
      * @param string Expected folder type
      *
      * @return object kolab_storage_folder  The folder object
      */
     public static function get_folder($folder, $type = null)
     {
         return self::setup() ? new kolab_storage_folder($folder, $type) : null;
     }
 
     /**
      * Getter for a single Kolab object, identified by its UID.
      * This will search all folders storing objects of the given type.
      *
      * @param string Object UID
      * @param string Object type (contact,event,task,journal,file,note,configuration)
      * @return array The Kolab object represented as hash array or false if not found
      */
     public static function get_object($uid, $type)
     {
         self::setup();
         $folder = null;
         foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) {
             if (!$folder)
                 $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
             else
                 $folder->set_folder($foldername, $type, $folderdata[$foldername]);
 
             if ($object = $folder->get_object($uid, '*'))
                 return $object;
         }
 
         return false;
     }
 
     /**
      * Execute cross-folder searches with the given query.
      *
      * @param array  Pseudo-SQL query as list of filter parameter triplets
      * @param string Object type (contact,event,task,journal,file,note,configuration)
      * @return array List of Kolab data objects (each represented as hash array)
      * @see kolab_storage_format::select()
      */
     public static function select($query, $type)
     {
         self::setup();
         $folder = null;
         $result = array();
 
         foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) {
             if (!$folder)
                 $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
             else
                 $folder->set_folder($foldername, $type, $folderdata[$foldername]);
 
             foreach ($folder->select($query, '*') as $object) {
                 $result[] = $object;
             }
         }
 
         return $result;
     }
 
     /**
      * Returns Free-busy server URL
      */
     public static function get_freebusy_server()
     {
+        self::setup();
+
         $url = 'https://' . $_SESSION['imap_host'] . '/freebusy';
         $url = self::$config->get('kolab_freebusy_server', $url);
         $url = rcube_utils::resolve_url($url);
 
         return unslashify($url);
     }
 
     /**
      * Compose an URL to query the free/busy status for the given user
      */
     public static function get_freebusy_url($email)
     {
         return self::get_freebusy_server() . '/' . $email . '.ifb';
     }
 
     /**
      * Creates folder ID from folder name
      *
      * @param string  $folder Folder name (UTF7-IMAP)
      * @param boolean $enc    Use lossless encoding
      * @return string Folder ID string
      */
     public static function folder_id($folder, $enc = null)
     {
         return $enc == true || ($enc === null && self::$encode_ids) ?
             self::id_encode($folder) :
             asciiwords(strtr($folder, '/.-', '___'));
     }
 
     /**
      * Encode the given ID to a safe ascii representation
      *
      * @param string $id Arbitrary identifier string
      *
      * @return string Ascii representation
      */
     public static function id_encode($id)
     {
         return rtrim(strtr(base64_encode($id), '+/', '-_'), '=');
     }
 
     /**
      * Convert the given identifier back to it's raw value
      *
      * @param string $id Ascii identifier
      * @return string Raw identifier string
      */
     public static function id_decode($id)
     {
       return base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT));
     }
 
     /**
      * Return the (first) path of the requested IMAP namespace
      *
      * @param string  Namespace name (personal, shared, other)
      * @return string IMAP root path for that namespace
      */
     public static function namespace_root($name)
     {
         foreach ((array)self::$imap->get_namespace($name) as $paths) {
             if (strlen($paths[0]) > 1) {
                 return $paths[0];
             }
         }
 
         return '';
     }
 
 
     /**
      * Deletes IMAP folder
      *
      * @param string $name Folder name (UTF7-IMAP)
      *
      * @return bool True on success, false on failure
      */
     public static function folder_delete($name)
     {
         // clear cached entries first
         if ($folder = self::get_folder($name))
             $folder->cache->purge();
 
         $rcmail = rcube::get_instance();
         $plugin = $rcmail->plugins->exec_hook('folder_delete', array('name' => $name));
 
         $success = self::$imap->delete_folder($name);
         self::$last_error = self::$imap->get_error_str();
 
         return $success;
     }
 
     /**
      * Creates IMAP folder
      *
      * @param string $name       Folder name (UTF7-IMAP)
      * @param string $type       Folder type
      * @param bool   $subscribed Sets folder subscription
      * @param bool   $active     Sets folder state (client-side subscription)
      *
      * @return bool True on success, false on failure
      */
     public static function folder_create($name, $type = null, $subscribed = false, $active = false)
     {
         self::setup();
 
         $rcmail = rcube::get_instance();
         $plugin = $rcmail->plugins->exec_hook('folder_create', array('record' => array(
             'name' => $name,
             'subscribe' => $subscribed,
         )));
 
         if ($saved = self::$imap->create_folder($name, $subscribed)) {
             // set metadata for folder type
             if ($type) {
                 $saved = self::set_folder_type($name, $type);
 
                 // revert if metadata could not be set
                 if (!$saved) {
                     self::$imap->delete_folder($name);
                 }
                 // activate folder
                 else if ($active) {
                     self::set_state($name, true);
                 }
             }
         }
 
         if ($saved) {
             return true;
         }
 
         self::$last_error = self::$imap->get_error_str();
         return false;
     }
 
 
     /**
      * Renames IMAP folder
      *
      * @param string $oldname Old folder name (UTF7-IMAP)
      * @param string $newname New folder name (UTF7-IMAP)
      *
      * @return bool True on success, false on failure
      */
     public static function folder_rename($oldname, $newname)
     {
         self::setup();
 
         $rcmail = rcube::get_instance();
         $plugin = $rcmail->plugins->exec_hook('folder_rename', array(
             'oldname' => $oldname, 'newname' => $newname));
 
         $oldfolder = self::get_folder($oldname);
         $active    = self::folder_is_active($oldname);
         $success   = self::$imap->rename_folder($oldname, $newname);
         self::$last_error = self::$imap->get_error_str();
 
         // pass active state to new folder name
         if ($success && $active) {
             self::set_state($oldname, false);
             self::set_state($newname, true);
         }
 
         // assign existing cache entries to new resource uri
         if ($success && $oldfolder) {
             $oldfolder->cache->rename($newname);
         }
 
         return $success;
     }
 
 
     /**
      * Rename or Create a new IMAP folder.
      *
      * Does additional checks for permissions and folder name restrictions
      *
      * @param array Hash array with folder properties and metadata
      *  - name:       Folder name
      *  - oldname:    Old folder name when changed
      *  - parent:     Parent folder to create the new one in
      *  - type:       Folder type to create
      *  - subscribed: Subscribed flag (IMAP subscription)
      *  - active:     Activation flag (client-side subscription)
      * @return mixed New folder name or False on failure
      */
     public static function folder_update(&$prop)
     {
         self::setup();
 
         $folder    = rcube_charset::convert($prop['name'], RCUBE_CHARSET, 'UTF7-IMAP');
         $oldfolder = $prop['oldname']; // UTF7
         $parent    = $prop['parent']; // UTF7
         $delimiter = self::$imap->get_hierarchy_delimiter();
 
         if (strlen($oldfolder)) {
             $options = self::$imap->folder_info($oldfolder);
         }
 
         if (!empty($options) && ($options['norename'] || $options['protected'])) {
         }
         // sanity checks (from steps/settings/save_folder.inc)
         else if (!strlen($folder)) {
             self::$last_error = 'cannotbeempty';
             return false;
         }
         else if (strlen($folder) > 128) {
             self::$last_error = 'nametoolong';
             return false;
         }
         else {
             // these characters are problematic e.g. when used in LIST/LSUB
             foreach (array($delimiter, '%', '*') as $char) {
                 if (strpos($folder, $char) !== false) {
                     self::$last_error = 'forbiddencharacter';
                     return false;
                 }
             }
         }
 
         if (!empty($options) && ($options['protected'] || $options['norename'])) {
             $folder = $oldfolder;
         }
         else if (strlen($parent)) {
             $folder = $parent . $delimiter . $folder;
         }
         else {
             // add namespace prefix (when needed)
             $folder = self::$imap->mod_folder($folder, 'in');
         }
 
         // Check access rights to the parent folder
         if (strlen($parent) && (!strlen($oldfolder) || $oldfolder != $folder)) {
             $parent_opts = self::$imap->folder_info($parent);
             if ($parent_opts['namespace'] != 'personal'
                 && (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights'])))
             ) {
                 self::$last_error = 'No permission to create folder';
                 return false;
           }
         }
 
         // update the folder name
         if (strlen($oldfolder)) {
             if ($oldfolder != $folder) {
                 $result = self::folder_rename($oldfolder, $folder);
           }
           else
               $result = true;
         }
         // create new folder
         else {
             $result = self::folder_create($folder, $prop['type'], $prop['subscribed'], $prop['active']);
         }
 
         if ($result) {
             self::set_folder_props($folder, $prop);
         }
 
         return $result ? $folder : false;
     }
 
 
     /**
      * Getter for human-readable name of Kolab object (folder)
      * See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference
      *
      * @param string $folder    IMAP folder name (UTF7-IMAP)
      * @param string $folder_ns Will be set to namespace name of the folder
      *
      * @return string Name of the folder-object
      */
     public static function object_name($folder, &$folder_ns=null)
     {
         self::setup();
 
         // find custom display name in folder METADATA
         if ($name = self::custom_displayname($folder)) {
             return $name;
         }
 
         $found     = false;
         $namespace = self::$imap->get_namespace();
 
         if (!empty($namespace['shared'])) {
             foreach ($namespace['shared'] as $ns) {
                 if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
                     $prefix = '';
                     $folder = substr($folder, strlen($ns[0]));
                     $delim  = $ns[1];
                     $found  = true;
                     $folder_ns = 'shared';
                     break;
                 }
             }
         }
         if (!$found && !empty($namespace['other'])) {
             foreach ($namespace['other'] as $ns) {
                 if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
                     // remove namespace prefix
                     $folder = substr($folder, strlen($ns[0]));
                     $delim  = $ns[1];
                     // get username
                     $pos    = strpos($folder, $delim);
                     if ($pos) {
                         $prefix = '('.substr($folder, 0, $pos).')';
                         $folder = substr($folder, $pos+1);
                     }
                     else {
                         $prefix = '('.$folder.')';
                         $folder = '';
                     }
 
                     $found  = true;
                     $folder_ns = 'other';
                     break;
                 }
             }
         }
         if (!$found && !empty($namespace['personal'])) {
             foreach ($namespace['personal'] as $ns) {
                 if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
                     // remove namespace prefix
                     $folder = substr($folder, strlen($ns[0]));
                     $prefix = '';
                     $delim  = $ns[1];
                     $found  = true;
                     break;
                 }
             }
         }
 
         if (empty($delim))
             $delim = self::$imap->get_hierarchy_delimiter();
 
         $folder = rcube_charset::convert($folder, 'UTF7-IMAP');
         $folder = html::quote($folder);
         $folder = str_replace(html::quote($delim), ' &raquo; ', $folder);
 
         if ($prefix)
             $folder = html::quote($prefix) . ($folder !== '' ? ' ' . $folder : '');
 
         if (!$folder_ns)
             $folder_ns = 'personal';
 
         return $folder;
     }
 
     /**
      * Get custom display name (saved in metadata) for the given folder
      */
     public static function custom_displayname($folder)
     {
       // find custom display name in folder METADATA
       if (self::$config->get('kolab_custom_display_names', true)) {
           $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED));
           if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) {
               return $name;
           }
       }
 
       return false;
     }
 
     /**
      * Helper method to generate a truncated folder name to display.
      * Note: $origname is a string returned by self::object_name()
      */
     public static function folder_displayname($origname, &$names)
     {
         $name = $origname;
 
         // find folder prefix to truncate
         for ($i = count($names)-1; $i >= 0; $i--) {
             if (strpos($name, $names[$i] . ' &raquo; ') === 0) {
                 $length = strlen($names[$i] . ' &raquo; ');
                 $prefix = substr($name, 0, $length);
                 $count  = count(explode(' &raquo; ', $prefix));
                 $diff   = 1;
 
                 // check if prefix folder is in other users namespace
                 for ($n = count($names)-1; $n >= 0; $n--) {
                     if (strpos($prefix, '(' . $names[$n] . ') ') === 0) {
                         $diff = 0;
                         break;
                     }
                 }
 
                 $name = str_repeat('&nbsp;&nbsp;&nbsp;', $count - $diff) . '&raquo; ' . substr($name, $length);
                 break;
             }
             // other users namespace and parent folder exists
             else if (strpos($name, '(' . $names[$i] . ') ') === 0) {
                 $length = strlen('(' . $names[$i] . ') ');
                 $prefix = substr($name, 0, $length);
                 $count  = count(explode(' &raquo; ', $prefix));
                 $name   = str_repeat('&nbsp;&nbsp;&nbsp;', $count) . '&raquo; ' . substr($name, $length);
                 break;
             }
         }
 
         $names[] = $origname;
 
         return $name;
     }
 
 
     /**
      * Creates a SELECT field with folders list
      *
      * @param string $type    Folder type
      * @param array  $attrs   SELECT field attributes (e.g. name)
      * @param string $current The name of current folder (to skip it)
      *
      * @return html_select SELECT object
      */
     public static function folder_selector($type, $attrs, $current = '')
     {
         // get all folders of specified type (sorted)
         $folders = self::get_folders($type, true);
 
         $delim = self::$imap->get_hierarchy_delimiter();
         $names = array();
         $len   = strlen($current);
 
         if ($len && ($rpos = strrpos($current, $delim))) {
             $parent = substr($current, 0, $rpos);
             $p_len  = strlen($parent);
         }
 
         // Filter folders list
         foreach ($folders as $c_folder) {
             $name = $c_folder->name;
 
             // skip current folder and it's subfolders
             if ($len) {
                 if ($name == $current) {
                     // Make sure parent folder is listed (might be skipped e.g. if it's namespace root)
                     if ($p_len && !isset($names[$parent])) {
                         $names[$parent] = self::object_name($parent);
                     }
                     continue;
                 }
                 if (strpos($name, $current.$delim) === 0) {
                     continue;
                 }
             }
 
             // always show the parent of current folder
             if ($p_len && $name == $parent) {
             }
             // skip folders where user have no rights to create subfolders
             else if ($c_folder->get_owner() != $_SESSION['username']) {
                 $rights = $c_folder->get_myrights();
                 if (!preg_match('/[ck]/', $rights)) {
                     continue;
                 }
             }
 
             $names[$name] = self::object_name($name);
         }
 
         // Build SELECT field of parent folder
         $attrs['is_escaped'] = true;
         $select = new html_select($attrs);
         $select->add('---', '');
 
         $listnames = array();
         foreach (array_keys($names) as $imap_name) {
             $name = $origname = $names[$imap_name];
 
             // find folder prefix to truncate
             for ($i = count($listnames)-1; $i >= 0; $i--) {
                 if (strpos($name, $listnames[$i].' &raquo; ') === 0) {
                     $length = strlen($listnames[$i].' &raquo; ');
                     $prefix = substr($name, 0, $length);
                     $count  = count(explode(' &raquo; ', $prefix));
                     $name   = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
                     break;
                 }
             }
 
             $listnames[] = $origname;
             $select->add($name, $imap_name);
         }
 
         return $select;
     }
 
 
     /**
      * Returns a list of folder names
      *
      * @param string  Optional root folder
      * @param string  Optional name pattern
      * @param string  Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
      * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
      * @param array   Will be filled with folder-types data
      *
      * @return array List of folders
      */
     public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = null, &$folderdata = array())
     {
         if (!self::setup()) {
             return null;
         }
 
         // use IMAP subscriptions
         if ($subscribed === null && self::$config->get('kolab_use_subscriptions')) {
             $subscribed = true;
         }
 
         if (!$filter) {
             // Get ALL folders list, standard way
             if ($subscribed) {
                 $folders = self::$imap->list_folders_subscribed($root, $mbox);
                 // add temporarily subscribed folders
                 if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) {
                     $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders']));
                 }
             }
             else {
                 $folders = self::_imap_list_folders($root, $mbox);
             }
 
             return $folders;
         }
         $prefix = $root . $mbox;
         $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
 
         // get folders types for all folders
         if (!$subscribed || $prefix == '*' || !self::$config->get('kolab_skip_namespace')) {
             $folderdata = self::folders_typedata($prefix);
         }
         else {
             // fetch folder types for the effective list of (subscribed) folders when post-filtering
             $folderdata = array();
         }
 
         if (!is_array($folderdata)) {
             return array();
         }
 
         // In some conditions we can skip LIST command (?)
         if (!$subscribed && $filter != 'mail' && $prefix == '*') {
             foreach ($folderdata as $folder => $type) {
                 if (!preg_match($regexp, $type)) {
                     unset($folderdata[$folder]);
                 }
             }
 
             return self::$imap->sort_folder_list(array_keys($folderdata), true);
         }
 
         // Get folders list
         if ($subscribed) {
             $folders = self::$imap->list_folders_subscribed($root, $mbox);
 
             // add temporarily subscribed folders
             if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) {
                 $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders']));
             }
         }
         else {
             $folders = self::_imap_list_folders($root, $mbox);
         }
 
         // In case of an error, return empty list (?)
         if (!is_array($folders)) {
             return array();
         }
 
         // Filter folders list
         foreach ($folders as $idx => $folder) {
             // lookup folder type
             if (!array_key_exists($folder, $folderdata)) {
                 $folderdata[$folder] = self::folder_type($folder);
             }
 
             $type = $folderdata[$folder];
 
             if ($filter == 'mail' && empty($type)) {
                 continue;
             }
             if (empty($type) || !preg_match($regexp, $type)) {
                 unset($folders[$idx]);
             }
         }
 
         return $folders;
     }
 
     /**
      * Wrapper for rcube_imap::list_folders() with optional post-filtering
      */
     protected static function _imap_list_folders($root, $mbox)
     {
         $postfilter = null;
 
         // compose a post-filter expression for the excluded namespaces
         if ($root . $mbox == '*' && ($skip_ns = self::$config->get('kolab_skip_namespace'))) {
             $excludes = array();
             foreach ((array)$skip_ns as $ns) {
                 if ($ns_root = self::namespace_root($ns)) {
                     $excludes[] = $ns_root;
                 }
             }
 
             if (count($excludes)) {
                 $postfilter = '!^(' . join(')|(', array_map('preg_quote', $excludes)) . ')!';
             }
         }
 
         // use normal LIST command to return all folders, it's fast enough
         $folders = self::$imap->list_folders($root, $mbox, null, null, !empty($postfilter));
 
         if (!empty($postfilter)) {
             $folders = array_filter($folders, function($folder) use ($postfilter) { return !preg_match($postfilter, $folder); });
             $folders = self::$imap->sort_folder_list($folders);
         }
 
         return $folders;
     }
 
 
     /**
      * Search for shared or otherwise not listed groupware folders the user has access
      *
      * @param string Folder type of folders to search for
      * @param string Search string
      * @param array  Namespace(s) to exclude results from
      *
      * @return array List of matching kolab_storage_folder objects
      */
     public static function search_folders($type, $query, $exclude_ns = array())
     {
         if (!self::setup()) {
             return array();
         }
 
         $folders = array();
         $query = str_replace('*', '', $query);
 
         // find unsubscribed IMAP folders of the given type
         foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) {
             // FIXME: only consider the last part of the folder path for searching?
             $realname = strtolower(rcube_charset::convert($foldername, 'UTF7-IMAP'));
             if (($query == '' || strpos($realname, $query) !== false) &&
                 !self::folder_is_subscribed($foldername, true) &&
                 !in_array(self::$imap->folder_namespace($foldername), (array)$exclude_ns)
               ) {
                 $folders[] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
             }
         }
 
         return $folders;
     }
 
 
     /**
      * Sort the given list of kolab folders by namespace/name
      *
      * @param array List of kolab_storage_folder objects
      * @return array Sorted list of folders
      */
     public static function sort_folders($folders)
     {
         $pad     = '  ';
         $out     = array();
         $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array());
 
         foreach ($folders as $folder) {
             $folders[$folder->name] = $folder;
             $ns = $folder->get_namespace();
             $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET)) . $pad;  // decode &raquo;
         }
 
         // $folders is a result of get_folders() we can assume folders were already sorted
         foreach (array_keys($nsnames) as $ns) {
             asort($nsnames[$ns], SORT_LOCALE_STRING);
             foreach (array_keys($nsnames[$ns]) as $utf7name) {
                 $out[] = $folders[$utf7name];
             }
         }
 
         return $out;
     }
 
 
     /**
      * Check the folder tree and add the missing parents as virtual folders
      *
      * @param array $folders Folders list
      * @param object $tree   Reference to the root node of the folder tree
      *
      * @return array Flat folders list
      */
     public static function folder_hierarchy($folders, &$tree = null)
     {
         $_folders = array();
         $delim    = self::$imap->get_hierarchy_delimiter();
         $other_ns = rtrim(self::namespace_root('other'), $delim);
         $tree     = new kolab_storage_folder_virtual('', '<root>', '');  // create tree root
         $refs     = array('' => $tree);
 
         foreach ($folders as $idx => $folder) {
             $path = explode($delim, $folder->name);
             array_pop($path);
             $folder->parent = join($delim, $path);
             $folder->children = array();  // reset list
 
             // skip top folders or ones with a custom displayname
             if (count($path) < 1 || kolab_storage::custom_displayname($folder->name)) {
                 $tree->children[] = $folder;
             }
             else {
                 $parents = array();
                 $depth = $folder->get_namespace() == 'personal' ? 1 : 2;
 
                 while (count($path) >= $depth && ($parent = join($delim, $path))) {
                     array_pop($path);
                     $parent_parent = join($delim, $path);
                     if (!$refs[$parent]) {
                         if ($folder->type && self::folder_type($parent) == $folder->type) {
                             $refs[$parent] = new kolab_storage_folder($parent, $folder->type, $folder->type);
                             $refs[$parent]->parent = $parent_parent;
                         }
                         else if ($parent_parent == $other_ns) {
                             $refs[$parent] = new kolab_storage_folder_user($parent, $parent_parent);
                         }
                         else {
                             $name = kolab_storage::object_name($parent, $folder->get_namespace());
                             $refs[$parent] = new kolab_storage_folder_virtual($parent, $name, $folder->get_namespace(), $parent_parent);
                         }
                         $parents[] = $refs[$parent];
                     }
                 }
 
                 if (!empty($parents)) {
                     $parents = array_reverse($parents);
                     foreach ($parents as $parent) {
                         $parent_node = $refs[$parent->parent] ?: $tree;
                         $parent_node->children[] = $parent;
                         $_folders[] = $parent;
                     }
                 }
 
                 $parent_node = $refs[$folder->parent] ?: $tree;
                 $parent_node->children[] = $folder;
             }
 
             $refs[$folder->name] = $folder;
             $_folders[] = $folder;
             unset($folders[$idx]);
         }
 
         return $_folders;
     }
 
 
     /**
      * Returns folder types indexed by folder name
      *
      * @param string $prefix Folder prefix (Default '*' for all folders)
      *
      * @return array|bool List of folders, False on failure
      */
     public static function folders_typedata($prefix = '*')
     {
         if (!self::setup()) {
             return false;
         }
 
         // return cached result
         if (is_array(self::$typedata[$prefix])) {
             return self::$typedata[$prefix];
         }
 
         $type_keys = array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE);
 
         // fetch metadata from *some* folders only
         if (($prefix == '*' || $prefix == '') && ($skip_ns = self::$config->get('kolab_skip_namespace'))) {
             $delimiter = self::$imap->get_hierarchy_delimiter();
             $folderdata = $blacklist = array();
             foreach ((array)$skip_ns as $ns) {
                 if ($ns_root = rtrim(self::namespace_root($ns), $delimiter)) {
                     $blacklist[] = $ns_root;
                 }
             }
             foreach (array('personal','other','shared') as $ns) {
                 if (!in_array($ns, (array)$skip_ns)) {
                     $ns_root = rtrim(self::namespace_root($ns), $delimiter);
 
                     // list top-level folders and their childs one by one
                     // GETMETADATA "%" doesn't list shared or other namespace folders but "*" would
                     if ($ns_root == '') {
                         foreach ((array)self::$imap->get_metadata('%', $type_keys) as $folder => $metadata) {
                             if (!in_array($folder, $blacklist)) {
                                 $folderdata[$folder] = $metadata;
                                 $opts = self::$imap->folder_attributes($folder);
                                 if (!in_array('\\HasNoChildren', $opts) && ($data = self::$imap->get_metadata($folder.$delimiter.'*', $type_keys))) {
                                     $folderdata += $data;
                                 }
                             }
                         }
                     }
                     else if ($data = self::$imap->get_metadata($ns_root.$delimiter.'*', $type_keys)) {
                         $folderdata += $data;
                     }
                 }
             }
         }
         else {
             $folderdata = self::$imap->get_metadata($prefix, $type_keys);
         }
 
         if (!is_array($folderdata)) {
             return false;
         }
 
         // keep list in memory
         self::$typedata[$prefix] = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata);
 
         return self::$typedata[$prefix];
     }
 
 
     /**
      * Callback for array_map to select the correct annotation value
      */
     public static function folder_select_metadata($types)
     {
         if (!empty($types[self::CTYPE_KEY_PRIVATE])) {
             return $types[self::CTYPE_KEY_PRIVATE];
         }
         else if (!empty($types[self::CTYPE_KEY])) {
             list($ctype, ) = explode('.', $types[self::CTYPE_KEY]);
             return $ctype;
         }
         return null;
     }
 
 
     /**
      * Returns type of IMAP folder
      *
      * @param string $folder Folder name (UTF7-IMAP)
      *
      * @return string Folder type
      */
     public static function folder_type($folder)
     {
         self::setup();
 
         // return in-memory cached result
         foreach (self::$typedata as $typedata) {
             if (array_key_exists($folder, $typedata)) {
                 return $typedata[$folder];
             }
         }
 
         $metadata = self::$imap->get_metadata($folder, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE));
 
         if (!is_array($metadata)) {
             return null;
         }
 
         if (!empty($metadata[$folder])) {
             return self::folder_select_metadata($metadata[$folder]);
         }
 
         return 'mail';
     }
 
 
     /**
      * Sets folder content-type.
      *
      * @param string $folder Folder name
      * @param string $type   Content type
      *
      * @return boolean True on success
      */
     public static function set_folder_type($folder, $type='mail')
     {
         self::setup();
 
         list($ctype, $subtype) = explode('.', $type);
 
         $success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null));
 
         if (!$success)  // fallback: only set private annotation
             $success |= self::$imap->set_metadata($folder, array(self::CTYPE_KEY_PRIVATE => $type));
 
         return $success;
     }
 
 
     /**
      * Check subscription status of this folder
      *
      * @param string $folder Folder name
      * @param boolean $temp  Include temporary/session subscriptions
      *
      * @return boolean True if subscribed, false if not
      */
     public static function folder_is_subscribed($folder, $temp = false)
     {
         if (self::$subscriptions === null) {
             self::setup();
             self::$with_tempsubs = false;
             self::$subscriptions = self::$imap->list_folders_subscribed();
             self::$with_tempsubs = true;
         }
 
         return in_array($folder, self::$subscriptions) ||
             ($temp && in_array($folder, (array)$_SESSION['kolab_subscribed_folders']));
     }
 
 
     /**
      * Change subscription status of this folder
      *
      * @param string $folder Folder name
      * @param boolean $temp  Only subscribe temporarily for the current session
      *
      * @return True on success, false on error
      */
     public static function folder_subscribe($folder, $temp = false)
     {
         self::setup();
 
         // temporary/session subscription
         if ($temp) {
             if (self::folder_is_subscribed($folder)) {
                 return true;
             }
             else if (!is_array($_SESSION['kolab_subscribed_folders']) || !in_array($folder, $_SESSION['kolab_subscribed_folders'])) {
                 $_SESSION['kolab_subscribed_folders'][] = $folder;
                 return true;
             }
         }
         else if (self::$imap->subscribe($folder)) {
             self::$subscriptions = null;
             return true;
         }
 
         return false;
     }
 
 
     /**
      * Change subscription status of this folder
      *
      * @param string $folder Folder name
      * @param boolean $temp  Only remove temporary subscription
      *
      * @return True on success, false on error
      */
     public static function folder_unsubscribe($folder, $temp = false)
     {
         self::setup();
 
         // temporary/session subscription
         if ($temp) {
             if (is_array($_SESSION['kolab_subscribed_folders']) && ($i = array_search($folder, $_SESSION['kolab_subscribed_folders'])) !== false) {
                 unset($_SESSION['kolab_subscribed_folders'][$i]);
             }
             return true;
         }
         else if (self::$imap->unsubscribe($folder)) {
             self::$subscriptions = null;
             return true;
         }
 
         return false;
     }
 
 
     /**
      * Check activation status of this folder
      *
      * @param string $folder Folder name
      *
      * @return boolean True if active, false if not
      */
     public static function folder_is_active($folder)
     {
         $active_folders = self::get_states();
 
         return in_array($folder, $active_folders);
     }
 
 
     /**
      * Change activation status of this folder
      *
      * @param string $folder Folder name
      *
      * @return True on success, false on error
      */
     public static function folder_activate($folder)
     {
         // activation implies temporary subscription
         self::folder_subscribe($folder, true);
         return self::set_state($folder, true);
     }
 
 
     /**
      * Change activation status of this folder
      *
      * @param string $folder Folder name
      *
      * @return True on success, false on error
      */
     public static function folder_deactivate($folder)
     {
         // remove from temp subscriptions, really?
         self::folder_unsubscribe($folder, true);
 
         return self::set_state($folder, false);
     }
 
 
     /**
      * Return list of active folders
      */
     private static function get_states()
     {
         if (self::$states !== null) {
             return self::$states;
         }
 
         $rcube   = rcube::get_instance();
         $folders = $rcube->config->get('kolab_active_folders');
 
         if ($folders !== null) {
             self::$states = !empty($folders) ? explode('**', $folders) : array();
         }
         // for backward-compatibility copy server-side subscriptions to activation states
         else {
             self::setup();
             if (self::$subscriptions === null) {
                 self::$with_tempsubs = false;
                 self::$subscriptions = self::$imap->list_folders_subscribed();
                 self::$with_tempsubs = true;
             }
             self::$states = self::$subscriptions;
             $folders = implode(self::$states, '**');
             $rcube->user->save_prefs(array('kolab_active_folders' => $folders));
         }
 
         return self::$states;
     }
 
 
     /**
      * Update list of active folders
      */
     private static function set_state($folder, $state)
     {
         self::get_states();
 
         // update in-memory list
         $idx = array_search($folder, self::$states);
         if ($state && $idx === false) {
             self::$states[] = $folder;
         }
         else if (!$state && $idx !== false) {
             unset(self::$states[$idx]);
         }
 
         // update user preferences
         $folders = implode(self::$states, '**');
         $rcube   = rcube::get_instance();
         return $rcube->user->save_prefs(array('kolab_active_folders' => $folders));
     }
 
     /**
      * Creates default folder of specified type
      * To be run when none of subscribed folders (of specified type) is found
      *
      * @param string $type  Folder type
      * @param string $props Folder properties (color, etc)
      *
      * @return string Folder name
      */
     public static function create_default_folder($type, $props = array())
     {
         if (!self::setup()) {
             return;
         }
 
         $folders = self::$imap->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE));
 
         // from kolab_folders config
         $folder_type  = strpos($type, '.') ? str_replace('.', '_', $type) : $type . '_default';
         $default_name = self::$config->get('kolab_folders_' . $folder_type);
         $folder_type  = str_replace('_', '.', $folder_type);
 
         // check if we have any folder in personal namespace
         // folder(s) may exist but not subscribed
         foreach ((array)$folders as $f => $data) {
             if (strpos($data[self::CTYPE_KEY_PRIVATE], $type) === 0) {
                 $folder = $f;
                 break;
             }
         }
 
         if (!$folder) {
             if (!$default_name) {
                 $default_name = self::$default_folders[$type];
             }
 
             if (!$default_name) {
                 return;
             }
 
             $folder = rcube_charset::convert($default_name, RCUBE_CHARSET, 'UTF7-IMAP');
             $prefix = self::$imap->get_namespace('prefix');
 
             // add personal namespace prefix if needed
             if ($prefix && strpos($folder, $prefix) !== 0 && $folder != 'INBOX') {
                 $folder = $prefix . $folder;
             }
 
             if (!self::$imap->folder_exists($folder)) {
                 if (!self::$imap->create_folder($folder)) {
                     return;
                 }
             }
 
             self::set_folder_type($folder, $folder_type);
         }
 
         self::folder_subscribe($folder);
 
         if ($props['active']) {
             self::set_state($folder, true);
         }
 
         if (!empty($props)) {
             self::set_folder_props($folder, $props);
         }
 
         return $folder;
     }
 
     /**
      * Sets folder metadata properties
      *
      * @param string $folder Folder name
      * @param array  $prop   Folder properties
      */
     public static function set_folder_props($folder, &$prop)
     {
         if (!self::setup()) {
             return;
         }
 
         // TODO: also save 'showalarams' and other properties here
         $ns        = self::$imap->folder_namespace($folder);
         $supported = array(
             'color'       => array(self::COLOR_KEY_SHARED, self::COLOR_KEY_PRIVATE),
             'displayname' => array(self::NAME_KEY_SHARED, self::NAME_KEY_PRIVATE),
         );
 
         foreach ($supported as $key => $metakeys) {
             if (array_key_exists($key, $prop)) {
                 $meta_saved = false;
                 if ($ns == 'personal')  // save in shared namespace for personal folders
                     $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key]));
                 if (!$meta_saved)    // try in private namespace
                     $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key]));
                 if ($meta_saved)
                     unset($prop[$key]);  // unsetting will prevent fallback to local user prefs
             }
         }
     }
 
 
     /**
      *
      * @param mixed   $query    Search value (or array of field => value pairs)
      * @param int     $mode     Matching mode: 0 - partial (*abc*), 1 - strict (=), 2 - prefix (abc*)
      * @param array   $required List of fields that shall ot be empty
      * @param int     $limit    Maximum number of records
      * @param int     $count    Returns the number of records found
      *
      * @return array List or false on error
      */
     public static function search_users($query, $mode = 1, $required = array(), $limit = 0, &$count = 0)
     {
         $query = str_replace('*', '', $query);
 
         // requires a working LDAP setup
         if (!self::ldap() || strlen($query) == 0) {
             return array();
         }
 
         // search users using the configured attributes
         $results = self::$ldap->dosearch(self::$config->get('kolab_users_search_attrib', array('cn','mail','alias')), $query, $mode, $required, $limit, $count);
 
         // exclude myself
         if ($_SESSION['kolab_dn']) {
             unset($results[$_SESSION['kolab_dn']]);
         }
 
         // resolve to IMAP folder name
         $root = self::namespace_root('other');
         $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
 
         array_walk($results, function(&$user, $dn) use ($root, $user_attrib) {
             list($localpart, ) = explode('@', $user[$user_attrib]);
             $user['kolabtargetfolder'] = $root . $localpart;
         });
 
         return $results;
     }
 
 
     /**
      * Returns a list of IMAP folders shared by the given user
      *
      * @param array   User entry from LDAP
      * @param string  Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
      * @param boolean Return subscribed folders only (null to use configured subscription mode)
      * @param array   Will be filled with folder-types data
      *
      * @return array List of folders
      */
     public static function list_user_folders($user, $type, $subscribed = null, &$folderdata = array())
     {
         self::setup();
 
         $folders = array();
 
         // use localpart of user attribute as root for folder listing
         $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail'));
         if (!empty($user[$user_attrib])) {
             list($mbox) = explode('@', $user[$user_attrib]);
 
             $delimiter = self::$imap->get_hierarchy_delimiter();
             $other_ns = self::namespace_root('other');
             $folders = self::list_folders($other_ns . $mbox . $delimiter, '*', $type, $subscribed, $folderdata);
         }
 
         return $folders;
     }
 
 
     /**
      * Get a list of (virtual) top-level folders from the other users namespace
      *
      * @param string  Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
      * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
      *
      * @return array List of kolab_storage_folder_user objects
      */
     public static function get_user_folders($type, $subscribed)
     {
         $folders = $folderdata = array();
 
         if (self::setup()) {
             $delimiter = self::$imap->get_hierarchy_delimiter();
             $other_ns = rtrim(self::namespace_root('other'), $delimiter);
             $path_len = count(explode($delimiter, $other_ns));
 
             foreach ((array)self::list_folders($other_ns . $delimiter, '*', '', $subscribed) as $foldername) {
                 if ($foldername == 'INBOX')  // skip INBOX which is added by default
                     continue;
 
                 $path = explode($delimiter, $foldername);
 
                 // compare folder type if a subfolder is listed
                 if ($type && count($path) > $path_len + 1 && $type != self::folder_type($foldername)) {
                     continue;
                 }
 
                 // truncate folder path to top-level folders of the 'other' namespace
                 $foldername = join($delimiter, array_slice($path, 0, $path_len + 1));
 
                 if (!$folders[$foldername]) {
                     $folders[$foldername] = new kolab_storage_folder_user($foldername, $other_ns);
                 }
             }
 
             // for every (subscribed) user folder, list all (unsubscribed) subfolders
             foreach ($folders as $userfolder) {
                 foreach ((array)self::list_folders($userfolder->name . $delimiter, '*', $type, false, $folderdata) as $foldername) {
                     if (!$folders[$foldername]) {
                         $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]);
                         $userfolder->children[] = $folders[$foldername];
                     }
                 }
             }
         }
 
         return $folders;
     }
 
 
     /**
      * Handler for user_delete plugin hooks
      *
      * Remove all cache data from the local database related to the given user.
      */
     public static function delete_user_folders($args)
     {
         $db = rcmail::get_instance()->get_dbh();
         $prefix = 'imap://' . urlencode($args['username']) . '@' . $args['host'] . '/%';
         $db->query("DELETE FROM " . $db->table_name('kolab_folders', true) . " WHERE `resource` LIKE ?", $prefix);
     }
 }