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 @@ - '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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ - '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 @@ - ';', - '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("/(?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 @@ -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[1-2][0-9]{3})(?P[0-1][0-9])(?P[0-3][0-9])'; - $time = '(?P[0-2][0-9])(?P[0-5][0-9])(?P[0-5][0-9])'; - $regex = "/^$date(T$time(?PZ)?)?$/"; - - 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 @@ -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 @@ -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[A-Z0-9-]+)(?:;(?P^(?([^:^\"]|\"([^\"]*)\")*))?"; - $regex = "/^(?P$token)$parameters:(?P.*)$/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[^\"^;]*|"[^"]*")'; - - $regex = "/(?<=^|;)(?P$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 @@ - 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 @@ -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 @@ -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 @@ -'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 @@ - * * Copyright (C) 2013-2015, Kolab Systems AG * * 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 . */ 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 $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) { 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; } 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') { + foreach ($prop->parameters() as $pname => $pvalue) { + if ($pname == 'VALUE' && $pvalue == '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_alaram_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']); } // 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) { - $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); + $vdt->setValue($dt); + + if ($is_dateonly) { + $vdt['VALUE'] = 'DATE'; + } // 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)); } 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])); $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))); } } if ($event['organizer']) { $ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'], 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; - - 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; - } - } + protected $structuredValues = array( + // vCard + 'N', + 'ADR', + 'ORG', + 'GENDER', + 'LOCATION', + // iCalendar + 'REQUEST-STATUS', + ); - return $out; - } } diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php index e0e87636..6b42fd41 100644 --- a/plugins/libcalendaring/tests/libvcalendar.php +++ b/plugins/libcalendaring/tests/libvcalendar.php @@ -1,562 +1,568 @@ * - * Copyright (C) 2013, Kolab Systems AG + * Copyright (C) 2014, Kolab Systems AG * * 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 . */ 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)"); $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->assertTrue($attendee['rsvp'], 'Attendee 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('Kalender', $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"); // 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"); $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"); // 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"); } /** * @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->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:"); $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]; $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('RECURRENCE-ID:20130814', $ics, "Recurrence-ID (1) being the exception date"); + $this->assertContains('RECURRENCE-ID: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; } }