Merge branch 'dev/sabre-vobject3'

Resolves T53
This commit is contained in:
Thomas Bruederli 2015-04-22 17:20:55 +02:00
commit 1e2089e2c2
37 changed files with 257 additions and 5610 deletions

View file

@ -19,7 +19,8 @@
}
],
"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"
}
}

View file

@ -1,405 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* VObject Component
*
* This class represents a VCALENDAR/VCARD component. A component is for example
* VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
* ends with END:COMPONENTNAME
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Component extends Node {
/**
* Name, for example VEVENT
*
* @var string
*/
public $name;
/**
* Children properties and components
*
* @var array
*/
public $children = array();
/**
* If components are added to this map, they will be automatically mapped
* to their respective classes, if parsed by the reader or constructed with
* the 'create' method.
*
* @var array
*/
static public $classMap = array(
'VALARM' => 'Sabre\\VObject\\Component\\VAlarm',
'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar',
'VCARD' => 'Sabre\\VObject\\Component\\VCard',
'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
);
/**
* Creates the new component by name, but in addition will also see if
* there's a class mapped to the property name.
*
* @param string $name
* @param string $value
* @return Component
*/
static public function create($name, $value = null) {
$name = strtoupper($name);
if (isset(self::$classMap[$name])) {
return new self::$classMap[$name]($name, $value);
} else {
return new self($name, $value);
}
}
/**
* Creates a new component.
*
* By default this object will iterate over its own children, but this can
* be overridden with the iterator argument
*
* @param string $name
* @param ElementList $iterator
*/
public function __construct($name, ElementList $iterator = null) {
$this->name = strtoupper($name);
if (!is_null($iterator)) $this->iterator = $iterator;
}
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize() {
$str = "BEGIN:" . $this->name . "\r\n";
/**
* Gives a component a 'score' for sorting purposes.
*
* This is solely used by the childrenSort method.
*
* A higher score means the item will be lower in the list.
* To avoid score collisions, each "score category" has a reasonable
* space to accomodate elements. The $key is added to the $score to
* preserve the original relative order of elements.
*
* @param int $key
* @param array $array
* @return int
*/
$sortScore = function($key, $array) {
if ($array[$key] instanceof Component) {
// We want to encode VTIMEZONE first, this is a personal
// preference.
if ($array[$key]->name === 'VTIMEZONE') {
$score=300000000;
return $score+$key;
} else {
$score=400000000;
return $score+$key;
}
} else {
// Properties get encoded first
// VCARD version 4.0 wants the VERSION property to appear first
if ($array[$key] instanceof Property) {
if ($array[$key]->name === 'VERSION') {
$score=100000000;
return $score+$key;
} else {
// All other properties
$score=200000000;
return $score+$key;
}
}
}
};
$tmp = $this->children;
uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
$sA = $sortScore($a, $tmp);
$sB = $sortScore($b, $tmp);
if ($sA === $sB) return 0;
return ($sA < $sB) ? -1 : 1;
});
foreach($this->children as $child) $str.=$child->serialize();
$str.= "END:" . $this->name . "\r\n";
return $str;
}
/**
* Adds a new component or element
*
* You can call this method with the following syntaxes:
*
* add(Node $node)
* add(string $name, $value, array $parameters = array())
*
* The first version adds an Element
* The second adds a property as a string.
*
* @param mixed $item
* @param mixed $itemValue
* @return void
*/
public function add($item, $itemValue = null, array $parameters = array()) {
if ($item instanceof Node) {
if (!is_null($itemValue)) {
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
}
$item->parent = $this;
$this->children[] = $item;
} elseif(is_string($item)) {
$item = Property::create($item,$itemValue, $parameters);
$item->parent = $this;
$this->children[] = $item;
} else {
throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
}
}
/**
* Returns an iterable list of children
*
* @return ElementList
*/
public function children() {
return new ElementList($this->children);
}
/**
* Returns an array with elements that match the specified name.
*
* This function is also aware of MIME-Directory groups (as they appear in
* vcards). This means that if a property is grouped as "HOME.EMAIL", it
* will also be returned when searching for just "EMAIL". If you want to
* search for a property in a specific group, you can select on the entire
* string ("HOME.EMAIL"). If you want to search on a specific property that
* has not been assigned a group, specify ".EMAIL".
*
* Keys are retained from the 'children' array, which may be confusing in
* certain cases.
*
* @param string $name
* @return array
*/
public function select($name) {
$group = null;
$name = strtoupper($name);
if (strpos($name,'.')!==false) {
list($group,$name) = explode('.', $name, 2);
}
$result = array();
foreach($this->children as $key=>$child) {
if (
strtoupper($child->name) === $name &&
(is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
) {
$result[$key] = $child;
}
}
reset($result);
return $result;
}
/**
* This method only returns a list of sub-components. Properties are
* ignored.
*
* @return array
*/
public function getComponents() {
$result = array();
foreach($this->children as $child) {
if ($child instanceof Component) {
$result[] = $child;
}
}
return $result;
}
/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
* @return array
*/
public function validate($options = 0) {
$result = array();
foreach($this->children as $child) {
$result = array_merge($result, $child->validate($options));
}
return $result;
}
/* Magic property accessors {{{ */
/**
* Using 'get' you will either get a property or component,
*
* If there were no child-elements found with the specified name,
* null is returned.
*
* @param string $name
* @return Property
*/
public function __get($name) {
$matches = $this->select($name);
if (count($matches)===0) {
return null;
} else {
$firstMatch = current($matches);
/** @var $firstMatch Property */
$firstMatch->setIterator(new ElementList(array_values($matches)));
return $firstMatch;
}
}
/**
* This method checks if a sub-element with the specified name exists.
*
* @param string $name
* @return bool
*/
public function __isset($name) {
$matches = $this->select($name);
return count($matches)>0;
}
/**
* Using the setter method you can add properties or subcomponents
*
* You can either pass a Component, Property
* object, or a string to automatically create a Property.
*
* If the item already exists, it will be removed. If you want to add
* a new item with the same name, always use the add() method.
*
* @param string $name
* @param mixed $value
* @return void
*/
public function __set($name, $value) {
$matches = $this->select($name);
$overWrite = count($matches)?key($matches):null;
if ($value instanceof Component || $value instanceof Property) {
$value->parent = $this;
if (!is_null($overWrite)) {
$this->children[$overWrite] = $value;
} else {
$this->children[] = $value;
}
} elseif (is_scalar($value)) {
$property = Property::create($name,$value);
$property->parent = $this;
if (!is_null($overWrite)) {
$this->children[$overWrite] = $property;
} else {
$this->children[] = $property;
}
} else {
throw new \InvalidArgumentException('You must pass a \\Sabre\\VObject\\Component, \\Sabre\\VObject\\Property or scalar type');
}
}
/**
* Removes all properties and components within this component.
*
* @param string $name
* @return void
*/
public function __unset($name) {
$matches = $this->select($name);
foreach($matches as $k=>$child) {
unset($this->children[$k]);
$child->parent = null;
}
}
/* }}} */
/**
* This method is automatically called when the object is cloned.
* Specifically, this will ensure all child elements are also cloned.
*
* @return void
*/
public function __clone() {
foreach($this->children as $key=>$child) {
$this->children[$key] = clone $child;
$this->children[$key]->parent = $this;
}
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VAlarm component
*
* This component contains some additional functionality specific for VALARMs.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VAlarm extends VObject\Component {
/**
* Returns a DateTime object when this alarm is going to trigger.
*
* This ignores repeated alarm, only the first trigger is returned.
*
* @return DateTime
*/
public function getEffectiveTriggerTime() {
$trigger = $this->TRIGGER;
if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
$triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
$related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
$parentComponent = $this->parent;
if ($related === 'START') {
if ($parentComponent->name === 'VTODO') {
$propName = 'DUE';
} else {
$propName = 'DTSTART';
}
$effectiveTrigger = clone $parentComponent->$propName->getDateTime();
$effectiveTrigger->add($triggerDuration);
} else {
if ($parentComponent->name === 'VTODO') {
$endProp = 'DUE';
} elseif ($parentComponent->name === 'VEVENT') {
$endProp = 'DTEND';
} else {
throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
}
if (isset($parentComponent->$endProp)) {
$effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
$effectiveTrigger->add($triggerDuration);
} elseif (isset($parentComponent->DURATION)) {
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
$duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
$effectiveTrigger->add($duration);
$effectiveTrigger->add($triggerDuration);
} else {
$effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
$effectiveTrigger->add($triggerDuration);
}
}
} else {
$effectiveTrigger = $trigger->getDateTime();
}
return $effectiveTrigger;
}
/**
* Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes.
*
* The rules used to determine if an event falls within the specified
* time-range is based on the CalDAV specification.
*
* @param \DateTime $start
* @param \DateTime $end
* @return bool
*/
public function isInTimeRange(\DateTime $start, \DateTime $end) {
$effectiveTrigger = $this->getEffectiveTriggerTime();
if (isset($this->DURATION)) {
$duration = VObject\DateTimeParser::parseDuration($this->DURATION);
$repeat = (string)$this->repeat;
if (!$repeat) {
$repeat = 1;
}
$period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
foreach($period as $occurrence) {
if ($start <= $occurrence && $end > $occurrence) {
return true;
}
}
return false;
} else {
return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
}
}
}

View file

@ -1,244 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* The VCalendar component
*
* This component adds functionality to a component, specific for a VCALENDAR.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCalendar extends VObject\Document {
static $defaultName = 'VCALENDAR';
/**
* Returns a list of all 'base components'. For instance, if an Event has
* a recurrence rule, and one instance is overridden, the overridden event
* will have the same UID, but will be excluded from this list.
*
* VTIMEZONE components will always be excluded.
*
* @param string $componentName filter by component name
* @return array
*/
public function getBaseComponents($componentName = null) {
$components = array();
foreach($this->children as $component) {
if (!$component instanceof VObject\Component)
continue;
if (isset($component->{'RECURRENCE-ID'}))
continue;
if ($componentName && $component->name !== strtoupper($componentName))
continue;
if ($component->name === 'VTIMEZONE')
continue;
$components[] = $component;
}
return $components;
}
/**
* If this calendar object, has events with recurrence rules, this method
* can be used to expand the event into multiple sub-events.
*
* Each event will be stripped from it's recurrence information, and only
* the instances of the event in the specified timerange will be left
* alone.
*
* In addition, this method will cause timezone information to be stripped,
* and normalized to UTC.
*
* This method will alter the VCalendar. This cannot be reversed.
*
* This functionality is specifically used by the CalDAV standard. It is
* possible for clients to request expand events, if they are rather simple
* clients and do not have the possibility to calculate recurrences.
*
* @param DateTime $start
* @param DateTime $end
* @return void
*/
public function expand(\DateTime $start, \DateTime $end) {
$newEvents = array();
foreach($this->select('VEVENT') as $key=>$vevent) {
if (isset($vevent->{'RECURRENCE-ID'})) {
unset($this->children[$key]);
continue;
}
if (!$vevent->rrule) {
unset($this->children[$key]);
if ($vevent->isInTimeRange($start, $end)) {
$newEvents[] = $vevent;
}
continue;
}
$uid = (string)$vevent->uid;
if (!$uid) {
throw new \LogicException('Event did not have a UID!');
}
$it = new VObject\RecurrenceIterator($this, $vevent->uid);
$it->fastForward($start);
while($it->valid() && $it->getDTStart() < $end) {
if ($it->getDTEnd() > $start) {
$newEvents[] = $it->getEventObject();
}
$it->next();
}
unset($this->children[$key]);
}
foreach($newEvents as $newEvent) {
foreach($newEvent->children as $child) {
if ($child instanceof VObject\Property\DateTime &&
$child->getDateType() == VObject\Property\DateTime::LOCALTZ) {
$child->setDateTime($child->getDateTime(),VObject\Property\DateTime::UTC);
}
}
$this->add($newEvent);
}
// Removing all VTIMEZONE components
unset($this->VTIMEZONE);
}
/**
* Validates the node for correctness.
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @return array
*/
/*
public function validate() {
$warnings = array();
$version = $this->select('VERSION');
if (count($version)!==1) {
$warnings[] = array(
'level' => 1,
'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time',
'node' => $this,
);
} else {
if ((string)$this->VERSION !== '2.0') {
$warnings[] = array(
'level' => 1,
'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
'node' => $this,
);
}
}
$version = $this->select('PRODID');
if (count($version)!==1) {
$warnings[] = array(
'level' => 2,
'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time',
'node' => $this,
);
}
if (count($this->CALSCALE) > 1) {
$warnings[] = array(
'level' => 2,
'message' => 'The CALSCALE property must not be specified more than once.',
'node' => $this,
);
}
if (count($this->METHOD) > 1) {
$warnings[] = array(
'level' => 2,
'message' => 'The METHOD property must not be specified more than once.',
'node' => $this,
);
}
$allowedComponents = array(
'VEVENT',
'VTODO',
'VJOURNAL',
'VFREEBUSY',
'VTIMEZONE',
);
$allowedProperties = array(
'PRODID',
'VERSION',
'CALSCALE',
'METHOD',
);
$componentsFound = 0;
foreach($this->children as $child) {
if($child instanceof Component) {
$componentsFound++;
if (!in_array($child->name, $allowedComponents)) {
$warnings[] = array(
'level' => 1,
'message' => 'The ' . $child->name . " component is not allowed in the VCALENDAR component",
'node' => $this,
);
}
}
if ($child instanceof Property) {
if (!in_array($child->name, $allowedProperties)) {
$warnings[] = array(
'level' => 2,
'message' => 'The ' . $child->name . " property is not allowed in the VCALENDAR component",
'node' => $this,
);
}
}
}
if ($componentsFound===0) {
$warnings[] = array(
'level' => 1,
'message' => 'An iCalendar object must have at least 1 component.',
'node' => $this,
);
}
return array_merge(
$warnings,
parent::validate()
);
}
*/
}

View file

@ -1,107 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* The VCard component
*
* This component represents the BEGIN:VCARD and END:VCARD found in every
* vcard.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCard extends VObject\Component {
static $defaultName = 'VCARD';
/**
* VCards with version 2.1, 3.0 and 4.0 are found.
*
* If the VCARD doesn't know its version, 4.0 is assumed.
*/
const DEFAULT_VERSION = '4.0';
/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
* @return array
*/
public function validate($options = 0) {
$warnings = array();
$version = $this->select('VERSION');
if (count($version)!==1) {
$warnings[] = array(
'level' => 1,
'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->VERSION = self::DEFAULT_VERSION;
}
} else {
$version = (string)$this->VERSION;
if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
$warnings[] = array(
'level' => 1,
'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->VERSION = '4.0';
}
}
}
$fn = $this->select('FN');
if (count($fn)!==1) {
$warnings[] = array(
'level' => 1,
'message' => 'The FN property must appear in the VCARD component exactly 1 time',
'node' => $this,
);
if (($options & self::REPAIR) && count($fn) === 0) {
// We're going to try to see if we can use the contents of the
// N property.
if (isset($this->N)) {
$value = explode(';', (string)$this->N);
if (isset($value[1]) && $value[1]) {
$this->FN = $value[1] . ' ' . $value[0];
} else {
$this->FN = $value[0];
}
// Otherwise, the ORG property may work
} elseif (isset($this->ORG)) {
$this->FN = (string)$this->ORG;
}
}
}
return array_merge(
parent::validate($options),
$warnings
);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VEvent component
*
* This component contains some additional functionality specific for VEVENT's.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VEvent extends VObject\Component {
/**
* Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes.
*
* The rules used to determine if an event falls within the specified
* time-range is based on the CalDAV specification.
*
* @param \DateTime $start
* @param \DateTime $end
* @return bool
*/
public function isInTimeRange(\DateTime $start, \DateTime $end) {
if ($this->RRULE) {
$it = new VObject\RecurrenceIterator($this);
$it->fastForward($start);
// We fast-forwarded to a spot where the end-time of the
// recurrence instance exceeded the start of the requested
// time-range.
//
// If the starttime of the recurrence did not exceed the
// end of the time range as well, we have a match.
return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
}
$effectiveStart = $this->DTSTART->getDateTime();
if (isset($this->DTEND)) {
// The DTEND property is considered non inclusive. So for a 3 day
// event in july, dtstart and dtend would have to be July 1st and
// July 4th respectively.
//
// See:
// http://tools.ietf.org/html/rfc5545#page-54
$effectiveEnd = $this->DTEND->getDateTime();
} elseif (isset($this->DURATION)) {
$effectiveEnd = clone $effectiveStart;
$effectiveEnd->add( VObject\DateTimeParser::parseDuration($this->DURATION) );
} elseif ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
$effectiveEnd = clone $effectiveStart;
$effectiveEnd->modify('+1 day');
} else {
$effectiveEnd = clone $effectiveStart;
}
return (
($start <= $effectiveEnd) && ($end > $effectiveStart)
);
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* The VFreeBusy component
*
* This component adds functionality to a component, specific for VFREEBUSY
* components.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VFreeBusy extends VObject\Component {
/**
* Checks based on the contained FREEBUSY information, if a timeslot is
* available.
*
* @param DateTime $start
* @param Datetime $end
* @return bool
*/
public function isFree(\DateTime $start, \Datetime $end) {
foreach($this->select('FREEBUSY') as $freebusy) {
// We are only interested in FBTYPE=BUSY (the default),
// FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
continue;
}
// The freebusy component can hold more than 1 value, separated by
// commas.
$periods = explode(',', (string)$freebusy);
foreach($periods as $period) {
// Every period is formatted as [start]/[end]. The start is an
// absolute UTC time, the end may be an absolute UTC time, or
// duration (relative) value.
list($busyStart, $busyEnd) = explode('/', $period);
$busyStart = VObject\DateTimeParser::parse($busyStart);
$busyEnd = VObject\DateTimeParser::parse($busyEnd);
if ($busyEnd instanceof \DateInterval) {
$tmp = clone $busyStart;
$tmp->add($busyEnd);
$busyEnd = $tmp;
}
if($start < $busyEnd && $end > $busyStart) {
return false;
}
}
}
return true;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VJournal component
*
* This component contains some additional functionality specific for VJOURNALs.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VJournal extends VObject\Component {
/**
* Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes.
*
* The rules used to determine if an event falls within the specified
* time-range is based on the CalDAV specification.
*
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
public function isInTimeRange(\DateTime $start, \DateTime $end) {
$dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
if ($dtstart) {
$effectiveEnd = clone $dtstart;
if ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
$effectiveEnd->modify('+1 day');
}
return ($start <= $effectiveEnd && $end > $dtstart);
}
return false;
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VTodo component
*
* This component contains some additional functionality specific for VTODOs.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VTodo extends VObject\Component {
/**
* Returns true or false depending on if the event falls in the specified
* time-range. This is used for filtering purposes.
*
* The rules used to determine if an event falls within the specified
* time-range is based on the CalDAV specification.
*
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
public function isInTimeRange(\DateTime $start, \DateTime $end) {
$dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
$duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null;
$due = isset($this->DUE)?$this->DUE->getDateTime():null;
$completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
$created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
if ($dtstart) {
if ($duration) {
$effectiveEnd = clone $dtstart;
$effectiveEnd->add($duration);
return $start <= $effectiveEnd && $end > $dtstart;
} elseif ($due) {
return
($start < $due || $start <= $dtstart) &&
($end > $dtstart || $end >= $due);
} else {
return $start <= $dtstart && $end > $dtstart;
}
}
if ($due) {
return ($start < $due && $end >= $due);
}
if ($completed && $created) {
return
($start <= $created || $start <= $completed) &&
($end >= $created || $end >= $completed);
}
if ($completed) {
return ($start <= $completed && $end >= $completed);
}
if ($created) {
return ($end > $created);
}
return true;
}
}

View file

@ -1,181 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* DateTimeParser
*
* This class is responsible for parsing the several different date and time
* formats iCalendar and vCards have.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class DateTimeParser {
/**
* Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
*
* Specifying a reference timezone is optional. It will only be used
* if the non-UTC format is used. The argument is used as a reference, the
* returned DateTime object will still be in the UTC timezone.
*
* @param string $dt
* @param DateTimeZone $tz
* @return DateTime
*/
static public function parseDateTime($dt,\DateTimeZone $tz = null) {
// Format is YYYYMMDD + "T" + hhmmss
$result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
}
if ($matches[7]==='Z' || is_null($tz)) {
$tz = new \DateTimeZone('UTC');
}
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
// Still resetting the timezone, to normalize everything to UTC
$date->setTimeZone(new \DateTimeZone('UTC'));
return $date;
}
/**
* Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
*
* @param string $date
* @return DateTime
*/
static public function parseDate($date) {
// Format is YYYYMMDD
$result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
}
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
return $date;
}
/**
* Parses an iCalendar (RFC5545) formatted duration value.
*
* This method will either return a DateTimeInterval object, or a string
* suitable for strtotime or DateTime::modify.
*
* @param string $duration
* @param bool $asString
* @return DateInterval|string
*/
static public function parseDuration($duration, $asString = false) {
$result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
if (!$result) {
throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
}
if (!$asString) {
$invert = false;
if ($matches['plusminus']==='-') {
$invert = true;
}
$parts = array(
'week',
'day',
'hour',
'minute',
'second',
);
foreach($parts as $part) {
$matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
}
// We need to re-construct the $duration string, because weeks and
// days are not supported by DateInterval in the same string.
$duration = 'P';
$days = $matches['day'];
if ($matches['week']) {
$days+=$matches['week']*7;
}
if ($days)
$duration.=$days . 'D';
if ($matches['minute'] || $matches['second'] || $matches['hour']) {
$duration.='T';
if ($matches['hour'])
$duration.=$matches['hour'].'H';
if ($matches['minute'])
$duration.=$matches['minute'].'M';
if ($matches['second'])
$duration.=$matches['second'].'S';
}
if ($duration==='P') {
$duration = 'PT0S';
}
$iv = new \DateInterval($duration);
if ($invert) $iv->invert = true;
return $iv;
}
$parts = array(
'week',
'day',
'hour',
'minute',
'second',
);
$newDur = '';
foreach($parts as $part) {
if (isset($matches[$part]) && $matches[$part]) {
$newDur.=' '.$matches[$part] . ' ' . $part . 's';
}
}
$newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
if ($newDur === '+') { $newDur = '+0 seconds'; };
return $newDur;
}
/**
* Parses either a Date or DateTime, or Duration value.
*
* @param string $date
* @param DateTimeZone|string $referenceTZ
* @return DateTime|DateInterval
*/
static public function parse($date, $referenceTZ = null) {
if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
return self::parseDuration($date);
} elseif (strlen($date)===8) {
return self::parseDate($date);
} else {
return self::parseDateTime($date, $referenceTZ);
}
}
}

View file

@ -1,109 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* Document
*
* A document is just like a component, except that it's also the top level
* element.
*
* Both a VCALENDAR and a VCARD are considered documents.
*
* This class also provides a registry for document types.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH. All rights reserved.
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Document extends Component {
/**
* The default name for this component.
*
* This should be 'VCALENDAR' or 'VCARD'.
*
* @var string
*/
static $defaultName;
/**
* Creates a new document.
*
* We're changing the default behavior slightly here. First, we don't want
* to have to specify a name (we already know it), and we want to allow
* children to be specified in the first argument.
*
* But, the default behavior also works.
*
* So the two sigs:
*
* new Document(array $children = array());
* new Document(string $name, array $children = array())
*
* @return void
*/
public function __construct() {
$args = func_get_args();
if (count($args)===0 || is_array($args[0])) {
array_unshift($args, static::$defaultName);
call_user_func_array(array('parent', '__construct'), $args);
} else {
call_user_func_array(array('parent', '__construct'), $args);
}
}
/**
* Creates a new component
*
* This method automatically searches for the correct component class, based
* on its name.
*
* You can specify the children either in key=>value syntax, in which case
* properties will automatically be created, or you can just pass a list of
* Component and Property object.
*
* @param string $name
* @param array $children
* @return Component
*/
public function createComponent($name, array $children = array()) {
$component = Component::create($name);
foreach($children as $k=>$v) {
if ($v instanceof Node) {
$component->add($v);
} else {
$component->add($k, $v);
}
}
return $component;
}
/**
* Factory method for creating new properties
*
* This method automatically searches for the correct property class, based
* on its name.
*
* You can specify the parameters either in key=>value syntax, in which case
* parameters will automatically be created, or you can just pass a list of
* Parameter objects.
*
* @param string $name
* @param mixed $value
* @param array $parameters
* @return Property
*/
public function createProperty($name, $value = null, array $parameters = array()) {
return Property::create($name, $value, $parameters);
}
}

View file

@ -1,172 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* VObject ElementList
*
* This class represents a list of elements. Lists are the result of queries,
* such as doing $vcalendar->vevent where there's multiple VEVENT objects.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ElementList implements \Iterator, \Countable, \ArrayAccess {
/**
* Inner elements
*
* @var array
*/
protected $elements = array();
/**
* Creates the element list.
*
* @param array $elements
*/
public function __construct(array $elements) {
$this->elements = $elements;
}
/* {{{ Iterator interface */
/**
* Current position
*
* @var int
*/
private $key = 0;
/**
* Returns current item in iteration
*
* @return Element
*/
public function current() {
return $this->elements[$this->key];
}
/**
* To the next item in the iterator
*
* @return void
*/
public function next() {
$this->key++;
}
/**
* Returns the current iterator key
*
* @return int
*/
public function key() {
return $this->key;
}
/**
* Returns true if the current position in the iterator is a valid one
*
* @return bool
*/
public function valid() {
return isset($this->elements[$this->key]);
}
/**
* Rewinds the iterator
*
* @return void
*/
public function rewind() {
$this->key = 0;
}
/* }}} */
/* {{{ Countable interface */
/**
* Returns the number of elements
*
* @return int
*/
public function count() {
return count($this->elements);
}
/* }}} */
/* {{{ ArrayAccess Interface */
/**
* Checks if an item exists through ArrayAccess.
*
* @param int $offset
* @return bool
*/
public function offsetExists($offset) {
return isset($this->elements[$offset]);
}
/**
* Gets an item through ArrayAccess.
*
* @param int $offset
* @return mixed
*/
public function offsetGet($offset) {
return $this->elements[$offset];
}
/**
* Sets an item through ArrayAccess.
*
* @param int $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset,$value) {
throw new \LogicException('You can not add new objects to an ElementList');
}
/**
* Sets an item through ArrayAccess.
*
* This method just forwards the request to the inner iterator
*
* @param int $offset
* @return void
*/
public function offsetUnset($offset) {
throw new \LogicException('You can not remove objects from an ElementList');
}
/* }}} */
}

View file

@ -1,322 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* This class helps with generating FREEBUSY reports based on existing sets of
* objects.
*
* It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
* generates a single VFREEBUSY object.
*
* VFREEBUSY components are described in RFC5545, The rules for what should
* go in a single freebusy report is taken from RFC4791, section 7.10.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class FreeBusyGenerator {
/**
* Input objects
*
* @var array
*/
protected $objects;
/**
* Start of range
*
* @var DateTime|null
*/
protected $start;
/**
* End of range
*
* @var DateTime|null
*/
protected $end;
/**
* VCALENDAR object
*
* @var Component
*/
protected $baseObject;
/**
* Creates the generator.
*
* Check the setTimeRange and setObjects methods for details about the
* arguments.
*
* @param DateTime $start
* @param DateTime $end
* @param mixed $objects
* @return void
*/
public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
if ($start && $end) {
$this->setTimeRange($start, $end);
}
if ($objects) {
$this->setObjects($objects);
}
}
/**
* Sets the VCALENDAR object.
*
* If this is set, it will not be generated for you. You are responsible
* for setting things like the METHOD, CALSCALE, VERSION, etc..
*
* The VFREEBUSY object will be automatically added though.
*
* @param Component $vcalendar
* @return void
*/
public function setBaseObject(Component $vcalendar) {
$this->baseObject = $vcalendar;
}
/**
* Sets the input objects
*
* You must either specify a valendar object as a strong, or as the parse
* Component.
* It's also possible to specify multiple objects as an array.
*
* @param mixed $objects
* @return void
*/
public function setObjects($objects) {
if (!is_array($objects)) {
$objects = array($objects);
}
$this->objects = array();
foreach($objects as $object) {
if (is_string($object)) {
$this->objects[] = Reader::read($object);
} elseif ($object instanceof Component) {
$this->objects[] = $object;
} else {
throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
}
}
}
/**
* Sets the time range
*
* Any freebusy object falling outside of this time range will be ignored.
*
* @param DateTime $start
* @param DateTime $end
* @return void
*/
public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
$this->start = $start;
$this->end = $end;
}
/**
* Parses the input data and returns a correct VFREEBUSY object, wrapped in
* a VCALENDAR.
*
* @return Component
*/
public function getResult() {
$busyTimes = array();
foreach($this->objects as $object) {
foreach($object->getBaseComponents() as $component) {
switch($component->name) {
case 'VEVENT' :
$FBTYPE = 'BUSY';
if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
break;
}
if (isset($component->STATUS)) {
$status = strtoupper($component->STATUS);
if ($status==='CANCELLED') {
break;
}
if ($status==='TENTATIVE') {
$FBTYPE = 'BUSY-TENTATIVE';
}
}
$times = array();
if ($component->RRULE) {
$iterator = new RecurrenceIterator($object, (string)$component->uid);
if ($this->start) {
$iterator->fastForward($this->start);
}
$maxRecurrences = 200;
while($iterator->valid() && --$maxRecurrences) {
$startTime = $iterator->getDTStart();
if ($this->end && $startTime > $this->end) {
break;
}
$times[] = array(
$iterator->getDTStart(),
$iterator->getDTEnd(),
);
$iterator->next();
}
} else {
$startTime = $component->DTSTART->getDateTime();
if ($this->end && $startTime > $this->end) {
break;
}
$endTime = null;
if (isset($component->DTEND)) {
$endTime = $component->DTEND->getDateTime();
} elseif (isset($component->DURATION)) {
$duration = DateTimeParser::parseDuration((string)$component->DURATION);
$endTime = clone $startTime;
$endTime->add($duration);
} elseif ($component->DTSTART->getDateType() === Property\DateTime::DATE) {
$endTime = clone $startTime;
$endTime->modify('+1 day');
} else {
// The event had no duration (0 seconds)
break;
}
$times[] = array($startTime, $endTime);
}
foreach($times as $time) {
if ($this->end && $time[0] > $this->end) break;
if ($this->start && $time[1] < $this->start) break;
$busyTimes[] = array(
$time[0],
$time[1],
$FBTYPE,
);
}
break;
case 'VFREEBUSY' :
foreach($component->FREEBUSY as $freebusy) {
$fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
// Skipping intervals marked as 'free'
if ($fbType==='FREE')
continue;
$values = explode(',', $freebusy);
foreach($values as $value) {
list($startTime, $endTime) = explode('/', $value);
$startTime = DateTimeParser::parseDateTime($startTime);
if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
$duration = DateTimeParser::parseDuration($endTime);
$endTime = clone $startTime;
$endTime->add($duration);
} else {
$endTime = DateTimeParser::parseDateTime($endTime);
}
if($this->start && $this->start > $endTime) continue;
if($this->end && $this->end < $startTime) continue;
$busyTimes[] = array(
$startTime,
$endTime,
$fbType
);
}
}
break;
}
}
}
if ($this->baseObject) {
$calendar = $this->baseObject;
} else {
$calendar = Component::create('VCALENDAR');
$calendar->version = '2.0';
$calendar->prodid = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
$calendar->calscale = 'GREGORIAN';
}
$vfreebusy = Component::create('VFREEBUSY');
$calendar->add($vfreebusy);
if ($this->start) {
$dtstart = Property::create('DTSTART');
$dtstart->setDateTime($this->start,Property\DateTime::UTC);
$vfreebusy->add($dtstart);
}
if ($this->end) {
$dtend = Property::create('DTEND');
$dtend->setDateTime($this->end,Property\DateTime::UTC);
$vfreebusy->add($dtend);
}
$dtstamp = Property::create('DTSTAMP');
$dtstamp->setDateTime(new \DateTime('now'), Property\DateTime::UTC);
$vfreebusy->add($dtstamp);
foreach($busyTimes as $busyTime) {
$busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
$busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
$prop = Property::create(
'FREEBUSY',
$busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
);
$prop['FBTYPE'] = $busyTime[2];
$vfreebusy->add($prop);
}
return $calendar;
}
}

View file

@ -1,187 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* Base class for all nodes
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
/**
* The following constants are used by the validate() method.
*/
const REPAIR = 1;
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
abstract function serialize();
/**
* Iterator override
*
* @var ElementList
*/
protected $iterator = null;
/**
* A link to the parent node
*
* @var Node
*/
public $parent = null;
/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
* @return array
*/
public function validate($options = 0) {
return array();
}
/* {{{ IteratorAggregator interface */
/**
* Returns the iterator for this object
*
* @return ElementList
*/
public function getIterator() {
if (!is_null($this->iterator))
return $this->iterator;
return new ElementList(array($this));
}
/**
* Sets the overridden iterator
*
* Note that this is not actually part of the iterator interface
*
* @param ElementList $iterator
* @return void
*/
public function setIterator(ElementList $iterator) {
$this->iterator = $iterator;
}
/* }}} */
/* {{{ Countable interface */
/**
* Returns the number of elements
*
* @return int
*/
public function count() {
$it = $this->getIterator();
return $it->count();
}
/* }}} */
/* {{{ ArrayAccess Interface */
/**
* Checks if an item exists through ArrayAccess.
*
* This method just forwards the request to the inner iterator
*
* @param int $offset
* @return bool
*/
public function offsetExists($offset) {
$iterator = $this->getIterator();
return $iterator->offsetExists($offset);
}
/**
* Gets an item through ArrayAccess.
*
* This method just forwards the request to the inner iterator
*
* @param int $offset
* @return mixed
*/
public function offsetGet($offset) {
$iterator = $this->getIterator();
return $iterator->offsetGet($offset);
}
/**
* Sets an item through ArrayAccess.
*
* This method just forwards the request to the inner iterator
*
* @param int $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset,$value) {
$iterator = $this->getIterator();
$iterator->offsetSet($offset,$value);
// @codeCoverageIgnoreStart
//
// This method always throws an exception, so we ignore the closing
// brace
}
// @codeCoverageIgnoreEnd
/**
* Sets an item through ArrayAccess.
*
* This method just forwards the request to the inner iterator
*
* @param int $offset
* @return void
*/
public function offsetUnset($offset) {
$iterator = $this->getIterator();
$iterator->offsetUnset($offset);
// @codeCoverageIgnoreStart
//
// This method always throws an exception, so we ignore the closing
// brace
}
// @codeCoverageIgnoreEnd
/* }}} */
}

View file

@ -1,102 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* VObject Parameter
*
* This class represents a parameter. A parameter is always tied to a property.
* In the case of:
* DTSTART;VALUE=DATE:20101108
* VALUE=DATE would be the parameter name and value.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Parameter extends Node {
/**
* Parameter name
*
* @var string
*/
public $name;
/**
* Parameter value
*
* @var string
*/
public $value;
/**
* Sets up the object
*
* @param string $name
* @param string $value
*/
public function __construct($name, $value = null) {
if (!is_scalar($value) && !is_null($value)) {
throw new \InvalidArgumentException('The value argument must be a scalar value or null');
}
$this->name = strtoupper($name);
$this->value = $value;
}
/**
* Returns the parameter's internal value.
*
* @return string
*/
public function getValue() {
return $this->value;
}
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize() {
if (is_null($this->value)) {
return $this->name;
}
$src = array(
'\\',
"\n",
);
$out = array(
'\\\\',
'\n',
);
// quote parameters according to RFC 5545, Section 3.2
$quotes = '';
if (preg_match('/[:;,]/', $this->value)) {
$quotes = '"';
}
return $this->name . '=' . $quotes . str_replace($src, $out, $this->value) . $quotes;
}
/**
* Called when this object is being cast to a string
*
* @return string
*/
public function __toString() {
return $this->value;
}
}

View file

@ -1,12 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* Exception thrown by Reader if an invalid object was attempted to be parsed.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ParseException extends \Exception { }

View file

@ -1,453 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* VObject Property
*
* A property in VObject is usually in the form PARAMNAME:paramValue.
* An example is : SUMMARY:Weekly meeting
*
* Properties can also have parameters:
* SUMMARY;LANG=en:Weekly meeting.
*
* Parameters can be accessed using the ArrayAccess interface.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Property extends Node {
/**
* Propertyname
*
* @var string
*/
public $name;
/**
* Group name
*
* This may be something like 'HOME' for vcards.
*
* @var string
*/
public $group;
/**
* Property parameters
*
* @var array
*/
public $parameters = array();
/**
* Property value
*
* @var string
*/
public $value;
/**
* If properties are added to this map, they will be automatically mapped
* to their respective classes, if parsed by the reader or constructed with
* the 'create' method.
*
* @var array
*/
static public $classMap = array(
'COMPLETED' => 'Sabre\\VObject\\Property\\DateTime',
'CREATED' => 'Sabre\\VObject\\Property\\DateTime',
'DTEND' => 'Sabre\\VObject\\Property\\DateTime',
'DTSTAMP' => 'Sabre\\VObject\\Property\\DateTime',
'DTSTART' => 'Sabre\\VObject\\Property\\DateTime',
'DUE' => 'Sabre\\VObject\\Property\\DateTime',
'EXDATE' => 'Sabre\\VObject\\Property\\MultiDateTime',
'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\DateTime',
'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\DateTime',
'TRIGGER' => 'Sabre\\VObject\\Property\\DateTime',
'N' => 'Sabre\\VObject\\Property\\Compound',
'ORG' => 'Sabre\\VObject\\Property\\Compound',
'ADR' => 'Sabre\\VObject\\Property\\Compound',
'CATEGORIES' => 'Sabre\\VObject\\Property\\Compound',
);
/**
* Creates the new property by name, but in addition will also see if
* there's a class mapped to the property name.
*
* Parameters can be specified with the optional third argument. Parameters
* must be a key->value map of the parameter name, and value. If the value
* is specified as an array, it is assumed that multiple parameters with
* the same name should be added.
*
* @param string $name
* @param string $value
* @param array $parameters
* @return Property
*/
static public function create($name, $value = null, array $parameters = array()) {
$name = strtoupper($name);
$shortName = $name;
$group = null;
if (strpos($shortName,'.')!==false) {
list($group, $shortName) = explode('.', $shortName);
}
if (isset(self::$classMap[$shortName])) {
return new self::$classMap[$shortName]($name, $value, $parameters);
} else {
return new self($name, $value, $parameters);
}
}
/**
* Creates a new property object
*
* Parameters can be specified with the optional third argument. Parameters
* must be a key->value map of the parameter name, and value. If the value
* is specified as an array, it is assumed that multiple parameters with
* the same name should be added.
*
* @param string $name
* @param string $value
* @param array $parameters
*/
public function __construct($name, $value = null, array $parameters = array()) {
if (!is_scalar($value) && !is_null($value)) {
throw new \InvalidArgumentException('The value argument must be scalar or null');
}
$name = strtoupper($name);
$group = null;
if (strpos($name,'.')!==false) {
list($group, $name) = explode('.', $name);
}
$this->name = $name;
$this->group = $group;
$this->setValue($value);
foreach($parameters as $paramName => $paramValues) {
if (!is_array($paramValues)) {
$paramValues = array($paramValues);
}
foreach($paramValues as $paramValue) {
$this->add($paramName, $paramValue);
}
}
}
/**
* Updates the internal value
*
* @param string $value
* @return void
*/
public function setValue($value) {
$this->value = $value;
}
/**
* Returns the internal value
*
* @param string $value
* @return string
*/
public function getValue() {
return $this->value;
}
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize() {
$str = $this->name;
if ($this->group) $str = $this->group . '.' . $this->name;
foreach($this->parameters as $param) {
$str.=';' . $param->serialize();
}
$src = array(
'\\',
"\n",
"\r",
);
$out = array(
'\\\\',
'\n',
'',
);
// avoid double-escaping of \, and \; from Compound properties
if (method_exists($this, 'setParts')) {
$src[] = '\\\\,';
$out[] = '\\,';
$src[] = '\\\\;';
$out[] = '\\;';
}
$str.=':' . str_replace($src, $out, $this->value);
$out = '';
while(strlen($str)>0) {
if (strlen($str)>75) {
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
} else {
$out.=$str . "\r\n";
$str='';
break;
}
}
return $out;
}
/**
* Adds a new componenten or element
*
* You can call this method with the following syntaxes:
*
* add(Parameter $element)
* add(string $name, $value)
*
* The first version adds an Parameter
* The second adds a property as a string.
*
* @param mixed $item
* @param mixed $itemValue
* @return void
*/
public function add($item, $itemValue = null) {
if ($item instanceof Parameter) {
if (!is_null($itemValue)) {
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
}
$item->parent = $this;
$this->parameters[] = $item;
} elseif(is_string($item)) {
$parameter = new Parameter($item,$itemValue);
$parameter->parent = $this;
$this->parameters[] = $parameter;
} else {
throw new \InvalidArgumentException('The first argument must either be a Node a string');
}
}
/* ArrayAccess interface {{{ */
/**
* Checks if an array element exists
*
* @param mixed $name
* @return bool
*/
public function offsetExists($name) {
if (is_int($name)) return parent::offsetExists($name);
$name = strtoupper($name);
foreach($this->parameters as $parameter) {
if ($parameter->name == $name) return true;
}
return false;
}
/**
* Returns a parameter, or parameter list.
*
* @param string $name
* @return Node
*/
public function offsetGet($name) {
if (is_int($name)) return parent::offsetGet($name);
$name = strtoupper($name);
$result = array();
foreach($this->parameters as $parameter) {
if ($parameter->name == $name)
$result[] = $parameter;
}
if (count($result)===0) {
return null;
} elseif (count($result)===1) {
return $result[0];
} else {
$result[0]->setIterator(new ElementList($result));
return $result[0];
}
}
/**
* Creates a new parameter
*
* @param string $name
* @param mixed $value
* @return void
*/
public function offsetSet($name, $value) {
if (is_int($name)) parent::offsetSet($name, $value);
if (is_scalar($value)) {
if (!is_string($name))
throw new \InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.');
$this->offsetUnset($name);
$parameter = new Parameter($name, $value);
$parameter->parent = $this;
$this->parameters[] = $parameter;
} elseif ($value instanceof Parameter) {
if (!is_null($name))
throw new \InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a \\Sabre\\VObject\\Parameter. Add using $array[]=$parameterObject.');
$value->parent = $this;
$this->parameters[] = $value;
} else {
throw new \InvalidArgumentException('You can only add parameters to the property object');
}
}
/**
* Removes one or more parameters with the specified name
*
* @param string $name
* @return void
*/
public function offsetUnset($name) {
if (is_int($name)) parent::offsetUnset($name);
$name = strtoupper($name);
foreach($this->parameters as $key=>$parameter) {
if ($parameter->name == $name) {
$parameter->parent = null;
unset($this->parameters[$key]);
}
}
}
/* }}} */
/**
* Called when this object is being cast to a string
*
* @return string
*/
public function __toString() {
return (string)$this->value;
}
/**
* This method is automatically called when the object is cloned.
* Specifically, this will ensure all child elements are also cloned.
*
* @return void
*/
public function __clone() {
foreach($this->parameters as $key=>$child) {
$this->parameters[$key] = clone $child;
$this->parameters[$key]->parent = $this;
}
}
/**
* Validates the node for correctness.
*
* The following options are supported:
* - Node::REPAIR - If something is broken, and automatic repair may
* be attempted.
*
* An array is returned with warnings.
*
* Every item in the array has the following properties:
* * level - (number between 1 and 3 with severity information)
* * message - (human readable message)
* * node - (reference to the offending node)
*
* @param int $options
* @return array
*/
public function validate($options = 0) {
$warnings = array();
// Checking if our value is UTF-8
if (!StringUtil::isUTF8($this->value)) {
$warnings[] = array(
'level' => 1,
'message' => 'Property is not valid UTF-8!',
'node' => $this,
);
if ($options & self::REPAIR) {
$this->value = StringUtil::convertToUTF8($this->value);
}
}
// Checking if the propertyname does not contain any invalid bytes.
if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
$warnings[] = array(
'level' => 1,
'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
'node' => $this,
);
if ($options & self::REPAIR) {
// Uppercasing and converting underscores to dashes.
$this->name = strtoupper(
str_replace('_', '-', $this->name)
);
// Removing every other invalid character
$this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
}
}
// Validating inner parameters
foreach($this->parameters as $param) {
$warnings = array_merge($warnings, $param->validate($options));
}
return $warnings;
}
}

View file

@ -1,125 +0,0 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
/**
* Compound property.
*
* This class adds (de)serialization of compound properties to/from arrays.
*
* Currently the following properties from RFC 6350 are mapped to use this
* class:
*
* N: Section 6.2.2
* ADR: Section 6.3.1
* ORG: Section 6.6.4
* CATEGORIES: Section 6.7.1
*
* In order to use this correctly, you must call setParts and getParts to
* retrieve and modify dates respectively.
*
* @author Thomas Tanghus (http://tanghus.net/)
* @author Lars Kneschke
* @author Evert Pot (http://evertpot.com/)
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Compound extends VObject\Property {
/**
* If property names are added to this map, they will be (de)serialised as arrays
* using the getParts() and setParts() methods.
* The keys are the property names, values are delimiter chars.
*
* @var array
*/
static public $delimiterMap = array(
'N' => ';',
'ADR' => ';',
'ORG' => ';',
'CATEGORIES' => ',',
);
/**
* The currently used delimiter.
*
* @var string
*/
protected $delimiter = null;
/**
* Get a compound value as an array.
*
* @param $name string
* @return array
*/
public function getParts() {
if (is_null($this->value)) {
return array();
}
$delimiter = $this->getDelimiter();
// split by any $delimiter which is NOT prefixed by a slash.
// Note that this is not a a perfect solution. If a value is prefixed
// by two slashes, it should actually be split anyway.
//
// Hopefully we can fix this better in a future version, where we can
// break compatibility a bit.
$compoundValues = preg_split("/(?<!\\\)$delimiter/", $this->value);
// remove slashes from any semicolon and comma left escaped in the single values
$compoundValues = array_map(
function($val) {
return strtr($val, array('\,' => ',', '\;' => ';'));
}, $compoundValues);
return $compoundValues;
}
/**
* Returns the delimiter for this property.
*
* @return string
*/
public function getDelimiter() {
if (!$this->delimiter) {
if (isset(self::$delimiterMap[$this->name])) {
$this->delimiter = self::$delimiterMap[$this->name];
} else {
// To be a bit future proof, we are going to default the
// delimiter to ;
$this->delimiter = ';';
}
}
return $this->delimiter;
}
/**
* Set a compound value as an array.
*
*
* @param $name string
* @return array
*/
public function setParts(array $values) {
// add slashes to all semicolons and commas in the single values
$values = array_map(
function($val) {
return strtr($val, array(',' => '\,', ';' => '\;'));
}, $values);
$this->setValue(
implode($this->getDelimiter(), $values)
);
}
}

View file

@ -1,245 +0,0 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
/**
* DateTime property
*
* This element is used for iCalendar properties such as the DTSTART property.
* It basically provides a few helper functions that make it easier to deal
* with these. It supports both DATE-TIME and DATE values.
*
* In order to use this correctly, you must call setDateTime and getDateTime to
* retrieve and modify dates respectively.
*
* If you use the 'value' or properties directly, this object does not keep
* reference and results might appear incorrectly.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class DateTime extends VObject\Property {
/**
* Local 'floating' time
*/
const LOCAL = 1;
/**
* UTC-based time
*/
const UTC = 2;
/**
* Local time plus timezone
*/
const LOCALTZ = 3;
/**
* Only a date, time is ignored
*/
const DATE = 4;
/**
* DateTime representation
*
* @var \DateTime
*/
protected $dateTime;
/**
* dateType
*
* @var int
*/
protected $dateType;
/**
* Updates the Date and Time.
*
* @param \DateTime $dt
* @param int $dateType
* @return void
*/
public function setDateTime(\DateTime $dt, $dateType = self::LOCALTZ) {
switch($dateType) {
case self::LOCAL :
$this->setValue($dt->format('Ymd\\THis'));
$this->offsetUnset('VALUE');
$this->offsetUnset('TZID');
$this->offsetSet('VALUE','DATE-TIME');
break;
case self::UTC :
$dt->setTimeZone(new \DateTimeZone('UTC'));
$this->setValue($dt->format('Ymd\\THis\\Z'));
$this->offsetUnset('VALUE');
$this->offsetUnset('TZID');
$this->offsetSet('VALUE','DATE-TIME');
break;
case self::LOCALTZ :
$this->setValue($dt->format('Ymd\\THis'));
$this->offsetUnset('VALUE');
$this->offsetUnset('TZID');
$this->offsetSet('VALUE','DATE-TIME');
$this->offsetSet('TZID', $dt->getTimeZone()->getName());
break;
case self::DATE :
$this->setValue($dt->format('Ymd'));
$this->offsetUnset('VALUE');
$this->offsetUnset('TZID');
$this->offsetSet('VALUE','DATE');
break;
default :
throw new \InvalidArgumentException('You must pass a valid dateType constant');
}
$this->dateTime = $dt;
$this->dateType = $dateType;
}
/**
* Returns the current DateTime value.
*
* If no value was set, this method returns null.
*
* @return \DateTime|null
*/
public function getDateTime() {
if ($this->dateTime)
return $this->dateTime;
list(
$this->dateType,
$this->dateTime
) = self::parseData($this->value, $this);
return $this->dateTime;
}
/**
* Returns the type of Date format.
*
* This method returns one of the format constants. If no date was set,
* this method will return null.
*
* @return int|null
*/
public function getDateType() {
if ($this->dateType)
return $this->dateType;
list(
$this->dateType,
$this->dateTime,
) = self::parseData($this->value, $this);
return $this->dateType;
}
/**
* This method will return true, if the property had a date and a time, as
* opposed to only a date.
*
* @return bool
*/
public function hasTime() {
return $this->getDateType()!==self::DATE;
}
/**
* Parses the internal data structure to figure out what the current date
* and time is.
*
* The returned array contains two elements:
* 1. A 'DateType' constant (as defined on this class), or null.
* 2. A DateTime object (or null)
*
* @param string|null $propertyValue The string to parse (yymmdd or
* ymmddThhmmss, etc..)
* @param \Sabre\VObject\Property|null $property The instance of the
* property we're parsing.
* @return array
*/
static public function parseData($propertyValue, VObject\Property $property = null) {
if (is_null($propertyValue)) {
return array(null, null);
}
$date = '(?P<year>[1-2][0-9]{3})(?P<month>[0-1][0-9])(?P<date>[0-3][0-9])';
$time = '(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9])';
$regex = "/^$date(T$time(?P<isutc>Z)?)?$/";
if (!preg_match($regex, $propertyValue, $matches)) {
throw new \InvalidArgumentException($propertyValue . ' is not a valid \DateTime or Date string');
}
if (!isset($matches['hour'])) {
// Date-only
return array(
self::DATE,
new \DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00', new \DateTimeZone('UTC')),
);
}
$dateStr =
$matches['year'] .'-' .
$matches['month'] . '-' .
$matches['date'] . ' ' .
$matches['hour'] . ':' .
$matches['minute'] . ':' .
$matches['second'];
if (isset($matches['isutc'])) {
$dt = new \DateTime($dateStr,new \DateTimeZone('UTC'));
$dt->setTimeZone(new \DateTimeZone('UTC'));
return array(
self::UTC,
$dt
);
}
// Finding the timezone.
$tzid = $property['TZID'];
if (!$tzid) {
// This was a floating time string. This implies we use the
// timezone from date_default_timezone_set / date.timezone ini
// setting.
return array(
self::LOCAL,
new \DateTime($dateStr)
);
}
// To look up the timezone, we must first find the VCALENDAR component.
$root = $property;
while($root->parent) {
$root = $root->parent;
}
if ($root->name === 'VCALENDAR') {
$tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid, $root);
} else {
$tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid);
}
$dt = new \DateTime($dateStr, $tz);
$dt->setTimeZone($tz);
return array(
self::LOCALTZ,
$dt
);
}
}

View file

@ -1,180 +0,0 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject;
/**
* Multi-DateTime property
*
* This element is used for iCalendar properties such as the EXDATE property.
* It basically provides a few helper functions that make it easier to deal
* with these. It supports both DATE-TIME and DATE values.
*
* In order to use this correctly, you must call setDateTimes and getDateTimes
* to retrieve and modify dates respectively.
*
* If you use the 'value' or properties directly, this object does not keep
* reference and results might appear incorrectly.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class MultiDateTime extends VObject\Property {
/**
* DateTime representation
*
* @var DateTime[]
*/
protected $dateTimes;
/**
* dateType
*
* This is one of the Sabre\VObject\Property\DateTime constants.
*
* @var int
*/
protected $dateType;
/**
* Updates the value
*
* @param array $dt Must be an array of DateTime objects.
* @param int $dateType
* @return void
*/
public function setDateTimes(array $dt, $dateType = VObject\Property\DateTime::LOCALTZ) {
foreach($dt as $i)
if (!$i instanceof \DateTime)
throw new \InvalidArgumentException('You must pass an array of DateTime objects');
$this->offsetUnset('VALUE');
$this->offsetUnset('TZID');
switch($dateType) {
case DateTime::LOCAL :
$val = array();
foreach($dt as $i) {
$val[] = $i->format('Ymd\\THis');
}
$this->setValue(implode(',',$val));
$this->offsetSet('VALUE','DATE-TIME');
break;
case DateTime::UTC :
$val = array();
foreach($dt as $i) {
$i->setTimeZone(new \DateTimeZone('UTC'));
$val[] = $i->format('Ymd\\THis\\Z');
}
$this->setValue(implode(',',$val));
$this->offsetSet('VALUE','DATE-TIME');
break;
case DateTime::LOCALTZ :
$val = array();
foreach($dt as $i) {
$val[] = $i->format('Ymd\\THis');
}
$this->setValue(implode(',',$val));
$this->offsetSet('VALUE','DATE-TIME');
$this->offsetSet('TZID', $dt[0]->getTimeZone()->getName());
break;
case DateTime::DATE :
$val = array();
foreach($dt as $i) {
$val[] = $i->format('Ymd');
}
$this->setValue(implode(',',$val));
$this->offsetSet('VALUE','DATE');
break;
default :
throw new \InvalidArgumentException('You must pass a valid dateType constant');
}
$this->dateTimes = $dt;
$this->dateType = $dateType;
}
/**
* Returns the current DateTime value.
*
* If no value was set, this method returns null.
*
* @return array|null
*/
public function getDateTimes() {
if ($this->dateTimes)
return $this->dateTimes;
$dts = array();
if (!$this->value) {
$this->dateTimes = null;
$this->dateType = null;
return null;
}
foreach(explode(',',$this->value) as $val) {
list(
$type,
$dt
) = DateTime::parseData($val, $this);
$dts[] = $dt;
$this->dateType = $type;
}
$this->dateTimes = $dts;
return $this->dateTimes;
}
/**
* Returns the type of Date format.
*
* This method returns one of the format constants. If no date was set,
* this method will return null.
*
* @return int|null
*/
public function getDateType() {
if ($this->dateType)
return $this->dateType;
if (!$this->value) {
$this->dateTimes = null;
$this->dateType = null;
return null;
}
$dts = array();
foreach(explode(',',$this->value) as $val) {
list(
$type,
$dt
) = DateTime::parseData($val, $this);
$dts[] = $dt;
$this->dateType = $type;
}
$this->dateTimes = $dts;
return $this->dateType;
}
/**
* This method will return true, if the property had a date and a time, as
* opposed to only a date.
*
* @return bool
*/
public function hasTime() {
return $this->getDateType()!==DateTime::DATE;
}
}

View file

@ -1,223 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* VCALENDAR/VCARD reader
*
* This class reads the vobject file, and returns a full element tree.
*
* TODO: this class currently completely works 'statically'. This is pointless,
* and defeats OOP principals. Needs refactoring in a future version.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Reader {
/**
* If this option is passed to the reader, it will be less strict about the
* validity of the lines.
*
* Currently using this option just means, that it will accept underscores
* in property names.
*/
const OPTION_FORGIVING = 1;
/**
* If this option is turned on, any lines we cannot parse will be ignored
* by the reader.
*/
const OPTION_IGNORE_INVALID_LINES = 2;
/**
* Parses the file and returns the top component
*
* The options argument is a bitfield. Pass any of the OPTIONS constant to
* alter the parsers' behaviour.
*
* @param string $data
* @param int $options
* @return Node
*/
static function read($data, $options = 0) {
// Normalizing newlines
$data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
$lines = explode("\n", $data);
// Unfolding lines
$lines2 = array();
foreach($lines as $line) {
// Skipping empty lines
if (!$line) continue;
if ($line[0]===" " || $line[0]==="\t") {
$lines2[count($lines2)-1].=substr($line,1);
} else {
$lines2[] = $line;
}
}
unset($lines);
reset($lines2);
return self::readLine($lines2, $options);
}
/**
* Reads and parses a single line.
*
* This method receives the full array of lines. The array pointer is used
* to traverse.
*
* This method returns null if an invalid line was encountered, and the
* IGNORE_INVALID_LINES option was turned on.
*
* @param array $lines
* @param int $options See the OPTIONS constants.
* @return Node
*/
static private function readLine(&$lines, $options = 0) {
$line = current($lines);
$lineNr = key($lines);
next($lines);
// Components
if (strtoupper(substr($line,0,6)) === "BEGIN:") {
$componentName = strtoupper(substr($line,6));
$obj = Component::create($componentName);
$nextLine = current($lines);
while(strtoupper(substr($nextLine,0,4))!=="END:") {
$parsedLine = self::readLine($lines, $options);
$nextLine = current($lines);
if (is_null($parsedLine)) {
continue;
}
$obj->add($parsedLine);
if ($nextLine===false)
throw new ParseException('Invalid VObject. Document ended prematurely.');
}
// Checking component name of the 'END:' line.
if (substr($nextLine,4)!==$obj->name) {
throw new ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"');
}
next($lines);
return $obj;
}
// Properties
//$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches);
if ($options & self::OPTION_FORGIVING) {
$token = '[A-Z0-9-\._]+';
} else {
$token = '[A-Z0-9-\.]+';
}
$parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
$regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
$result = preg_match($regex,$line,$matches);
if (!$result) {
if ($options & self::OPTION_IGNORE_INVALID_LINES) {
return null;
} else {
throw new ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format');
}
}
$propertyName = strtoupper($matches['name']);
$propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n))#',function($matches) {
if ($matches[2]==='n' || $matches[2]==='N') {
return "\n";
} else {
return $matches[2];
}
}, $matches['value']);
$obj = Property::create($propertyName, $propertyValue);
if ($matches['parameters']) {
foreach(self::readParameters($matches['parameters']) as $param) {
$obj->add($param);
}
}
return $obj;
}
/**
* Reads a parameter list from a property
*
* This method returns an array of Parameter
*
* @param string $parameters
* @return array
*/
static private function readParameters($parameters) {
$token = '[A-Z0-9-]+';
$paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")';
$regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
preg_match_all($regex, $parameters, $matches, PREG_SET_ORDER);
$params = array();
foreach($matches as $match) {
if (!isset($match['paramValue'])) {
$value = null;
} else {
$value = $match['paramValue'];
if (isset($value[0]) && $value[0]==='"') {
// Stripping quotes, if needed
$value = substr($value,1,strlen($value)-2);
}
$value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
if ($matches[2]==='n' || $matches[2]==='N') {
return "\n";
} else {
return $matches[2];
}
}, $value);
}
$params[] = new Parameter($match['paramName'], $value);
}
return $params;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,111 +0,0 @@
<?php
namespace Sabre\VObject\Splitter;
use Sabre\VObject;
/**
* Splitter
*
* This class is responsible for splitting up iCalendar objects.
*
* This class expects a single VCALENDAR object with one or more
* calendar-objects inside. Objects with identical UID's will be combined into
* a single object.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Dominik Tobschall
* @author Armin Hackmann
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ICalendar implements SplitterInterface {
/**
* Timezones
*
* @var array
*/
protected $vtimezones = array();
/**
* iCalendar objects
*
* @var array
*/
protected $objects = array();
/**
* Constructor
*
* The splitter should receive an readable file stream as it's input.
*
* @param resource $input
*/
public function __construct($input) {
$data = VObject\Reader::read(stream_get_contents($input));
$vtimezones = array();
$components = array();
foreach($data->children as $component) {
if (!$component instanceof VObject\Component) {
continue;
}
// Get all timezones
if ($component->name === 'VTIMEZONE') {
$this->vtimezones[(string)$component->TZID] = $component;
continue;
}
// Get component UID for recurring Events search
if($component->UID) {
$uid = (string)$component->UID;
} else {
// Generating a random UID
$uid = sha1(microtime()) . '-vobjectimport';
}
// Take care of recurring events
if (!array_key_exists($uid, $this->objects)) {
$this->objects[$uid] = VObject\Component::create('VCALENDAR');
}
$this->objects[$uid]->add(clone $component);
}
}
/**
* Every time getNext() is called, a new object will be parsed, until we
* hit the end of the stream.
*
* When the end is reached, null will be returned.
*
* @return Sabre\VObject\Component|null
*/
public function getNext() {
if($object=array_shift($this->objects)) {
// create our baseobject
$object->version = '2.0';
$object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
$object->calscale = 'GREGORIAN';
// add vtimezone information to obj (if we have it)
foreach ($this->vtimezones as $vtimezone) {
$object->add($vtimezone);
}
return $object;
} else {
return null;
}
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace Sabre\VObject\Splitter;
/**
* VObject splitter
*
* The splitter is responsible for reading a large vCard or iCalendar object,
* and splitting it into multiple objects.
*
* This is for example for Card and CalDAV, which require every event and vcard
* to exist in their own objects, instead of one large one.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Dominik Tobschall
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface SplitterInterface {
/**
* Constructor
*
* The splitter should receive an readable file stream as it's input.
*
* @param resource $input
*/
function __construct($input);
/**
* Every time getNext() is called, a new object will be parsed, until we
* hit the end of the stream.
*
* When the end is reached, null will be returned.
*
* @return Sabre\VObject\Component|null
*/
function getNext();
}

View file

@ -1,76 +0,0 @@
<?php
namespace Sabre\VObject\Splitter;
use Sabre\VObject;
/**
* Splitter
*
* This class is responsible for splitting up VCard objects.
*
* It is assumed that the input stream contains 1 or more VCARD objects. This
* class checks for BEGIN:VCARD and END:VCARD and parses each encountered
* component individually.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Dominik Tobschall
* @author Armin Hackmann
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCard implements SplitterInterface {
/**
* File handle
*
* @var resource
*/
protected $input;
/**
* Constructor
*
* The splitter should receive an readable file stream as it's input.
*
* @param resource $input
*/
public function __construct($input) {
$this->input = $input;
}
/**
* Every time getNext() is called, a new object will be parsed, until we
* hit the end of the stream.
*
* When the end is reached, null will be returned.
*
* @return Sabre\VObject\Component|null
*/
public function getNext() {
$vcard = '';
do {
if (feof($this->input)) {
return false;
}
$line = fgets($this->input);
$vcard .= $line;
} while(strtoupper(substr($line,0,4))!=="END:");
$object = VObject\Reader::read($vcard);
if($object->name !== 'VCARD') {
throw new \InvalidArgumentException("Thats no vCard!", 1);
}
return $object;
}
}

View file

@ -1,61 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* Useful utilities for working with various strings.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class StringUtil {
/**
* Returns true or false depending on if a string is valid UTF-8
*
* @param string $str
* @return bool
*/
static function isUTF8($str) {
// First check.. mb_check_encoding
if (!mb_check_encoding($str, 'UTF-8')) {
return false;
}
// Control characters
if (preg_match('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', $str)) {
return false;
}
return true;
}
/**
* This method tries its best to convert the input string to UTF-8.
*
* Currently only ISO-5991-1 input and UTF-8 input is supported, but this
* may be expanded upon if we receive other examples.
*
* @param string $str
* @return string
*/
static function convertToUTF8($str) {
$encoding = mb_detect_encoding($str , array('UTF-8','ISO-8859-1'), true);
if ($encoding === 'ISO-8859-1') {
$newStr = utf8_encode($str);
} else {
$newStr = $str;
}
// Removing any control characters
return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E\x0F])%', '', $newStr));
}
}

View file

@ -1,482 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* Time zone name translation
*
* This file translates well-known time zone names into "Olson database" time zone names.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Frank Edelhaeuser (fedel@users.sourceforge.net)
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class TimeZoneUtil {
public static $map = array(
// from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
// snapshot taken on 2012/01/16
// windows
'AUS Central Standard Time'=>'Australia/Darwin',
'AUS Eastern Standard Time'=>'Australia/Sydney',
'Afghanistan Standard Time'=>'Asia/Kabul',
'Alaskan Standard Time'=>'America/Anchorage',
'Arab Standard Time'=>'Asia/Riyadh',
'Arabian Standard Time'=>'Asia/Dubai',
'Arabic Standard Time'=>'Asia/Baghdad',
'Argentina Standard Time'=>'America/Buenos_Aires',
'Armenian Standard Time'=>'Asia/Yerevan',
'Atlantic Standard Time'=>'America/Halifax',
'Azerbaijan Standard Time'=>'Asia/Baku',
'Azores Standard Time'=>'Atlantic/Azores',
'Bangladesh Standard Time'=>'Asia/Dhaka',
'Canada Central Standard Time'=>'America/Regina',
'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
'Caucasus Standard Time'=>'Asia/Yerevan',
'Cen. Australia Standard Time'=>'Australia/Adelaide',
'Central America Standard Time'=>'America/Guatemala',
'Central Asia Standard Time'=>'Asia/Almaty',
'Central Brazilian Standard Time'=>'America/Cuiaba',
'Central Europe Standard Time'=>'Europe/Budapest',
'Central European Standard Time'=>'Europe/Warsaw',
'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
'Central Standard Time'=>'America/Chicago',
'Central Standard Time (Mexico)'=>'America/Mexico_City',
'China Standard Time'=>'Asia/Shanghai',
'Dateline Standard Time'=>'Etc/GMT+12',
'E. Africa Standard Time'=>'Africa/Nairobi',
'E. Australia Standard Time'=>'Australia/Brisbane',
'E. Europe Standard Time'=>'Europe/Minsk',
'E. South America Standard Time'=>'America/Sao_Paulo',
'Eastern Standard Time'=>'America/New_York',
'Egypt Standard Time'=>'Africa/Cairo',
'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
'FLE Standard Time'=>'Europe/Kiev',
'Fiji Standard Time'=>'Pacific/Fiji',
'GMT Standard Time'=>'Europe/London',
'GTB Standard Time'=>'Europe/Istanbul',
'Georgian Standard Time'=>'Asia/Tbilisi',
'Greenland Standard Time'=>'America/Godthab',
'Greenwich Standard Time'=>'Atlantic/Reykjavik',
'Hawaiian Standard Time'=>'Pacific/Honolulu',
'India Standard Time'=>'Asia/Calcutta',
'Iran Standard Time'=>'Asia/Tehran',
'Israel Standard Time'=>'Asia/Jerusalem',
'Jordan Standard Time'=>'Asia/Amman',
'Kamchatka Standard Time'=>'Asia/Kamchatka',
'Korea Standard Time'=>'Asia/Seoul',
'Magadan Standard Time'=>'Asia/Magadan',
'Mauritius Standard Time'=>'Indian/Mauritius',
'Mexico Standard Time'=>'America/Mexico_City',
'Mexico Standard Time 2'=>'America/Chihuahua',
'Mid-Atlantic Standard Time'=>'Etc/GMT-2',
'Middle East Standard Time'=>'Asia/Beirut',
'Montevideo Standard Time'=>'America/Montevideo',
'Morocco Standard Time'=>'Africa/Casablanca',
'Mountain Standard Time'=>'America/Denver',
'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
'Myanmar Standard Time'=>'Asia/Rangoon',
'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
'Namibia Standard Time'=>'Africa/Windhoek',
'Nepal Standard Time'=>'Asia/Katmandu',
'New Zealand Standard Time'=>'Pacific/Auckland',
'Newfoundland Standard Time'=>'America/St_Johns',
'North Asia East Standard Time'=>'Asia/Irkutsk',
'North Asia Standard Time'=>'Asia/Krasnoyarsk',
'Pacific SA Standard Time'=>'America/Santiago',
'Pacific Standard Time'=>'America/Los_Angeles',
'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
'Pakistan Standard Time'=>'Asia/Karachi',
'Paraguay Standard Time'=>'America/Asuncion',
'Romance Standard Time'=>'Europe/Paris',
'Russian Standard Time'=>'Europe/Moscow',
'SA Eastern Standard Time'=>'America/Cayenne',
'SA Pacific Standard Time'=>'America/Bogota',
'SA Western Standard Time'=>'America/La_Paz',
'SE Asia Standard Time'=>'Asia/Bangkok',
'Samoa Standard Time'=>'Pacific/Apia',
'Singapore Standard Time'=>'Asia/Singapore',
'South Africa Standard Time'=>'Africa/Johannesburg',
'Sri Lanka Standard Time'=>'Asia/Colombo',
'Syria Standard Time'=>'Asia/Damascus',
'Taipei Standard Time'=>'Asia/Taipei',
'Tasmania Standard Time'=>'Australia/Hobart',
'Tokyo Standard Time'=>'Asia/Tokyo',
'Tonga Standard Time'=>'Pacific/Tongatapu',
'US Eastern Standard Time'=>'America/Indianapolis',
'US Mountain Standard Time'=>'America/Phoenix',
'UTC+12'=>'Etc/GMT-12',
'UTC-02'=>'Etc/GMT+2',
'UTC-11'=>'Etc/GMT+11',
'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
'Venezuela Standard Time'=>'America/Caracas',
'Vladivostok Standard Time'=>'Asia/Vladivostok',
'W. Australia Standard Time'=>'Australia/Perth',
'W. Central Africa Standard Time'=>'Africa/Lagos',
'W. Europe Standard Time'=>'Europe/Berlin',
'West Asia Standard Time'=>'Asia/Tashkent',
'West Pacific Standard Time'=>'Pacific/Port_Moresby',
'Yakutsk Standard Time'=>'Asia/Yakutsk',
// Microsoft exchange timezones
// Source:
// http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
//
// Correct timezones deduced with help from:
// http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
'Universal Coordinated Time' => 'UTC',
'Casablanca, Monrovia' => 'Africa/Casablanca',
'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
'Prague, Central Europe' => 'Europe/Prague',
'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
'West Central Africa' => 'Africa/Luanda', // This was a best guess
'Athens, Istanbul, Minsk' => 'Europe/Athens',
'Bucharest' => 'Europe/Bucharest',
'Cairo' => 'Africa/Cairo',
'Harare, Pretoria' => 'Africa/Harare',
'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
'Baghdad' => 'Asia/Baghdad',
'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
'East Africa, Nairobi' => 'Africa/Nairobi',
'Tehran' => 'Asia/Tehran',
'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
'Kabul' => 'Asia/Kabul',
'Ekaterinburg' => 'Asia/Yekaterinburg',
'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
'Kathmandu, Nepal' => 'Asia/Kathmandu',
'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
'Astana, Dhaka' => 'Asia/Dhaka',
'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
'Rangoon' => 'Asia/Rangoon',
'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
'Krasnoyarsk' => 'Asia/Krasnoyarsk',
'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
'Kuala Lumpur, Singapore' => 'Asia/Singapore',
'Perth, Western Australia' => 'Australia/Perth',
'Taipei' => 'Asia/Taipei',
'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
'Seoul, Korea Standard time' => 'Asia/Seoul',
'Yakutsk' => 'Asia/Yakutsk',
'Adelaide, Central Australia' => 'Australia/Adelaide',
'Darwin' => 'Australia/Darwin',
'Brisbane, East Australia' => 'Australia/Brisbane',
'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
'Guam, Port Moresby' => 'Pacific/Guam',
'Hobart, Tasmania' => 'Australia/Hobart',
'Vladivostok' => 'Asia/Vladivostok',
'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
'Auckland, Wellington' => 'Pacific/Auckland',
'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
'Azores' => 'Atlantic/Azores',
'Cape Verde Is.' => 'Atlantic/Cape_Verde',
'Mid-Atlantic' => 'America/Noronha',
'Brasilia' => 'America/Sao_Paulo', // Best guess
'Buenos Aires' => 'America/Argentina/Buenos_Aires',
'Greenland' => 'America/Godthab',
'Newfoundland' => 'America/St_Johns',
'Atlantic Time (Canada)' => 'America/Halifax',
'Caracas, La Paz' => 'America/Caracas',
'Santiago' => 'America/Santiago',
'Bogota, Lima, Quito' => 'America/Bogota',
'Eastern Time (US & Canada)' => 'America/New_York',
'Indiana (East)' => 'America/Indiana/Indianapolis',
'Central America' => 'America/Guatemala',
'Central Time (US & Canada)' => 'America/Chicago',
'Mexico City, Tegucigalpa' => 'America/Mexico_City',
'Saskatchewan' => 'America/Edmonton',
'Arizona' => 'America/Phoenix',
'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
'Alaska' => 'America/Anchorage',
'Hawaii' => 'Pacific/Honolulu',
'Midway Island, Samoa' => 'Pacific/Midway',
'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
// The following list are timezone names that could be generated by
// Lotus / Domino
'Dateline' => 'Etc/GMT-12',
'Samoa' => 'Pacific/Apia',
'Hawaiian' => 'Pacific/Honolulu',
'Alaskan' => 'America/Anchorage',
'Pacific' => 'America/Los_Angeles',
'Pacific Standard Time' => 'America/Los_Angeles',
'Mexico Standard Time 2' => 'America/Chihuahua',
'Mountain' => 'America/Denver',
'Mountain Standard Time' => 'America/Chihuahua',
'US Mountain' => 'America/Phoenix',
'Canada Central' => 'America/Edmonton',
'Central America' => 'America/Guatemala',
'Central' => 'America/Chicago',
'Central Standard Time' => 'America/Mexico_City',
'Mexico' => 'America/Mexico_City',
'Eastern' => 'America/New_York',
'SA Pacific' => 'America/Bogota',
'US Eastern' => 'America/Indiana/Indianapolis',
'Venezuela' => 'America/Caracas',
'Atlantic' => 'America/Halifax',
'Central Brazilian' => 'America/Manaus',
'Pacific SA' => 'America/Santiago',
'SA Western' => 'America/La_Paz',
'Newfoundland' => 'America/St_Johns',
'Argentina' => 'America/Argentina/Buenos_Aires',
'E. South America' => 'America/Belem',
'Greenland' => 'America/Godthab',
'Montevideo' => 'America/Montevideo',
'SA Eastern' => 'America/Belem',
'Mid-Atlantic' => 'Etc/GMT-2',
'Azores' => 'Atlantic/Azores',
'Cape Verde' => 'Atlantic/Cape_Verde',
'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
'Morocco' => 'Africa/Casablanca',
'Central Europe' => 'Europe/Prague',
'Central European' => 'Europe/Sarajevo',
'Romance' => 'Europe/Paris',
'W. Central Africa' => 'Africa/Lagos', // Best guess
'W. Europe' => 'Europe/Amsterdam',
'E. Europe' => 'Europe/Minsk',
'Egypt' => 'Africa/Cairo',
'FLE' => 'Europe/Helsinki',
'GTB' => 'Europe/Athens',
'Israel' => 'Asia/Jerusalem',
'Jordan' => 'Asia/Amman',
'Middle East' => 'Asia/Beirut',
'Namibia' => 'Africa/Windhoek',
'South Africa' => 'Africa/Harare',
'Arab' => 'Asia/Kuwait',
'Arabic' => 'Asia/Baghdad',
'E. Africa' => 'Africa/Nairobi',
'Georgian' => 'Asia/Tbilisi',
'Russian' => 'Europe/Moscow',
'Iran' => 'Asia/Tehran',
'Arabian' => 'Asia/Muscat',
'Armenian' => 'Asia/Yerevan',
'Azerbijan' => 'Asia/Baku',
'Caucasus' => 'Asia/Yerevan',
'Mauritius' => 'Indian/Mauritius',
'Afghanistan' => 'Asia/Kabul',
'Ekaterinburg' => 'Asia/Yekaterinburg',
'Pakistan' => 'Asia/Karachi',
'West Asia' => 'Asia/Tashkent',
'India' => 'Asia/Calcutta',
'Sri Lanka' => 'Asia/Colombo',
'Nepal' => 'Asia/Kathmandu',
'Central Asia' => 'Asia/Dhaka',
'N. Central Asia' => 'Asia/Almaty',
'Myanmar' => 'Asia/Rangoon',
'North Asia' => 'Asia/Krasnoyarsk',
'SE Asia' => 'Asia/Bangkok',
'China' => 'Asia/Shanghai',
'North Asia East' => 'Asia/Irkutsk',
'Singapore' => 'Asia/Singapore',
'Taipei' => 'Asia/Taipei',
'W. Australia' => 'Australia/Perth',
'Korea' => 'Asia/Seoul',
'Tokyo' => 'Asia/Tokyo',
'Yakutsk' => 'Asia/Yakutsk',
'AUS Central' => 'Australia/Darwin',
'Cen. Australia' => 'Australia/Adelaide',
'AUS Eastern' => 'Australia/Sydney',
'E. Australia' => 'Australia/Brisbane',
'Tasmania' => 'Australia/Hobart',
'Vladivostok' => 'Asia/Vladivostok',
'West Pacific' => 'Pacific/Guam',
'Central Pacific' => 'Asia/Magadan',
'Fiji' => 'Pacific/Fiji',
'New Zealand' => 'Pacific/Auckland',
'Tonga' => 'Pacific/Tongatapu',
);
/**
* List of microsoft exchange timezone ids.
*
* Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
*/
public static $microsoftExchangeMap = array(
0 => 'UTC',
31 => 'Africa/Casablanca',
// Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
// I'm not even kidding.. We handle this special case in the
// getTimeZone method.
2 => 'Europe/Lisbon',
1 => 'Europe/London',
4 => 'Europe/Berlin',
6 => 'Europe/Prague',
3 => 'Europe/Paris',
69 => 'Africa/Luanda', // This was a best guess
7 => 'Europe/Athens',
5 => 'Europe/Bucharest',
49 => 'Africa/Cairo',
50 => 'Africa/Harare',
59 => 'Europe/Helsinki',
27 => 'Asia/Jerusalem',
26 => 'Asia/Baghdad',
74 => 'Asia/Kuwait',
51 => 'Europe/Moscow',
56 => 'Africa/Nairobi',
25 => 'Asia/Tehran',
24 => 'Asia/Muscat', // Best guess
54 => 'Asia/Baku',
48 => 'Asia/Kabul',
58 => 'Asia/Yekaterinburg',
47 => 'Asia/Karachi',
23 => 'Asia/Calcutta',
62 => 'Asia/Kathmandu',
46 => 'Asia/Almaty',
71 => 'Asia/Dhaka',
66 => 'Asia/Colombo',
61 => 'Asia/Rangoon',
22 => 'Asia/Bangkok',
64 => 'Asia/Krasnoyarsk',
45 => 'Asia/Shanghai',
63 => 'Asia/Irkutsk',
21 => 'Asia/Singapore',
73 => 'Australia/Perth',
75 => 'Asia/Taipei',
20 => 'Asia/Tokyo',
72 => 'Asia/Seoul',
70 => 'Asia/Yakutsk',
19 => 'Australia/Adelaide',
44 => 'Australia/Darwin',
18 => 'Australia/Brisbane',
76 => 'Australia/Sydney',
43 => 'Pacific/Guam',
42 => 'Australia/Hobart',
68 => 'Asia/Vladivostok',
41 => 'Asia/Magadan',
17 => 'Pacific/Auckland',
40 => 'Pacific/Fiji',
67 => 'Pacific/Tongatapu',
29 => 'Atlantic/Azores',
53 => 'Atlantic/Cape_Verde',
30 => 'America/Noronha',
8 => 'America/Sao_Paulo', // Best guess
32 => 'America/Argentina/Buenos_Aires',
60 => 'America/Godthab',
28 => 'America/St_Johns',
9 => 'America/Halifax',
33 => 'America/Caracas',
65 => 'America/Santiago',
35 => 'America/Bogota',
10 => 'America/New_York',
34 => 'America/Indiana/Indianapolis',
55 => 'America/Guatemala',
11 => 'America/Chicago',
37 => 'America/Mexico_City',
36 => 'America/Edmonton',
38 => 'America/Phoenix',
12 => 'America/Denver', // Best guess
13 => 'America/Los_Angeles', // Best guess
14 => 'America/Anchorage',
15 => 'Pacific/Honolulu',
16 => 'Pacific/Midway',
39 => 'Pacific/Kwajalein',
);
/**
* This method will try to find out the correct timezone for an iCalendar
* date-time value.
*
* You must pass the contents of the TZID parameter, as well as the full
* calendar.
*
* If the lookup fails, this method will return the default PHP timezone
* (as configured using date_default_timezone_set, or the date.timezone ini
* setting).
*
* Alternatively, if $failIfUncertain is set to true, it will throw an
* exception if we cannot accurately determine the timezone.
*
* @param string $tzid
* @param Sabre\VObject\Component $vcalendar
* @return DateTimeZone
*/
static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
// First we will just see if the tzid is a support timezone identifier.
try {
return new \DateTimeZone($tzid);
} catch (\Exception $e) {
}
// Next, we check if the tzid is somewhere in our tzid map.
if (isset(self::$map[$tzid])) {
return new \DateTimeZone(self::$map[$tzid]);
}
// Maybe the author was hyper-lazy and just included an offset. We
// support it, but we aren't happy about it.
if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
}
if ($vcalendar) {
// If that didn't work, we will scan VTIMEZONE objects
foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
if ((string)$vtimezone->TZID === $tzid) {
// Some clients add 'X-LIC-LOCATION' with the olson name.
if (isset($vtimezone->{'X-LIC-LOCATION'})) {
$lic = (string)$vtimezone->{'X-LIC-LOCATION'};
// Libical generators may specify strings like
// "SystemV/EST5EDT". For those we must remove the
// SystemV part.
if (substr($lic,0,8)==='SystemV/') {
$lic = substr($lic,8);
}
try {
return new \DateTimeZone($lic);
} catch (\Exception $e) {
}
}
// Microsoft may add a magic number, which we also have an
// answer for.
if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
$cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value;
// 2 can mean both Europe/Lisbon and Europe/Sarajevo.
if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
return new \DateTimeZone('Europe/Sarajevo');
}
if (isset(self::$microsoftExchangeMap[$cdoId])) {
return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
}
}
}
}
}
if ($failIfUncertain) {
throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
}
// If we got all the way here, we default to UTC.
return new \DateTimeZone(date_default_timezone_get());
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace Sabre\VObject;
/**
* This class contains the version number for the VObject package
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Version {
/**
* Full version number
*/
const VERSION = '2.1.3';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'stable';
}

View file

@ -1,41 +0,0 @@
<?php
/**
* Includes file
*
* This file includes the entire VObject library in one go.
* The benefit is that an autoloader is not needed, which is often faster.
*
* @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
// Begin includes
include __DIR__ . '/DateTimeParser.php';
include __DIR__ . '/ElementList.php';
include __DIR__ . '/FreeBusyGenerator.php';
include __DIR__ . '/Node.php';
include __DIR__ . '/Parameter.php';
include __DIR__ . '/ParseException.php';
include __DIR__ . '/Property.php';
include __DIR__ . '/Reader.php';
include __DIR__ . '/RecurrenceIterator.php';
include __DIR__ . '/Splitter/SplitterInterface.php';
include __DIR__ . '/StringUtil.php';
include __DIR__ . '/TimeZoneUtil.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/Splitter/VCard.php';
include __DIR__ . '/Component.php';
include __DIR__ . '/Document.php';
include __DIR__ . '/Property/Compound.php';
include __DIR__ . '/Property/DateTime.php';
include __DIR__ . '/Property/MultiDateTime.php';
include __DIR__ . '/Splitter/ICalendar.php';
include __DIR__ . '/Component/VAlarm.php';
include __DIR__ . '/Component/VCalendar.php';
include __DIR__ . '/Component/VEvent.php';
include __DIR__ . '/Component/VFreeBusy.php';
include __DIR__ . '/Component/VJournal.php';
include __DIR__ . '/Component/VTodo.php';
// End includes

View file

@ -1,12 +0,0 @@
#!/bin/sh
# Download and install the Sabre\Vobject library for this plugin
wget 'https://github.com/fruux/sabre-vobject/archive/2.1.0.tar.gz' -O sabre-vobject-2.1.0.tar.gz
tar xf sabre-vobject-2.1.0.tar.gz
mv sabre-vobject-2.1.0/lib/* .
rm -rf sabre-vobject-2.1.0
cd lib/Sabre/VObject && wget --no-check-certificate -O Property.php https://raw2.github.com/thomascube/sabre-vobject/84b64c65f9a94f7ec5a5e327bab3cc1335dd613c/lib/Sabre/VObject/Property.php

View file

@ -22,19 +22,12 @@
*/
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
@ -43,8 +36,16 @@ class libvcalendar implements Iterator
private $attach_uri = null;
private $prodid = '-//Roundcube libcalendaring//Sabre//Sabre VObject//EN';
private $type_component_map = array('event' => 'VEVENT', 'task' => 'VTODO');
private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE',
'cutype' => 'CUTYPE', 'rsvp' => 'RSVP', 'delegated-from' => 'DELEGATED-FROM', 'delegated-to' => 'DELEGATED-TO');
private $attendee_keymap = array(
'name' => 'CN',
'status' => 'PARTSTAT',
'role' => 'ROLE',
'cutype' => 'CUTYPE',
'rsvp' => 'RSVP',
'delegated-from' => 'DELEGATED-FROM',
'delegated-to' => 'DELEGATED-TO',
'schedule-status' => 'SCHEDULE-STATUS',
);
private $iteratorkey = 0;
private $charset;
private $forward_exceptions;
@ -311,7 +312,7 @@ class libvcalendar implements Iterator
$this->method = strval($vobject->METHOD);
$this->agent = strval($vobject->PRODID);
foreach ($vobject->getBaseComponents() ?: $vobject->getComponents() as $ve) {
foreach ($vobject->getComponents() as $ve) {
if ($ve->name == 'VEVENT' || $ve->name == 'VTODO') {
// convert to hash array representation
$object = $this->_to_array($ve);
@ -414,6 +415,8 @@ class libvcalendar implements Iterator
if (!($prop instanceof VObject\Property))
continue;
$value = strval($prop);
switch ($prop->name) {
case 'DTSTART':
case 'DTEND':
@ -423,31 +426,30 @@ class libvcalendar implements Iterator
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']);
@ -458,13 +460,17 @@ class libvcalendar implements Iterator
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':
@ -477,16 +483,16 @@ class libvcalendar implements Iterator
case 'RELATED-TO':
$reltype = $prop->offsetGet('RELTYPE');
if ($reltype == 'PARENT' || $reltype === null) {
$event['parent_id'] = $prop->value;
$event['parent_id'] = $value;
}
break;
case 'SEQUENCE':
$event['sequence'] = intval($prop->value);
$event['sequence'] = intval($value);
break;
case 'PERCENT-COMPLETE':
$event['complete'] = intval($prop->value);
$event['complete'] = intval($value);
break;
case 'LOCATION':
@ -503,27 +509,28 @@ class libvcalendar implements Iterator
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';
@ -537,20 +544,20 @@ class libvcalendar implements Iterator
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;
}
}
@ -602,20 +609,20 @@ class libvcalendar implements Iterator
$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') {
if ($prop['VALUE'] == 'DATE-TIME') {
$trigger = '@' . $prop->getDateTime()->format('U');
$alarm['trigger'] = $prop->getDateTime();
}
}
if (!$trigger && ($values = libcalendaring::parse_alarm_value($prop->value))) {
if (!$trigger && ($values = libcalendaring::parse_alarm_value($value))) {
$trigger = $values[2];
}
if (!$alarm['trigger']) {
$alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $prop->value), 'T');
$alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $value), 'T');
// if all 0-values have been stripped, assume 'at time'
if ($alarm['trigger'] == 'P')
$alarm['trigger'] = 'PT0S';
@ -623,7 +630,7 @@ class libvcalendar implements Iterator
break;
case 'ACTION':
$action = $alarm['action'] = strtoupper($prop->value);
$action = $alarm['action'] = strtoupper($value);
break;
case 'SUMMARY':
@ -633,18 +640,18 @@ class libvcalendar implements Iterator
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;
}
@ -676,6 +683,11 @@ class libvcalendar implements Iterator
unset($event['end']);
}
// some iTip CANCEL messages only contain the start date
if (!$event['end'] && $event['start'] && $this->method == 'CANCEL') {
$event['end'] = clone $event['start'];
}
// minimal validation
if (empty($event['uid']) || ($event['_type'] == 'event' && empty($event['start']) != empty($event['end']))) {
throw new VObject\ParseException('Object validation failed: missing mandatory object properties');
@ -696,6 +708,8 @@ class libvcalendar implements Iterator
if (!($prop instanceof VObject\Property))
continue;
$value = strval($prop);
switch ($prop->name) {
case 'CREATED':
case 'LAST-MODIFIED':
@ -707,16 +721,16 @@ class libvcalendar implements Iterator
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) {
@ -725,8 +739,8 @@ class libvcalendar implements Iterator
// 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);
@ -739,7 +753,7 @@ class libvcalendar implements Iterator
break;
case 'COMMENT':
$this->freebusy['comment'] = $prop->value;
$this->freebusy['comment'] = $value;
}
}
@ -751,7 +765,15 @@ class libvcalendar implements Iterator
*/
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));
}
/**
@ -762,44 +784,47 @@ class libvcalendar implements Iterator
if (empty($prop)) {
return $as_array ? array() : null;
}
else if ($prop instanceof VObject\Property\MultiDateTime) {
else if ($prop instanceof VObject\Property\iCalendar\DateTime) {
if (count($prop->getDateTimes()) > 1) {
$dt = array();
$dateonly = ($prop->getDateType() & VObject\Property\DateTime::DATE);
$dateonly = !$prop->hasTime();
foreach ($prop->getDateTimes() as $item) {
$item->_dateonly = $dateonly;
$dt[] = $item;
}
}
else if ($prop instanceof VObject\Property\DateTime) {
else {
$dt = $prop->getDateTime();
if ($prop->getDateType() & VObject\Property\DateTime::DATE) {
if (!$prop->hasTime()) {
$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);
}
catch (Exception $e) {
// ignore date parse errors
}
}
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;
}
@ -815,16 +840,30 @@ class libvcalendar implements Iterator
/**
* Create a Sabre\VObject\Property instance from a PHP DateTime object
*
* @param object VObject\Document parent node to create property for
* @param string Property name
* @param object DateTime
* @param boolean Set as UTC date
* @param boolean Set as VALUE=DATE property
*/
public function datetime_prop($name, $dt, $utc = false, $dateonly = null)
public function datetime_prop($cal, $name, $dt, $utc = false, $dateonly = null, $set_type = false)
{
$is_utc = $utc || (($tz = $dt->getTimezone()) && in_array($tz->getName(), array('UTC','GMT','Z')));
if ($utc) {
$dt->setTimeZone(new \DateTimeZone('UTC'));
$is_utc = true;
}
else {
$is_utc = ($tz = $dt->getTimezone()) && in_array($tz->getName(), array('UTC','GMT','Z'));
}
$is_dateonly = $dateonly === null ? (bool)$dt->_dateonly : (bool)$dateonly;
$vdt = new VObject\Property\DateTime($name);
$vdt->setDateTime($dt, $is_dateonly ? VObject\Property\DateTime::DATE :
($is_utc ? VObject\Property\DateTime::UTC : VObject\Property\DateTime::LOCALTZ));
$vdt = $cal->createProperty($name, $dt, null, $is_dateonly ? 'DATE' : 'DATE-TIME');
if ($is_dateonly) {
$vdt['VALUE'] = 'DATE';
}
else if ($set_type) {
$vdt['VALUE'] = 'DATE-TIME';
}
// register timezone for VTIMEZONE block
if (!$is_utc && !$dateonly && $tz && ($tzname = $tz->getName())) {
@ -860,8 +899,8 @@ class libvcalendar implements Iterator
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;
}
@ -882,10 +921,10 @@ class libvcalendar implements Iterator
$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;
@ -903,7 +942,7 @@ class libvcalendar implements Iterator
// 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
}
@ -937,12 +976,14 @@ class libvcalendar implements Iterator
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'])) {
@ -951,30 +992,31 @@ class libvcalendar implements Iterator
$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
@ -1003,21 +1045,20 @@ class libvcalendar implements Iterator
$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);
}
@ -1050,15 +1091,15 @@ class libvcalendar implements Iterator
$ve->add('PERCENT-COMPLETE', intval($event['complete']));
// Apple iCal required the COMPLETED date to be set in order to consider a task complete
if ($event['complete'] == 100)
$ve->add($this->datetime_prop('COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
$ve->add($this->datetime_prop($cal, 'COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
}
if ($event['valarms']) {
foreach ($event['valarms'] as $alarm) {
$va = VObject\Component::create('VALARM');
$va = $cal->createComponent('VALARM');
$va->action = $alarm['action'];
if ($alarm['trigger'] instanceof DateTime) {
$va->add($this->datetime_prop('TRIGGER', $alarm['trigger'], true));
$va->add($this->datetime_prop($cal, 'TRIGGER', $alarm['trigger'], true, null, true));
}
else {
$va->add('TRIGGER', $alarm['trigger']);
@ -1087,13 +1128,13 @@ class libvcalendar implements Iterator
}
// legacy support
else if ($event['alarms']) {
$va = VObject\Component::create('VALARM');
$va = $cal->createComponent('VALARM');
list($trigger, $va->action) = explode(':', $event['alarms']);
$val = libcalendaring::parse_alarm_value($trigger);
if ($val[3])
$va->add('TRIGGER', $val[3]);
else if ($val[0] instanceof DateTime)
$va->add($this->datetime_prop('TRIGGER', $val[0]));
$va->add($this->datetime_prop($cal, 'TRIGGER', $val[0], true, null, true));
$ve->add($va);
}
@ -1105,12 +1146,14 @@ class libvcalendar implements Iterator
else if (!empty($attendee['email'])) {
if (isset($attendee['rsvp']))
$attendee['rsvp'] = $attendee['rsvp'] ? 'TRUE' : null;
$ve->add('ATTENDEE', 'mailto:' . $attendee['email'], array_filter(self::map_keys($attendee, $this->attendee_keymap)));
$ve->add('ATTENDEE', 'mailto:' . $attendee['email'],
array_filter(self::map_keys($attendee, $this->attendee_keymap)));
}
}
if ($event['organizer']) {
$ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'], self::map_keys($event['organizer'], array('name' => 'CN')));
$ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'],
array_filter(self::map_keys($event['organizer'], array('name' => 'CN'))));
}
foreach ((array)$event['url'] as $url) {
@ -1141,7 +1184,7 @@ class libvcalendar implements Iterator
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
}
@ -1179,7 +1222,7 @@ class libvcalendar implements Iterator
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);
@ -1198,10 +1241,11 @@ class libvcalendar implements Iterator
* @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 {
@ -1222,7 +1266,7 @@ class libvcalendar implements Iterator
$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;
@ -1236,12 +1280,12 @@ class libvcalendar implements Iterator
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;
}
@ -1315,48 +1359,25 @@ class libvcalendar implements Iterator
/**
* 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",
',',
protected $structuredValues = array(
// vCard
'N',
'ADR',
'ORG',
'GENDER',
'LOCATION',
// iCalendar
'REQUEST-STATUS',
);
$out = array(
'\\\\',
'\n',
'\,',
);
$str.=':' . str_replace($src, $out, $this->value);
$out = '';
while (strlen($str) > 0) {
if (strlen($str) > 75) {
$out.= mb_strcut($str, 0, 75, 'utf-8') . "\r\n";
$str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8');
} else {
$out.= $str . "\r\n";
$str = '';
break;
}
}
return $out;
}
}

View file

@ -5,7 +5,7 @@
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -98,7 +98,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$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");
}
@ -122,7 +122,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals('REQUEST', $ical->method, "iTip method");
// attendees
$this->assertEquals(2, count($event['attendees']), "Attendees list (including organizer)");
$this->assertEquals(3, count($event['attendees']), "Attendees list (including organizer)");
$organizer = $event['attendees'][0];
$this->assertEquals('ORGANIZER', $organizer['role'], 'Organizer ROLE');
$this->assertEquals('Rolf Test', $organizer['name'], 'Organizer name');
@ -131,8 +131,17 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals('REQ-PARTICIPANT', $attendee['role'], 'Attendee ROLE');
$this->assertEquals('NEEDS-ACTION', $attendee['status'], 'Attendee STATUS');
$this->assertEquals('rolf2@mykolab.com', $attendee['email'], 'Attendee mailto:');
$this->assertEquals('carl@mykolab.com', $attendee['delegated-from'], 'Attendee delegated-from');
$this->assertTrue($attendee['rsvp'], 'Attendee RSVP');
$delegator = $event['attendees'][2];
$this->assertEquals('NON-PARTICIPANT', $delegator['role'], 'Delegator ROLE');
$this->assertEquals('DELEGATED', $delegator['status'], 'Delegator STATUS');
$this->assertEquals('INDIVIDUAL', $delegator['cutype'], 'Delegator CUTYPE');
$this->assertEquals('carl@mykolab.com', $delegator['email'], 'Delegator mailto:');
$this->assertEquals('rolf2@mykolab.com', $delegator['delegated-to'], 'Delegator delegated-to');
$this->assertFalse($delegator['rsvp'], 'Delegator RSVP');
// attachments
$this->assertEquals(1, count($event['attachments']), "Embedded attachments");
$attachment = $event['attachments'][0];
@ -154,6 +163,14 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count($rrule['EXDATE']), "Recurrence EXDATEs");
$this->assertInstanceOf('DateTime', $rrule['EXDATE'][0], "Recurrence EXDATE as DateTime");
$this->assertTrue(is_array($rrule['EXCEPTIONS']));
$this->assertEquals(1, count($rrule['EXCEPTIONS']), "Recurrence Exceptions");
$exception = $rrule['EXCEPTIONS'][0];
$this->assertEquals($event['uid'], $event['uid'], "Exception UID");
$this->assertEquals('Recurring Test (Exception)', $exception['title'], "Exception title");
$this->assertInstanceOf('DateTime', $exception['start'], "Exception start");
// categories, class
$this->assertEquals('libcalendaring tests', join(',', (array)$event['categories']), "Event categories");
$this->assertEquals('confidential', $event['sensitivity'], "Class/sensitivity = confidential");
@ -201,13 +218,14 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals('DISPLAY', $event['valarms'][0]['action'], "First alarm action");
$this->assertEquals('This is the first event reminder', $event['valarms'][0]['description'], "First alarm text");
$this->assertEquals(2, count($event['valarms']), "List all VALARM blocks");
$this->assertEquals(3, count($event['valarms']), "List all VALARM blocks");
$valarm = $event['valarms'][1];
$this->assertEquals(1, count($valarm['attendees']), "Email alarm attendees");
$this->assertEquals('EMAIL', $valarm['action'], "Second alarm item (action)");
$this->assertEquals('-P1D', $valarm['trigger'], "Second alarm item (trigger)");
$this->assertEquals('This is the reminder message', $valarm['summary'], "Email alarm text");
$this->assertInstanceOf('DateTime', $event['valarms'][2]['trigger'], "Absolute trigger date/time");
// test alarms export
$ics = $ical->export(array($event));
@ -216,6 +234,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertContains('DESCRIPTION:This is the first event reminder', $ics, "Alarm description");
$this->assertContains('SUMMARY:This is the reminder message', $ics, "Email alarm summary");
$this->assertContains('ATTENDEE:mailto:reminder-recipient@example.org', $ics, "Email alarm recipient");
$this->assertContains('TRIGGER;VALUE=DATE-TIME:20130812', $ics, "Date-Time trigger");
}
/**
@ -283,6 +302,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals(9, count($event['recurrence']['RDATE']));
$this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][0]);
$this->assertInstanceOf('DateTime', $event['recurrence']['RDATE'][1]);
}
/**
@ -297,7 +317,9 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$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");
}
/**
@ -369,7 +391,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertRegExp('/ATTENDEE.*;ROLE=REQ-PARTICIPANT/', $ics, "Export Attendee ROLE");
$this->assertRegExp('/ATTENDEE.*;PARTSTAT=NEEDS-ACTION/', $ics, "Export Attendee Status");
$this->assertRegExp('/ATTENDEE.*;RSVP=TRUE/', $ics, "Export Attendee RSVP");
$this->assertRegExp('/ATTENDEE.*:mailto:rolf2@/', $ics, "Export Attendee mailto:");
$this->assertRegExp('/:mailto:rolf2@/', $ics, "Export Attendee mailto:");
$rrule = $event['recurrence'];
$this->assertRegExp('/RRULE:.*FREQ='.$rrule['FREQ'].'/', $ics, "Export Recurrence Frequence");
@ -421,6 +443,8 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
// add exceptions
$event = $events[0];
unset($event['recurrence']['EXCEPTIONS']);
$exception1 = $event;
$exception1['start'] = clone $event['start'];
$exception1['start']->setDate(2013, 8, 14);
@ -443,8 +467,8 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals($num, substr_count($ics, 'UID:'.$event['uid']), "Recurrence Exceptions with same UID");
$this->assertEquals($num, substr_count($ics, 'END:VEVENT'), "VEVENT encapsulation END");
$this->assertContains('RECURRENCE-ID;VALUE=DATE-TIME;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date");
$this->assertContains('RECURRENCE-ID;VALUE=DATE-TIME;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date");
$this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20130814', $ics, "Recurrence-ID (1) being the exception date");
$this->assertContains('RECURRENCE-ID;TZID=Europe/Zurich:20131113', $ics, "Recurrence-ID (2) being the exception date");
$this->assertContains('SUMMARY:'.$exception2['title'], $ics, "Exception title");
}
@ -478,7 +502,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$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");
}
/**
@ -505,10 +529,11 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
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());

View file

@ -46,6 +46,11 @@ ATTENDEE:mailto:reminder-recipient@example.org
SUMMARY:This is the reminder message
DESCRIPTION:This is the second event reminder
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:An absolute reminder
TRIGGER;VALUE=DATE-TIME:20130812T160000Z
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -7,7 +7,9 @@ BEGIN:VEVENT
ORGANIZER;CN="Rolf Test":MAILTO:rolf@mykolab.com
DTSTAMP:20130628T190056Z
ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;
X-UID=208889384:mailto:rolf2@mykolab.com
DELEGATED-FROM=carl@mykolab.com;X-UID=208889384:mailto:rolf2@mykolab.com
ATTENDEE;RSVP=FALSE;PARTSTAT=DELEGATED;ROLE=NON-PARTICIPANT;CUTYPE=INDIVIDUAL;
DELEGATED-TO=rolf2@mykolab.com:mailto:carl@mykolab.com
CREATED:20130628T190032Z
UID:ac6b0aee-2519-4e5c-9a25-48c57064c9f0
LAST-MODIFIED:20130628T190032Z

View file

@ -40,4 +40,12 @@ TRIGGER:-PT12H
ACTION:DISPLAY
END:VALARM
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID="Europe/Zurich":20140521T100000
DTEND;TZID="Europe/Zurich":20140521T150000
RECURRENCE-ID:20140521T080000Z
UID:7e93e8e8eef16f28aa33b78cd73613eb
DTSTAMP:20130718T082032Z
SUMMARY:Recurring Test (Exception)
END:VEVENT
END:VCALENDAR

View file

@ -262,6 +262,8 @@ class kolab_storage
*/
public static function get_freebusy_server()
{
self::setup();
$url = 'https://' . $_SESSION['imap_host'] . '/freebusy';
$url = self::$config->get('kolab_freebusy_server', $url);
$url = rcube_utils::resolve_url($url);