Second part of refactoring; should be runnable again, yet not thoroughly tested

This commit is contained in:
Tobias Hößl 2012-08-11 08:07:19 +00:00
parent b8234a1cb8
commit 6186153f68
88 changed files with 2135 additions and 1186 deletions

View file

@ -0,0 +1,8 @@
language: php
php:
- 5.3
- 5.4
script: phpunit --configuration tests/phpunit.xml
before_script: composer install

View file

@ -0,0 +1,7 @@
2.0.0-stable (2012-08-08)
* VObject is now a separate project from SabreDAV. See the SabreDAV
changelog for version information before 2.0.
* New: VObject library now uses PHP 5.3 namespaces.
* New: It's possible to specify lists of parameters when constructing
properties.
* New: made it easier to construct the FreeBusyGenerator.

27
dav/sabre-vobject/LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright (C) 2007-2012 Rooftop Solutions.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Sabre nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

388
dav/sabre-vobject/README.md Normal file
View file

@ -0,0 +1,388 @@
# SabreTooth VObject library
[![Build Status](https://secure.travis-ci.org/evert/sabre-vobject.png?branch=master)](http://travis-ci.org/evert/sabre-vobject)
The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545)
and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP.
The goal of the VObject library is to create a very complete library, with an easy to use API.
This project is a spin-off from [SabreDAV](http://code.google.com/p/sabredav/), where it has
been used for several years. The VObject library has 100% unittest coverage.
# Installation
VObject requires PHP 5.3, and should be installed using composer.
The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website).
After that, just declare the vobject dependency as follows:
```
"require" : {
"sabre/vobject" : "dev-master"
}
```
Then, run `composer.phar update` and you should be good. As soon as the first release is out, you should switch `dev-master` to `2.0.*` though.
# Usage
## Parsing
For our example, we will be using the following vcard:
```
BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject 2.0//EN
N:Planck;Max;;;
FN:Max Planck
EMAIL;TYPE=WORK:mplanck@example.org
item1.TEL;TYPE=CELL:(+49)3144435678
item1.X-ABLabel:Private cell
item2.TEL;TYPE=WORK:(+49)5554564744
item2.X-ABLabel:Work
END:VCARD
```
If we want to just print out Max' full name, you can just use property access:
```php
use Sabre\VObject;
$card = VObject\Reader::read($data);
echo $card->FN;
```
## Changing properties
Creating properties is pretty similar. If we like to add his birthday, we just
set the property:
```php
$card->BDAY = '1858-04-23';
```
Note that in the previous example, we're actually updating any existing BDAY that
may already exist. If we want to add a new property, without overwriting the previous
we can do this with the `add` method.
```php
$card->add('EMAIL','max@example.org');
```
## Parameters
If we want to also specify that this is max' home email addresses, we can do this with
a third parameter:
```
$card->add('EMAIL', 'max@example'org', array('type' => 'HOME'));
```
If we want to read out all the email addresses and their type, this would look something
like this:
```
foreach($card->EMAIL as $email) {
echo $email['TYPE'], ' - ', $email;
}
```
## Groups
In our example, you can see that the TEL properties are prefixed. These are 'groups' and
allow you to group multiple related properties together. The group can be any user-defined
name.
This particular example as generated by the OS X addressbook, which uses the `X-ABLabel`
to allow the user to specify custom labels for properties. OS X addressbook uses groups
to tie the label to the property.
The VObject library simply ignores the group if you don't specify it, so this will work:
```php
foreach($card->TEL as $tel) {
echo $tel, "\n";
}
```
But if you would like to target a specific group + property, this is possible too:
```php
echo $card->{'ITEM1.TEL'};
```
So if you would like to show all the phone numbers, along with their custom label, the
following syntax is used:
```
foreach($card->TEL as $tel) {
echo $card->{$tel->group . '.X-ABLABEL'}, ": ";
echo $tel, "\n";
}
```
## Serializing / Saving
If you want to generate your updated VObject, you can simply call the serialize() method.
```
echo $card->serialize();
```
## Components
iCalendar, unlike vCards always have sub-components. Where vCards are often just a flat
list, iCalendar objects tend to have a tree-like structure. For the following paragraphs,
we will use the following object as the example:
```
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 2.0//EN
BEGIN:VEVENT
SUMMARY:Curiosity landing
DTSTART:20120806T051439Z
LOCATION:Mars
END:VEVENT
END:VCALENDAR
```
Since events, tasks and journals are always in a sub component, this is also how we
access them.
```php
use Sabre\VObject;
$calendar = VObject\Reader::read($data);
echo $calendar->VEVENT->SUMMARY;
```
Adding components to a calendar is done with a factory method:
```php
$event = VObject\Component::create('VEVENT');
$calendar->add($event);
$event->SUMMARY = 'Curiosity launch';
$event->DTSTART = '20111126T150202Z';
$event->LOCATION = 'Cape Carnival';
```
By the way.. cloning also works as expected, as the entire structure is cloned along with it:
```php
$clonedEvent = clone $calendar->VEVENT[0];
$calendar->add($clonedEvent);
```
## Date and time handling
If you ever had to deal with iCalendar timezones, you know it can be complicated.
The way timezones are specified is flawed, which is something I may write an essay about some
day. VObject does its best to determine the correct timezone. Many standard formats
have been tested and verified, and special code has been implemented for special-casing
microsoft generated timezone information, and others.
To get a real php `DateTime` object, you can request this as follows:
```
$event = $calendar->VEVENT;
$start = $event->DTSTART->getDateTime();
echo $start->format(\DateTime::W3C);
```
To set the property with a DateTime object, you can use the following syntax:
```
$dateTime = new \DateTime('2012-08-07 23:53:00', new DateTimeZone('Europe/Amsterdam'));
$event->DTSTART->setDateTime($dateTime, VObject\Property\DateTime::DATE);
```
The second argument specifies the type of date you're setting. The following three
options exist:
1. `LOCAL` This is a floating time, with no timezone information. This basically specifies that the event happens in whatever the timezone's currently in. This would be encoded as `DTSTART;VALUE=DATE-TIME:20120807235300`
2. `UTC` This specifies that the time should be encoded as a UTC time. This is encoded as `DTSTART;VALUE=DATE-TIME:20120807205300Z`. Note the extra Z and the fact that it's two hours 'earlier'.
3. `LOCALTZ` specifies that it's supposed to be encoded in its local timezone. For example `DTSTART;VALUE=DATE-TIME;TZID=Europe/Amsterdam:20120807235300`.
4. `DATE` This is a date-only, and does not contain the time. In this case this would be encoded as `DTSTART;VALUE=DATE:20120807`.
A few important notes:
* When a `TZID` is specified, there should also be a matching `VTIMEZONE` object with all the timezone information. VObject cannot currently automatically embed this. However, in reality other clients seem to do fine without this information. Yet, for completeness, this will be added in the future.
* As mentioned, the timezone-determination process may sometimes fail. Report any issues you find, and I'll be quick to add workarounds!
## Recurrence rules
Recurrence rules allow events to recur, for example for a weekly meeting, or an anniversary.
This is done with the `RRULE` property. The `RRULE` property allows for a LOT of different
rules. VObject only implements the ones that actually appear in calendar software.
To read more about `RRULE` and all the options, check out [RFC5545](https://tools.ietf.org/html/rfc5545#section-3.8.5).
VObject supports the following options:
1. `UNTIL` for an end date.
2. `INTERVAL` for for example "every 2 days".
3. `COUNT` to stop recurring after x items.
4. `FREQ=DAILY` to recur every day, and `BYDAY` to limit it to certain days.
5. `FREQ=WEEKLY` to recur every week, `BYDAY` to expand this to multiple weekdays in every week and `WKST` to specify on which day the week starts.
6. `FREQ=MONTHLY` to recur every month, `BYMONTHDAY` to expand this to certain days in a month, `BYDAY` to expand it to certain weekdays occuring in a month, and `BYSETPOS` to limit the last two expansions.
7. `FREQ=YEARLY` to recur every year, `BYMONTH` to expand that to certain months in a year, and `BYDAY` and `BYWEEKDAY` to expand the `BYMONTH` rule even further.
VObject supports the `EXDATE` property for exclusions, but not yet the `RDATE` and `EXRULE`
properties. If you're interested in this, please file a github issue, as this will put it
on my radar.
This is a bit of a complex subject to go in excruciating detail. The
[RFC](https://tools.ietf.org/html/rfc5545#section-3.8.5) has a lot of examples though.
The hard part is not to write the RRULE, it is to expand them. The most complex and
hard-to-read code is hidden in this component. Dragons be here.
So, if we have a meeting every 2nd monday of the month, this would be specified as such:
```
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20120109T140000Z
RRULE:FREQ=MONTHLY;BYDAY=MO;BYSETPOS=2
END:VEVENT
END:VCALENDAR
```
Note that normally it's not allowed to indent the object like this, but it does make
it easier to read. This is also the first time I added in a UID, which is required
for all VEVENT, VTODO and VJOURNAL objects!
To figure out all the meetings for this year, we can use the following syntax:
```php
use Sabre\VObject;
$calendar = VObject\Reader::read($data);
$calendar->expand(new DateTime('2012-01-01'), new DateTime('2012-12-31'));
```
What the expand method does, is look at its inner events, and expand the recurring
rule. Our calendar now contains 12 events. The first will have its RRULE stripped,
and every subsequent VEVENT has the correct meeting date and a `RECURRENCE-ID` set.
This results in something like this:
```
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
DTSTART:20120109T140000Z
END:VEVENT
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
RECURRENCE-ID:20120213T140000Z
DTSTART:20120213T140000Z
END:VEVENT
BEGIN:VEVENT
UID:1102c450-e0d7-11e1-9b23-0800200c9a66
RECURRENCE-ID:20120312T140000Z
DTSTART:20120312T140000Z
END:VEVENT
..etc..
END:VCALENDAR
```
To show the list of dates, we would do this as such:
```
foreach($calendar->VEVENT as $event) {
echo $event->DTSTART->getDateTime()->format(\DateTime::ATOM);
}
```
In a recurring event, single instances can also be overriden. VObject also takes these
into consideration. The reason we needed to specify a start and end-date, is because
some recurrence rules can be 'never ending'.
You should make sure you pick a sane date-range. Because if you pick a 50 year
time-range, for a daily recurring event; this would result in over 18K objects.
# Free-busy report generation
Some calendaring software can make use of FREEBUSY reports to show when people are
available.
You can automatically generate these reports from calendars using the `FreeBusyGenerator`.
Example based on our last event:
```
// We're giving it the calendar object. It's also possible to specify multiple objects,
// by setting them as an array.
//
// We must also specify a start and end date, because recurring events are expanded.
$fbGenerator = new VObject\FreeBusyGenerator(
new DateTime('2012-01-01'),
new DateTime('2012-12-31'),
$calendar
);
// Grabbing the report
$freebusy = $fbGenerator->result();
// The freebusy report is another VCALENDAR object, so we can serialize it as usual:
echo $freebusy->serialize();
```
The output of this script will look like this:
```
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 2.0//EN
CALSCALE:GREGORIAN
BEGIN:VFREEBUSY
DTSTART;VALUE=DATE-TIME:20111231T230000Z
DTEND;VALUE=DATE-TIME:20111231T230000Z
DTSTAMP;VALUE=DATE-TIME:20120808T131628Z
FREEBUSY;FBTYPE=BUSY:20120109T140000Z/20120109T140000Z
FREEBUSY;FBTYPE=BUSY:20120213T140000Z/20120213T140000Z
FREEBUSY;FBTYPE=BUSY:20120312T140000Z/20120312T140000Z
FREEBUSY;FBTYPE=BUSY:20120409T140000Z/20120409T140000Z
FREEBUSY;FBTYPE=BUSY:20120514T140000Z/20120514T140000Z
FREEBUSY;FBTYPE=BUSY:20120611T140000Z/20120611T140000Z
FREEBUSY;FBTYPE=BUSY:20120709T140000Z/20120709T140000Z
FREEBUSY;FBTYPE=BUSY:20120813T140000Z/20120813T140000Z
FREEBUSY;FBTYPE=BUSY:20120910T140000Z/20120910T140000Z
FREEBUSY;FBTYPE=BUSY:20121008T140000Z/20121008T140000Z
FREEBUSY;FBTYPE=BUSY:20121112T140000Z/20121112T140000Z
FREEBUSY;FBTYPE=BUSY:20121210T140000Z/20121210T140000Z
END:VFREEBUSY
END:VCALENDAR
```

View file

@ -0,0 +1,27 @@
{
"name": "sabre/vobject",
"description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
"keywords" : [ "VObject", "iCalendar", "vCard" ],
"homepage" : "https://github.com/evert/sabre-vobject",
"license" : "BSD-3-Clause",
"require" : {
"php" : ">=5.3.1"
},
"authors" : [
{
"name" : "Evert Pot",
"email" : "evert@rooftopsolutions.nl",
"homepage" : "http://www.rooftopsolutions.nl/",
"role" : "Developer"
}
],
"support" : {
"forum" : "https://groups.google.com/group/sabredav-discuss",
"source" : "https://github.com/evert/sabre-vobject"
},
"autoload" : {
"psr-0" : {
"Sabre\\VObject" : "lib/"
}
}
}

View file

@ -0,0 +1,412 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Component extends Element {
/**
* Name, for example VEVENT
*
* @var string
*/
public $name;
/**
* Children properties and components
*
* @var array
*/
public $children = array();
/**
* The following constants are used by the validate() method.
*/
const REPAIR = 1;
/**
* 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',
);
/**
* 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(Element $element)
* 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 Element) {
if (!is_null($itemValue)) {
throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
}
$item->parent = $this;
$this->children[] = $item;
} elseif(is_string($item)) {
if (!is_scalar($itemValue)) {
throw new \InvalidArgumentException('The second argument must be scalar');
}
$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\\Element 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:
* - Component::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());
}
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

@ -0,0 +1,101 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VAlarm component
*
* This component contains some additional functionality specific for VALARMs.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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') {
$effectiveTrigger = clone $parentComponent->DTSTART->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

@ -0,0 +1,242 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCalendar extends VObject\Component {
/**
* 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

@ -0,0 +1,105 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCard extends VObject\Component {
/**
* 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:
* - Component::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';
}
}
}
$version = $this->select('FN');
if (count($version)!==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($version) === 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

@ -0,0 +1,70 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VEvent component
*
* This component contains some additional functionality specific for VEVENT's.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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

@ -0,0 +1,46 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VJournal component
*
* This component contains some additional functionality specific for VJOURNALs.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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

@ -0,0 +1,68 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
/**
* VTodo component
*
* This component contains some additional functionality specific for VTODOs.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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

@ -0,0 +1,181 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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-3][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-3][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

@ -0,0 +1,16 @@
<?php
namespace Sabre\VObject;
/**
* Base class for all elements
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Element extends Node {
public $parent = null;
}

View file

@ -0,0 +1,172 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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

@ -0,0 +1,322 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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 = new Component('VCALENDAR');
$calendar->version = '2.0';
$calendar->prodid = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
$calendar->calscale = 'GREGORIAN';
}
$vfreebusy = new Component('VFREEBUSY');
$calendar->add($vfreebusy);
if ($this->start) {
$dtstart = new Property\DateTime('DTSTART');
$dtstart->setDateTime($this->start,Property\DateTime::UTC);
$vfreebusy->add($dtstart);
}
if ($this->end) {
$dtend = new Property\DateTime('DTEND');
$dtend->setDateTime($this->start,Property\DateTime::UTC);
$vfreebusy->add($dtend);
}
$dtstamp = new Property\DateTime('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 = new Property(
'FREEBUSY',
$busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
);
$prop['FBTYPE'] = $busyTime[2];
$vfreebusy->add($prop);
}
return $calendar;
}
}

View file

@ -0,0 +1,166 @@
<?php
namespace Sabre\VObject;
/**
* Base class for all nodes
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
/**
* 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.
* 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() {
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);
}
/**
* 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);
}
/* }}} */
}

View file

@ -0,0 +1,84 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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) {
$this->name = strtoupper($name);
$this->value = $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',
'\;',
'\,',
);
return $this->name . '=' . str_replace($src, $out, $this->value);
}
/**
* Called when this object is being cast to a string
*
* @return string
*/
public function __toString() {
return $this->value;
}
}

View file

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

View file

@ -0,0 +1,365 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Property extends Element {
/**
* 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',
);
/**
* 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()) {
$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;
}
/**
* Turns the object back into a serialized blob.
*
* @return string
*/
public function serialize() {
$str = $this->name;
if ($this->group) $str = $this->group . '.' . $this->name;
if (count($this->parameters)) {
foreach($this->parameters as $param) {
$str.=';' . $param->serialize();
}
}
$src = array(
'\\',
"\n",
);
$out = array(
'\\\\',
'\n',
);
$str.=':' . str_replace($src, $out, $this->value);
$out = '';
while(strlen($str)>0) {
if (strlen($str)>75) {
$out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
$str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
} else {
$out.=$str . "\r\n";
$str='';
break;
}
}
return $out;
}
/**
* 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)) {
if (!is_scalar($itemValue) && !is_null($itemValue)) {
throw new \InvalidArgumentException('The second argument must be scalar');
}
$parameter = new Parameter($item,$itemValue);
$parameter->parent = $this;
$this->parameters[] = $parameter;
} else {
throw new \InvalidArgumentException('The first argument must either be a Element or 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 Element
*/
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;
}
}
}

View file

@ -0,0 +1,233 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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;
}
/**
* 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

@ -0,0 +1,168 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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;
}
}

View file

@ -0,0 +1,183 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Reader {
/**
* Parses the file and returns the top component
*
* @param string $data
* @return Element
*/
static function read($data) {
// 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);
}
/**
* Reads and parses a single line.
*
* This method receives the full array of lines. The array pointer is used
* to traverse.
*
* @param array $lines
* @return Element
*/
static private function readLine(&$lines) {
$line = current($lines);
$lineNr = key($lines);
next($lines);
// Components
if (stripos($line,"BEGIN:")===0) {
$componentName = strtoupper(substr($line,6));
$obj = Component::create($componentName);
$nextLine = current($lines);
while(stripos($nextLine,"END:")!==0) {
$obj->add(self::readLine($lines));
$nextLine = current($lines);
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);
$token = '[A-Z0-9-\.]+';
$parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
$regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
$result = preg_match($regex,$line,$matches);
if (!$result) {
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) {
$value = isset($match['paramValue'])?$match['paramValue']:null;
if (isset($value[0])) {
// Stripping quotes, if needed
if ($value[0] === '"') $value = substr($value,1,strlen($value)-2);
} else {
$value = '';
}
$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

@ -0,0 +1,351 @@
<?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-2012 Rooftop Solutions. All rights reserved.
* @author Frank Edelhaeuser (fedel@users.sourceforge.net)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @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'=>'Etc/GMT',
'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',
);
public static $microsoftExchangeMap = array(
0 => 'UTC',
31 => 'Africa/Casablanca',
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',
69 => '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 UTC.
*
* @param string $tzid
* @param Sabre\VObject\Component $vcalendar
* @return DateTimeZone
*/
static public function getTimeZone($tzid, Component $vcalendar = null) {
// 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]);
}
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'})) {
try {
return new \DateTimeZone($vtimezone->{'X-LIC-LOCATION'});
} catch (\Exception $e) {
}
}
// Microsoft may add a magic number, which we also have an
// answer for.
if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
if (isset(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value])) {
return new \DateTimeZone(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value]);
}
}
}
}
}
// If we got all the way here, we default to UTC.
return new \DateTimeZone(date_default_timezone_get());
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Sabre\VObject;
/**
* This class contains the version number for the VObject package
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Version {
/**
* Full version number
*/
const VERSION = '2.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'stable';
}

View file

@ -0,0 +1,146 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject\Component;
use DateTime;
class VAlarmTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VAlarm $valarm,$start,$end,$outcome) {
$this->assertEquals($outcome, $valarm->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
// Hard date and time
$valarm1 = Component::create('VALARM');
$valarm1->TRIGGER = '20120312T130000Z';
$valarm1->TRIGGER['VALUE'] = 'DATE-TIME';
$tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to start time of event
$valarm2 = Component::create('VALARM');
$valarm2->TRIGGER = '-P1D';
$valarm2->TRIGGER['VALUE'] = 'DURATION';
$vevent2 = Component::create('VEVENT');
$vevent2->DTSTART = '20120313T130000Z';
$vevent2->add($valarm2);
$tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to end time of event
$valarm3 = Component::create('VALARM');
$valarm3->TRIGGER = '-P1D';
$valarm3->TRIGGER['VALUE'] = 'DURATION';
$valarm3->TRIGGER['RELATED']= 'END';
$vevent3 = Component::create('VEVENT');
$vevent3->DTSTART = '20120301T130000Z';
$vevent3->DTEND = '20120401T130000Z';
$vevent3->add($valarm3);
$tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to end time of todo
$valarm4 = Component::create('VALARM');
$valarm4->TRIGGER = '-P1D';
$valarm4->TRIGGER['VALUE'] = 'DURATION';
$valarm4->TRIGGER['RELATED']= 'END';
$vtodo4 = Component::create('VTODO');
$vtodo4->DTSTART = '20120301T130000Z';
$vtodo4->DUE = '20120401T130000Z';
$vtodo4->add($valarm4);
$tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to start time of event + repeat
$valarm5 = Component::create('VALARM');
$valarm5->TRIGGER = '-P1D';
$valarm5->TRIGGER['VALUE'] = 'DURATION';
$valarm5->REPEAT = 10;
$valarm5->DURATION = 'P1D';
$vevent5 = Component::create('VEVENT');
$vevent5->DTSTART = '20120301T130000Z';
$vevent5->add($valarm5);
$tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true);
// Relation to start time of event + duration, but no repeat
$valarm6 = Component::create('VALARM');
$valarm6->TRIGGER = '-P1D';
$valarm6->TRIGGER['VALUE'] = 'DURATION';
$valarm6->DURATION = 'P1D';
$vevent6 = Component::create('VEVENT');
$vevent6->DTSTART = '20120313T130000Z';
$vevent6->add($valarm6);
$tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
$tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
// Relation to end time of event (DURATION instead of DTEND)
$valarm7 = Component::create('VALARM');
$valarm7->TRIGGER = '-P1D';
$valarm7->TRIGGER['VALUE'] = 'DURATION';
$valarm7->TRIGGER['RELATED']= 'END';
$vevent7 = Component::create('VEVENT');
$vevent7->DTSTART = '20120301T130000Z';
$vevent7->DURATION = 'P30D';
$vevent7->add($valarm7);
$tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
$tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
// Relation to end time of event (No DTEND or DURATION)
$valarm7 = Component::create('VALARM');
$valarm7->TRIGGER = '-P1D';
$valarm7->TRIGGER['VALUE'] = 'DURATION';
$valarm7->TRIGGER['RELATED']= 'END';
$vevent7 = Component::create('VEVENT');
$vevent7->DTSTART = '20120301T130000Z';
$vevent7->add($valarm7);
$tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true);
$tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false);
return $tests;
}
/**
* @expectedException LogicException
*/
public function testInTimeRangeInvalidComponent() {
$valarm = Component::create('VALARM');
$valarm->TRIGGER = '-P1D';
$valarm->TRIGGER['RELATED'] = 'END';
$vjournal = Component::create('VJOURNAL');
$vjournal->add($valarm);
$valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
}
}

View file

@ -0,0 +1,244 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
class VCalendarTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider expandData
*/
public function testExpand($input, $output) {
$vcal = VObject\Reader::read($input);
$vcal->expand(
new \DateTime('2011-12-01'),
new \DateTime('2011-12-31')
);
// This will normalize the output
$output = VObject\Reader::read($output)->serialize();
$this->assertEquals($output, $vcal->serialize());
}
public function expandData() {
$tests = array();
// No data
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
END:VCALENDAR
';
$output = $input;
$tests[] = array($input,$output);
// Simple events
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla
SUMMARY:InExpand
DTSTART;VALUE=DATE:20111202
END:VEVENT
BEGIN:VEVENT
UID:bla2
SUMMARY:NotInExpand
DTSTART;VALUE=DATE:20120101
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla
SUMMARY:InExpand
DTSTART;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Removing timezone info
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
END:VTIMEZONE
BEGIN:VEVENT
UID:bla4
SUMMARY:RemoveTZ info
DTSTART;TZID=Europe/Paris:20111203T130102
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla4
SUMMARY:RemoveTZ info
DTSTART;VALUE=DATE-TIME:20111203T120102Z
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
// Recurrence rule
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART:20111125T120000Z
DTEND:20111125T130000Z
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART;VALUE=DATE-TIME:20111202T120000Z
DTEND;VALUE=DATE-TIME:20111202T130000Z
RECURRENCE-ID:20111202T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART;VALUE=DATE-TIME:20111209T120000Z
DTEND;VALUE=DATE-TIME:20111209T130000Z
RECURRENCE-ID:20111209T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART;VALUE=DATE-TIME:20111216T120000Z
DTEND;VALUE=DATE-TIME:20111216T130000Z
RECURRENCE-ID:20111216T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART;VALUE=DATE-TIME:20111223T120000Z
DTEND;VALUE=DATE-TIME:20111223T130000Z
RECURRENCE-ID:20111223T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule
DTSTART;VALUE=DATE-TIME:20111230T120000Z
DTEND;VALUE=DATE-TIME:20111230T130000Z
RECURRENCE-ID:20111230T120000Z
END:VEVENT
END:VCALENDAR
';
// Recurrence rule + override
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART:20111125T120000Z
DTEND:20111125T130000Z
RRULE:FREQ=WEEKLY
END:VEVENT
BEGIN:VEVENT
UID:bla6
RECURRENCE-ID:20111209T120000Z
DTSTART:20111209T140000Z
DTEND:20111209T150000Z
SUMMARY:Override!
END:VEVENT
END:VCALENDAR
';
$output = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART;VALUE=DATE-TIME:20111202T120000Z
DTEND;VALUE=DATE-TIME:20111202T130000Z
RECURRENCE-ID:20111202T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
RECURRENCE-ID:20111209T120000Z
DTSTART:20111209T140000Z
DTEND:20111209T150000Z
SUMMARY:Override!
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART;VALUE=DATE-TIME:20111216T120000Z
DTEND;VALUE=DATE-TIME:20111216T130000Z
RECURRENCE-ID:20111216T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART;VALUE=DATE-TIME:20111223T120000Z
DTEND;VALUE=DATE-TIME:20111223T130000Z
RECURRENCE-ID:20111223T120000Z
END:VEVENT
BEGIN:VEVENT
UID:bla6
SUMMARY:Testing RRule2
DTSTART;VALUE=DATE-TIME:20111230T120000Z
DTEND;VALUE=DATE-TIME:20111230T130000Z
RECURRENCE-ID:20111230T120000Z
END:VEVENT
END:VCALENDAR
';
$tests[] = array($input, $output);
return $tests;
}
/**
* @expectedException LogicException
*/
public function testBrokenEventExpand() {
$input = 'BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
RRULE:FREQ=WEEKLY
DTSTART;VALUE=DATE:20111202
END:VEVENT
END:VCALENDAR
';
$vcal = VObject\Reader::read($input);
$vcal->expand(
new \DateTime('2011-12-01'),
new \DateTime('2011-12-31')
);
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
class VCardTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider validateData
*/
function testValidate($input, $expectedWarnings, $expectedRepairedOutput) {
$vcard = VObject\Reader::read($input);
$warnings = $vcard->validate();
$warnMsg = array();
foreach($warnings as $warning) {
$warnMsg[] = $warning['message'];
}
$this->assertEquals($expectedWarnings, $warnMsg);
$vcard->validate(VObject\Component::REPAIR);
$this->assertEquals(
$expectedRepairedOutput,
$vcard->serialize()
);
}
public function validateData() {
$tests = array();
// Correct
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
array(),
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
);
// No VERSION
$tests[] = array(
"BEGIN:VCARD\r\nFN:John Doe\r\nEND:VCARD\r\n",
array(
'The VERSION property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
);
// Unknown version
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nEND:VCARD\r\n",
array(
'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
);
// No FN
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD\r\n",
);
// No FN, N fallback
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n",
);
// No FN, N fallback, no first name
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n",
);
// No FN, ORG fallback
$tests[] = array(
"BEGIN:VCARD\r\nVERSION:4.0\r\nORG:Acme Co.\r\nEND:VCARD\r\n",
array(
'The FN property must appear in the VCARD component exactly 1 time',
),
"BEGIN:VCARD\r\nVERSION:4.0\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n",
);
return $tests;
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject;
class VEventTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VEvent $vevent,$start,$end,$outcome) {
$this->assertEquals($outcome, $vevent->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$vevent = new VEvent('VEVENT');
$vevent->DTSTART = '20111223T120000Z';
$tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent2 = clone $vevent;
$vevent2->DTEND = '20111225T120000Z';
$tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent3 = clone $vevent;
$vevent3->DURATION = 'P1D';
$tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vevent4 = clone $vevent;
$vevent4->DTSTART = '20111225';
$vevent4->DTSTART['VALUE'] = 'DATE';
$tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
// Event with no end date should be treated as lasting the entire day.
$tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true);
$vevent5 = clone $vevent;
$vevent5->DURATION = 'P1D';
$vevent5->RRULE = 'FREQ=YEARLY';
$tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true);
$vevent6 = clone $vevent;
$vevent6->DTSTART = '20111225';
$vevent6->DTSTART['VALUE'] = 'DATE';
$vevent6->DTEND = '20111225';
$vevent6->DTEND['VALUE'] = 'DATE';
$tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
// Added this test to ensure that recurrence rules with no DTEND also
// get checked for the entire day.
$vevent7 = clone $vevent;
$vevent7->DTSTART = '20120101';
$vevent7->DTSTART['VALUE'] = 'DATE';
$vevent7->RRULE = 'FREQ=MONTHLY';
$tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true);
return $tests;
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject\Component;
class VJournalTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VJournal $vtodo,$start,$end,$outcome) {
$this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$vjournal = Component::create('VJOURNAL');
$vjournal->DTSTART = '20111223T120000Z';
$tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vjournal2 = Component::create('VJOURNAL');
$vjournal2->DTSTART = '20111223';
$vjournal2->DTSTART['VALUE'] = 'DATE';
$tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vjournal3 = Component::create('VJOURNAL');
$tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false);
$tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
return $tests;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Sabre\VObject\Component;
use Sabre\VObject\Component;
class VTodoTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider timeRangeTestData
*/
public function testInTimeRange(VTodo $vtodo,$start,$end,$outcome) {
$this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
}
public function timeRangeTestData() {
$tests = array();
$vtodo = Component::create('VTODO');
$vtodo->DTSTART = '20111223T120000Z';
$tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo2 = clone $vtodo;
$vtodo2->DURATION = 'P1D';
$tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo3 = clone $vtodo;
$vtodo3->DUE = '20111225';
$tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo4 = Component::create('VTODO');
$vtodo4->DUE = '20111225';
$tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo5 = Component::create('VTODO');
$vtodo5->COMPLETED = '20111225';
$tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo6 = Component::create('VTODO');
$vtodo6->CREATED = '20111225';
$tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo7 = Component::create('VTODO');
$vtodo7->CREATED = '20111225';
$vtodo7->COMPLETED = '20111226';
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
$vtodo7 = Component::create('VTODO');
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
$tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true);
return $tests;
}
}

View file

@ -0,0 +1,413 @@
<?php
namespace Sabre\VObject;
class ComponentTest extends \PHPUnit_Framework_TestCase {
function testIterate() {
$comp = new Component('VCALENDAR');
$sub = new Component('VEVENT');
$comp->children[] = $sub;
$sub = new Component('VTODO');
$comp->children[] = $sub;
$count = 0;
foreach($comp->children() as $key=>$subcomponent) {
$count++;
$this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
}
$this->assertEquals(2,$count);
$this->assertEquals(1,$key);
}
function testMagicGet() {
$comp = new Component('VCALENDAR');
$sub = new Component('VEVENT');
$comp->children[] = $sub;
$sub = new Component('VTODO');
$comp->children[] = $sub;
$event = $comp->vevent;
$this->assertInstanceOf('Sabre\\VObject\\Component', $event);
$this->assertEquals('VEVENT', $event->name);
$this->assertInternalType('null', $comp->vjournal);
}
function testMagicGetGroups() {
$comp = new Component('VCARD');
$sub = new Property('GROUP1.EMAIL','1@1.com');
$comp->children[] = $sub;
$sub = new Property('GROUP2.EMAIL','2@2.com');
$comp->children[] = $sub;
$sub = new Property('EMAIL','3@3.com');
$comp->children[] = $sub;
$emails = $comp->email;
$this->assertEquals(3, count($emails));
$email1 = $comp->{"group1.email"};
$this->assertEquals('EMAIL', $email1[0]->name);
$this->assertEquals('GROUP1', $email1[0]->group);
$email3 = $comp->{".email"};
$this->assertEquals('EMAIL', $email3[0]->name);
$this->assertEquals(null, $email3[0]->group);
}
function testMagicIsset() {
$comp = new Component('VCALENDAR');
$sub = new Component('VEVENT');
$comp->children[] = $sub;
$sub = new Component('VTODO');
$comp->children[] = $sub;
$this->assertTrue(isset($comp->vevent));
$this->assertTrue(isset($comp->vtodo));
$this->assertFalse(isset($comp->vjournal));
}
function testMagicSetScalar() {
$comp = new Component('VCALENDAR');
$comp->myProp = 'myValue';
$this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
$this->assertEquals('myValue',$comp->MYPROP->value);
}
function testMagicSetScalarTwice() {
$comp = new Component('VCALENDAR');
$comp->myProp = 'myValue';
$comp->myProp = 'myValue';
$this->assertEquals(1,count($comp->children));
$this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
$this->assertEquals('myValue',$comp->MYPROP->value);
}
function testMagicSetComponent() {
$comp = new Component('VCALENDAR');
// Note that 'myProp' is ignored here.
$comp->myProp = new Component('VEVENT');
$this->assertEquals(1, count($comp->children));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testMagicSetTwice() {
$comp = new Component('VCALENDAR');
$comp->VEVENT = new Component('VEVENT');
$comp->VEVENT = new Component('VEVENT');
$this->assertEquals(1, count($comp->children));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testArrayAccessGet() {
$comp = new Component('VCALENDAR');
$event = new Component('VEVENT');
$event->summary = 'Event 1';
$comp->add($event);
$event2 = clone $event;
$event2->summary = 'Event 2';
$comp->add($event2);
$this->assertEquals(2,count($comp->children()));
$this->assertTrue($comp->vevent[1] instanceof Component);
$this->assertEquals('Event 2', (string)$comp->vevent[1]->summary);
}
function testArrayAccessExists() {
$comp = new Component('VCALENDAR');
$event = new Component('VEVENT');
$event->summary = 'Event 1';
$comp->add($event);
$event2 = clone $event;
$event2->summary = 'Event 2';
$comp->add($event2);
$this->assertTrue(isset($comp->vevent[0]));
$this->assertTrue(isset($comp->vevent[1]));
}
/**
* @expectedException LogicException
*/
function testArrayAccessSet() {
$comp = new Component('VCALENDAR');
$comp['hey'] = 'hi there';
}
/**
* @expectedException LogicException
*/
function testArrayAccessUnset() {
$comp = new Component('VCALENDAR');
unset($comp[0]);
}
function testAddScalar() {
$comp = new Component('VCALENDAR');
$comp->add('myprop','value');
$this->assertEquals(1, count($comp->children));
$this->assertTrue($comp->children[0] instanceof Property);
$this->assertEquals('MYPROP',$comp->children[0]->name);
$this->assertEquals('value',$comp->children[0]->value);
}
function testAddScalarParams() {
$comp = Component::create('VCALENDAR');
$comp->add('myprop','value',array('param1'=>'value1'));
$this->assertEquals(1, count($comp->children));
$this->assertTrue($comp->children[0] instanceof Property);
$this->assertEquals('MYPROP',$comp->children[0]->name);
$this->assertEquals('value',$comp->children[0]->value);
$this->assertEquals(1, count($comp->children[0]->parameters));
$this->assertTrue($comp->children[0]->parameters[0] instanceof Parameter);
$this->assertEquals('PARAM1',$comp->children[0]->parameters[0]->name);
$this->assertEquals('value1',$comp->children[0]->parameters[0]->value);
}
function testAddComponent() {
$comp = new Component('VCALENDAR');
$comp->add(new Component('VEVENT'));
$this->assertEquals(1, count($comp->children));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testAddComponentTwice() {
$comp = new Component('VCALENDAR');
$comp->add(new Component('VEVENT'));
$comp->add(new Component('VEVENT'));
$this->assertEquals(2, count($comp->children));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail() {
$comp = new Component('VCALENDAR');
$comp->add(new Component('VEVENT'),'hello');
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail2() {
$comp = new Component('VCALENDAR');
$comp->add(array());
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail3() {
$comp = new Component('VCALENDAR');
$comp->add('hello',array());
}
/**
* @expectedException InvalidArgumentException
*/
function testMagicSetInvalid() {
$comp = new Component('VCALENDAR');
// Note that 'myProp' is ignored here.
$comp->myProp = new \StdClass();
$this->assertEquals(1, count($comp->children));
$this->assertEquals('VEVENT',$comp->VEVENT->name);
}
function testMagicUnset() {
$comp = new Component('VCALENDAR');
$comp->add(new Component('VEVENT'));
unset($comp->vevent);
$this->assertEquals(array(), $comp->children);
}
function testCount() {
$comp = new Component('VCALENDAR');
$this->assertEquals(1,$comp->count());
}
function testChildren() {
$comp = new Component('VCALENDAR');
// Note that 'myProp' is ignored here.
$comp->children = array(
new Component('VEVENT'),
new Component('VTODO')
);
$r = $comp->children();
$this->assertTrue($r instanceof ElementList);
$this->assertEquals(2,count($r));
}
function testGetComponents() {
$comp = new Component('VCALENDAR');
// Note that 'myProp' is ignored here.
$comp->children = array(
new Property('FOO','BAR'),
new Component('VTODO')
);
$r = $comp->getComponents();
$this->assertInternalType('array', $r);
$this->assertEquals(1, count($r));
$this->assertEquals('VTODO', $r[0]->name);
}
function testSerialize() {
$comp = new Component('VCALENDAR');
$this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
}
function testSerializeChildren() {
$comp = new Component('VCALENDAR');
$comp->children = array(
new Component('VEVENT'),
new Component('VTODO')
);
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str);
}
function testSerializeOrderCompAndProp() {
$comp = new Component('VCALENDAR');
$comp->add(new Component('VEVENT'));
$comp->add('PROP1','BLABLA');
$comp->add('VERSION','2.0');
$comp->add(new Component('VTIMEZONE'));
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str);
}
function testAnotherSerializeOrderProp() {
$prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
$comp = new Component('VCARD');
$comp->__set('SOMEPROP','FOO');
$comp->__set('ANOTHERPROP','FOO');
$comp->__set('THIRDPROP','FOO');
foreach ($prop4s as $prop4) {
$comp->add('PROP4', 'FOO '.$prop4);
}
$comp->__set('PROPNUMBERFIVE', 'FOO');
$comp->__set('PROPNUMBERSIX', 'FOO');
$comp->__set('PROPNUMBERSEVEN', 'FOO');
$comp->__set('PROPNUMBEREIGHT', 'FOO');
$comp->__set('PROPNUMBERNINE', 'FOO');
$comp->__set('PROPNUMBERTEN', 'FOO');
$comp->__set('VERSION','2.0');
$comp->__set('UID', 'FOO');
$str = $comp->serialize();
$this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str);
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Sabre\VObject;
use DateTime;
use DateTimeZone;
use DateInterval;
class DateTimeParserTest extends \PHPUnit_Framework_TestCase {
function testParseICalendarDuration() {
$this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true));
$this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D',true));
$this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true));
$this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true));
$this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true));
$this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S'));
}
function testParseICalendarDurationDateInterval() {
$expected = new DateInterval('P7D');
$this->assertEquals($expected, DateTimeParser::parseDuration('P1W'));
$this->assertEquals($expected, DateTimeParser::parse('P1W'));
$expected = new DateInterval('PT3M');
$expected->invert = true;
$this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M'));
}
/**
* @expectedException LogicException
*/
function testParseICalendarDurationFail() {
DateTimeParser::parseDuration('P1X',true);
}
function testParseICalendarDateTime() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405');
$compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
* @expectedException LogicException
*/
function testParseICalendarDateTimeBadFormat() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405 ');
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeUTC() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405Z');
$compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeUTC2() {
$dateTime = DateTimeParser::parseDateTime('20101211T160000Z');
$compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
/**
* @depends testParseICalendarDateTime
*/
function testParseICalendarDateTimeCustomTimeZone() {
$dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
$compare = new DateTime('2010-03-16 13:14:05',new DateTimeZone('UTC'));
$this->assertEquals($compare, $dateTime);
}
function testParseICalendarDate() {
$dateTime = DateTimeParser::parseDate('20100316');
$expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC'));
$this->assertEquals($expected, $dateTime);
$dateTime = DateTimeParser::parse('20100316');
$this->assertEquals($expected, $dateTime);
}
/**
* @depends testParseICalendarDate
* @expectedException LogicException
*/
function testParseICalendarDateBadFormat() {
$dateTime = DateTimeParser::parseDate('20100316T141405');
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Sabre\VObject;
class ElementListTest extends \PHPUnit_Framework_TestCase {
function testIterate() {
$sub = new Component('VEVENT');
$elems = array(
$sub,
clone $sub,
clone $sub
);
$elemList = new ElementList($elems);
$count = 0;
foreach($elemList as $key=>$subcomponent) {
$count++;
$this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
}
$this->assertEquals(3,$count);
$this->assertEquals(2,$key);
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Sabre\VObject;
class EmClientTest extends \PHPUnit_Framework_TestCase {
function testParseTz() {
$str = 'BEGIN:VCALENDAR
X-WR-CALNAME:Blackhawks Schedule 2011-12
X-APPLE-CALENDAR-COLOR:#E51717
X-WR-TIMEZONE:America/Chicago
CALSCALE:GREGORIAN
PRODID:-//eM Client/4.0.13961.0
VERSION:2.0
BEGIN:VTIMEZONE
TZID:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
DTSTART:20070311T020000
TZNAME:CDT
TZOFFSETTO:-0500
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
DTSTART:20071104T020000
TZNAME:CST
TZOFFSETTO:-0600
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20110624T181236Z
UID:be3bbfff-96e8-4c66-9908-ab791a62231d
DTEND;TZID="America/Chicago":20111008T223000
TRANSP:OPAQUE
SUMMARY:Stars @ Blackhawks (Home Opener)
DTSTART;TZID="America/Chicago":20111008T193000
DTSTAMP:20120330T013232Z
SEQUENCE:2
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
LAST-MODIFIED:20120330T013237Z
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR';
$vObject = Reader::read($str);
$dt = $vObject->VEVENT->DTSTART->getDateTime();
$this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt);
}
}

View file

@ -0,0 +1,246 @@
<?php
namespace Sabre\VObject;
class FreeBusyGeneratorTest extends \PHPUnit_Framework_TestCase {
function getInput() {
// shows up
$blob1 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T120000Z
DTEND:20110101T130000Z
END:VEVENT
END:VCALENDAR
ICS;
// opaque, shows up
$blob2 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
TRANSP:OPAQUE
DTSTART:20110101T130000Z
DTEND:20110101T140000Z
END:VEVENT
END:VCALENDAR
ICS;
// transparent, hidden
$blob3 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
TRANSP:TRANSPARENT
DTSTART:20110101T140000Z
DTEND:20110101T150000Z
END:VEVENT
END:VCALENDAR
ICS;
// cancelled, hidden
$blob4 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
STATUS:CANCELLED
DTSTART:20110101T160000Z
DTEND:20110101T170000Z
END:VEVENT
END:VCALENDAR
ICS;
// tentative, shows up
$blob5 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
STATUS:TENTATIVE
DTSTART:20110101T180000Z
DTEND:20110101T190000Z
END:VEVENT
END:VCALENDAR
ICS;
// outside of time-range, hidden
$blob6 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T090000Z
DTEND:20110101T100000Z
END:VEVENT
END:VCALENDAR
ICS;
// outside of time-range, hidden
$blob7 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110104T090000Z
DTEND:20110104T100000Z
END:VEVENT
END:VCALENDAR
ICS;
// using duration, shows up
$blob8 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T190000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
// Day-long event, shows up
$blob9 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;TYPE=DATE:20110102
END:VEVENT
END:VCALENDAR
ICS;
// No duration, does not show up
$blob10 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T200000Z
END:VEVENT
END:VCALENDAR
ICS;
// encoded as object, shows up
$blob11 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20110101T210000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
ICS;
// Freebusy. Some parts show up
$blob12 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VFREEBUSY
FREEBUSY:20110103T010000Z/20110103T020000Z
FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z
FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z
FREEBUSY:20120101T000000Z/20120101T010000Z
FREEBUSY:20110103T050000Z/PT1H
END:VFREEBUSY
END:VCALENDAR
ICS;
// Yearly recurrence rule, shows up
$blob13 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20100101T220000Z
DTEND:20100101T230000Z
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
ICS;
// Yearly recurrence rule + duration, shows up
$blob14 = <<<ICS
BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20100101T230000Z
DURATION:PT1H
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR
ICS;
return array(
$blob1,
$blob2,
$blob3,
$blob4,
$blob5,
$blob6,
$blob7,
$blob8,
$blob9,
$blob10,
Reader::read($blob11),
$blob12,
$blob13,
$blob14,
);
}
function testGenerator() {
$gen = new FreeBusyGenerator(
new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')),
new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')),
$this->getInput()
);
$result = $gen->getResult();
$expected = array(
'20110101T120000Z/20110101T130000Z',
'20110101T130000Z/20110101T140000Z',
'20110101T180000Z/20110101T190000Z',
'20110101T190000Z/20110101T200000Z',
'20110102T000000Z/20110103T000000Z',
'20110101T210000Z/20110101T220000Z',
'20110103T010000Z/20110103T020000Z',
'20110103T030000Z/20110103T040000Z',
'20110103T040000Z/20110103T050000Z',
'20110103T050000Z/20110103T060000Z',
'20110101T220000Z/20110101T230000Z',
'20110101T230000Z/20110102T000000Z',
);
foreach($result->VFREEBUSY->FREEBUSY as $fb) {
$this->assertContains((string)$fb, $expected);
$k = array_search((string)$fb, $expected);
unset($expected[$k]);
}
if (count($expected)>0) {
$this->fail('There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize());
}
}
function testGeneratorBaseObject() {
$obj = new Component('VCALENDAR');
$obj->METHOD = 'PUBLISH';
$gen = new FreeBusyGenerator();
$gen->setObjects(array());
$gen->setBaseObject($obj);
$result = $gen->getResult();
$this->assertEquals('PUBLISH', $result->METHOD->value);
}
/**
* @expectedException InvalidArgumentException
*/
function testInvalidArg() {
$gen = new FreeBusyGenerator(
new \DateTime('2012-01-01'),
new \DateTime('2012-12-31'),
new \StdClass()
);
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Sabre\VObject;
class Issue153Test extends \PHPUnit_Framework_TestCase {
function testRead() {
$obj = Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf'));
$this->assertEquals('Test Benutzer', (string)$obj->fn);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Sabre\VObject;
class Issue154Test extends \PHPUnit_Framework_TestCase {
function testStuff() {
$vcard = new Component('VCARD');
$vcard->VERSION = '3.0';
$vcard->PHOTO = base64_encode('random_stuff');
$vcard->PHOTO->add('BASE64',null);
$vcard->UID = 'foo-bar';
$result = $vcard->serialize();
$expected = array(
"BEGIN:VCARD",
"VERSION:3.0",
"PHOTO;BASE64:" . base64_encode('random_stuff'),
"UID:foo-bar",
"END:VCARD",
"",
);
$this->assertEquals(implode("\r\n", $expected), $result);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Sabre\VObject;
class ParameterTest extends \PHPUnit_Framework_TestCase {
function testSetup() {
$param = new Parameter('name','value');
$this->assertEquals('NAME',$param->name);
$this->assertEquals('value',$param->value);
}
function testCastToString() {
$param = new Parameter('name','value');
$this->assertEquals('value',$param->__toString());
$this->assertEquals('value',(string)$param);
}
}

View file

@ -0,0 +1,234 @@
<?php
namespace Sabre\VObject\Property;
use Sabre\VObject\Component;
class DateTimeTest extends \PHPUnit_Framework_TestCase {
function testSetDateTime() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals('19850704T013000', $elem->value);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeLOCAL() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt, DateTime::LOCAL);
$this->assertEquals('19850704T013000', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeUTC() {
$tz = new \DateTimeZone('GMT');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt, DateTime::UTC);
$this->assertEquals('19850704T013000Z', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeLOCALTZ() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt, DateTime::LOCALTZ);
$this->assertEquals('19850704T013000', $elem->value);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeDATE() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt, DateTime::DATE);
$this->assertEquals('19850704', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE', (string)$elem['VALUE']);
}
/**
* @expectedException InvalidArgumentException
*/
function testSetDateTimeInvalid() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt, 7);
}
function testGetDateTimeCached() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new DateTime('DTSTART');
$elem->setDateTime($dt);
$this->assertEquals($elem->getDateTime(), $dt);
}
function testGetDateTimeDateNULL() {
$elem = new DateTime('DTSTART');
$dt = $elem->getDateTime();
$this->assertNull($dt);
$this->assertNull($elem->getDateType());
}
function testGetDateTimeDateDATE() {
$elem = new DateTime('DTSTART','19850704');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals(DateTime::DATE, $elem->getDateType());
}
function testGetDateTimeDateLOCAL() {
$elem = new DateTime('DTSTART','19850704T013000');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals(DateTime::LOCAL, $elem->getDateType());
}
function testGetDateTimeDateUTC() {
$elem = new DateTime('DTSTART','19850704T013000Z');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('UTC', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::UTC, $elem->getDateType());
}
function testGetDateTimeDateLOCALTZ() {
$elem = new DateTime('DTSTART','19850704T013000');
$elem['TZID'] = 'Europe/Amsterdam';
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
}
/**
* @expectedException InvalidArgumentException
*/
function testGetDateTimeDateInvalid() {
$elem = new DateTime('DTSTART','bla');
$dt = $elem->getDateTime();
}
function testGetDateTimeWeirdTZ() {
$elem = new DateTime('DTSTART','19850704T013000');
$elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
$event = new Component('VEVENT');
$event->add($elem);
$timezone = new Component('VTIMEZONE');
$timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
$timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam';
$calendar = new Component('VCALENDAR');
$calendar->add($event);
$calendar->add($timezone);
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
}
function testGetDateTimeBadTimeZone() {
$default = date_default_timezone_get();
date_default_timezone_set('Canada/Eastern');
$elem = new DateTime('DTSTART','19850704T013000');
$elem['TZID'] = 'Moon';
$event = new Component('VEVENT');
$event->add($elem);
$timezone = new Component('VTIMEZONE');
$timezone->TZID = 'Moon';
$timezone->{'X-LIC-LOCATION'} = 'Moon';
$calendar = new Component('VCALENDAR');
$calendar->add($event);
$calendar->add($timezone);
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
date_default_timezone_set($default);
}
}

View file

@ -0,0 +1,202 @@
<?php
namespace Sabre\VObject\Property;
class MultiDateTimeTest extends \PHPUnit_Framework_TestCase {
function testSetDateTime() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2));
$this->assertEquals('19850704T013000,19860704T013000', $elem->value);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeLOCAL() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2), DateTime::LOCAL);
$this->assertEquals('19850704T013000,19860704T013000', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeUTC() {
$tz = new \DateTimeZone('GMT');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2), DateTime::UTC);
$this->assertEquals('19850704T013000Z,19860704T013000Z', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeLOCALTZ() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->setTimeZone($tz);
$dt2->setTimeZone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2), DateTime::LOCALTZ);
$this->assertEquals('19850704T013000,19860704T013000', $elem->value);
$this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
$this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
}
function testSetDateTimeDATE() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->settimezone($tz);
$dt2->settimezone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2), DateTime::DATE);
$this->assertEquals('19850704,19860704', $elem->value);
$this->assertNull($elem['TZID']);
$this->assertEquals('DATE', (string)$elem['VALUE']);
}
/**
* @expectedException InvalidArgumentException
*/
function testSetDateTimeInvalid() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt = new \DateTime('1985-07-04 01:30:00', $tz);
$dt->setTimeZone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt), 7);
}
function testGetDateTimeCached() {
$tz = new \DateTimeZone('Europe/Amsterdam');
$dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
$dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
$dt1->settimezone($tz);
$dt2->settimezone($tz);
$elem = new MultiDateTime('DTSTART');
$elem->setDateTimes(array($dt1,$dt2));
$this->assertEquals($elem->getDateTimes(), array($dt1,$dt2));
}
function testGetDateTimeDateNULL() {
$elem = new MultiDateTime('DTSTART');
$dt = $elem->getDateTimes();
$this->assertNull($dt);
$this->assertNull($elem->getDateType());
}
function testGetDateTimeDateDATE() {
$elem = new MultiDateTime('DTSTART','19850704,19860704');
$dt = $elem->getDateTimes();
$this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
$this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
$this->assertEquals(DateTime::DATE, $elem->getDateType());
}
function testGetDateTimeDateDATEReverse() {
$elem = new MultiDateTime('DTSTART','19850704,19860704');
$this->assertEquals(DateTime::DATE, $elem->getDateType());
$dt = $elem->getDateTimes();
$this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
$this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
}
function testGetDateTimeDateLOCAL() {
$elem = new DateTime('DTSTART','19850704T013000');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals(DateTime::LOCAL, $elem->getDateType());
}
function testGetDateTimeDateUTC() {
$elem = new DateTime('DTSTART','19850704T013000Z');
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('UTC', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::UTC, $elem->getDateType());
}
function testGetDateTimeDateLOCALTZ() {
$elem = new DateTime('DTSTART','19850704T013000');
$elem['TZID'] = 'Europe/Amsterdam';
$dt = $elem->getDateTime();
$this->assertInstanceOf('DateTime', $dt);
$this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
$this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
$this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
}
/**
* @expectedException InvalidArgumentException
*/
function testGetDateTimeDateInvalid() {
$elem = new DateTime('DTSTART','bla');
$dt = $elem->getDateTime();
}
}

View file

@ -0,0 +1,293 @@
<?php
namespace Sabre\VObject;
class PropertyTest extends \PHPUnit_Framework_TestCase {
public function testToString() {
$property = new Property('propname','propvalue');
$this->assertEquals('PROPNAME', $property->name);
$this->assertEquals('propvalue', $property->value);
$this->assertEquals('propvalue', $property->__toString());
$this->assertEquals('propvalue', (string)$property);
}
public function testParameterExists() {
$property = new Property('propname','propvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$this->assertTrue(isset($property['PARAMNAME']));
$this->assertTrue(isset($property['paramname']));
$this->assertFalse(isset($property['foo']));
}
public function testParameterGet() {
$property = new Property('propname','propvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
}
public function testParameterNotExists() {
$property = new Property('propname','propvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$this->assertInternalType('null',$property['foo']);
}
public function testParameterMultiple() {
$property = new Property('propname','propvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
$this->assertEquals(2,count($property['paramname']));
}
public function testSetParameterAsString() {
$property = new Property('propname','propvalue');
$property['paramname'] = 'paramvalue';
$this->assertEquals(1,count($property->parameters));
$this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters[0]);
$this->assertEquals('PARAMNAME',$property->parameters[0]->name);
$this->assertEquals('paramvalue',$property->parameters[0]->value);
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetParameterAsStringNoKey() {
$property = new Property('propname','propvalue');
$property[] = 'paramvalue';
}
public function testSetParameterObject() {
$property = new Property('propname','propvalue');
$param = new Parameter('paramname','paramvalue');
$property[] = $param;
$this->assertEquals(1,count($property->parameters));
$this->assertEquals($param, $property->parameters[0]);
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetParameterObjectWithKey() {
$property = new Property('propname','propvalue');
$param = new Parameter('paramname','paramvalue');
$property['key'] = $param;
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetParameterObjectRandomObject() {
$property = new Property('propname','propvalue');
$property[] = new \StdClass();
}
public function testUnsetParameter() {
$property = new Property('propname','propvalue');
$param = new Parameter('paramname','paramvalue');
$property->parameters[] = $param;
unset($property['PARAMNAME']);
$this->assertEquals(0,count($property->parameters));
}
public function testParamCount() {
$property = new Property('propname','propvalue');
$param = new Parameter('paramname','paramvalue');
$property->parameters[] = $param;
$property->parameters[] = clone $param;
$this->assertEquals(2,count($property->parameters));
}
public function testSerialize() {
$property = new Property('propname','propvalue');
$this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize());
}
public function testSerializeParam() {
$property = new Property('propname','propvalue');
$property->parameters[] = new Parameter('paramname','paramvalue');
$property->parameters[] = new Parameter('paramname2','paramvalue2');
$this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize());
}
public function testSerializeNewLine() {
$property = new Property('propname',"line1\nline2");
$this->assertEquals("PROPNAME:line1\\nline2\r\n",$property->serialize());
}
public function testSerializeLongLine() {
$value = str_repeat('!',200);
$property = new Property('propname',$value);
$expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n";
$this->assertEquals($expected,$property->serialize());
}
public function testSerializeUTF8LineFold() {
$value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a
$property = new Property('propname', $value);
$expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n";
$this->assertEquals($expected, $property->serialize());
}
public function testGetIterator() {
$it = new ElementList(array());
$property = new Property('propname','propvalue');
$property->setIterator($it);
$this->assertEquals($it,$property->getIterator());
}
public function testGetIteratorDefault() {
$property = new Property('propname','propvalue');
$it = $property->getIterator();
$this->assertTrue($it instanceof ElementList);
$this->assertEquals(1,count($it));
}
function testAddScalar() {
$property = new Property('EMAIL');
$property->add('myparam','value');
$this->assertEquals(1, count($property->parameters));
$this->assertTrue($property->parameters[0] instanceof Parameter);
$this->assertEquals('MYPARAM',$property->parameters[0]->name);
$this->assertEquals('value',$property->parameters[0]->value);
}
function testAddParameter() {
$prop = new Property('EMAIL');
$prop->add(new Parameter('MYPARAM','value'));
$this->assertEquals(1, count($prop->parameters));
$this->assertEquals('MYPARAM',$prop['myparam']->name);
}
function testAddParameterTwice() {
$prop = new Property('EMAIL');
$prop->add(new Parameter('MYPARAM', 'value1'));
$prop->add(new Parameter('MYPARAM', 'value2'));
$this->assertEquals(2, count($prop->parameters));
$this->assertEquals('MYPARAM',$prop['MYPARAM']->name);
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail() {
$prop = new Property('EMAIL');
$prop->add(new Parameter('MPARAM'),'hello');
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail2() {
$property = new Property('EMAIL','value');
$property->add(array());
}
/**
* @expectedException InvalidArgumentException
*/
function testAddArgFail3() {
$property = new Property('EMAIL','value');
$property->add('hello',array());
}
function testClone() {
$property = new Property('EMAIL','value');
$property['FOO'] = 'BAR';
$property2 = clone $property;
$property['FOO'] = 'BAZ';
$this->assertEquals('BAR', (string)$property2['FOO']);
}
function testCreateParams() {
$property = Property::create('X-PROP', 'value', array(
'param1' => 'value1',
'param2' => array('value2', 'value3')
));
$this->assertEquals(1, count($property['PARAM1']));
$this->assertEquals(2, count($property['PARAM2']));
}
}

View file

@ -0,0 +1,265 @@
<?php
namespace Sabre\VObject;
class ReaderTest extends \PHPUnit_Framework_TestCase {
function testReadComponent() {
$data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadComponentUnixNewLine() {
$data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadComponentMacNewLine() {
$data = "BEGIN:VCALENDAR\rEND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
function testReadComponentLineFold() {
$data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(0, count($result->children));
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testReadCorruptComponent() {
$data = "BEGIN:VCALENDAR\r\nEND:FOO";
$result = Reader::read($data);
}
function testReadProperty() {
$data = "PROPNAME:propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
}
function testReadPropertyWithNewLine() {
$data = 'PROPNAME:Line1\\nLine2\\NLine3\\\\Not the 4th line!';
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->value);
}
function testReadMappedProperty() {
$data = "DTSTART:20110529";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property\\DateTime', $result);
$this->assertEquals('DTSTART', $result->name);
$this->assertEquals('20110529', $result->value);
}
function testReadMappedPropertyGrouped() {
$data = "foo.DTSTART:20110529";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property\\DateTime', $result);
$this->assertEquals('DTSTART', $result->name);
$this->assertEquals('20110529', $result->value);
}
/**
* @expectedException Sabre\VObject\ParseException
*/
function testReadBrokenLine() {
$data = "PROPNAME;propValue";
$result = Reader::read($data);
}
function testReadPropertyInComponent() {
$data = array(
"BEGIN:VCALENDAR",
"PROPNAME:propValue",
"END:VCALENDAR"
);
$result = Reader::read(implode("\r\n",$data));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(1, count($result->children));
$this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]);
$this->assertEquals('PROPNAME', $result->children[0]->name);
$this->assertEquals('propValue', $result->children[0]->value);
}
function testReadNestedComponent() {
$data = array(
"BEGIN:VCALENDAR",
"BEGIN:VTIMEZONE",
"BEGIN:DAYLIGHT",
"END:DAYLIGHT",
"END:VTIMEZONE",
"END:VCALENDAR"
);
$result = Reader::read(implode("\r\n",$data));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result);
$this->assertEquals('VCALENDAR', $result->name);
$this->assertEquals(1, count($result->children));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]);
$this->assertEquals('VTIMEZONE', $result->children[0]->name);
$this->assertEquals(1, count($result->children[0]->children));
$this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]);
$this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name);
}
function testReadPropertyParameter() {
$data = "PROPNAME;PARAMNAME=paramvalue:propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('paramvalue', $result->parameters[0]->value);
}
function testReadPropertyNoValue() {
$data = "PROPNAME;PARAMNAME:propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('', $result->parameters[0]->value);
}
function testReadPropertyParameterExtraColon() {
$data = "PROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue:anotherrandomstring', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('paramvalue', $result->parameters[0]->value);
}
function testReadProperty2Parameters() {
$data = "PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(2, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('paramvalue', $result->parameters[0]->value);
$this->assertEquals('PARAMNAME2', $result->parameters[1]->name);
$this->assertEquals('paramvalue2', $result->parameters[1]->value);
}
function testReadPropertyParameterQuoted() {
$data = "PROPNAME;PARAMNAME=\"paramvalue\":propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('paramvalue', $result->parameters[0]->value);
}
function testReadPropertyParameterNewLines() {
$data = "PROPNAME;PARAMNAME=paramvalue1\\nvalue2\\\\nvalue3:propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals("paramvalue1\nvalue2\\nvalue3", $result->parameters[0]->value);
}
function testReadPropertyParameterQuotedColon() {
$data = "PROPNAME;PARAMNAME=\"param:value\":propValue";
$result = Reader::read($data);
$this->assertInstanceOf('Sabre\\VObject\\Property', $result);
$this->assertEquals('PROPNAME', $result->name);
$this->assertEquals('propValue', $result->value);
$this->assertEquals(1, count($result->parameters));
$this->assertEquals('PARAMNAME', $result->parameters[0]->name);
$this->assertEquals('param:value', $result->parameters[0]->value);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Sabre\VObject;
class RecurrenceIteratorFifthTuesdayProblemTest extends \PHPUnit_Framework_TestCase {
function testGetDTEnd() {
$ics = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND;TZID=America/New_York:20070925T170000
UID:uuid
DTSTAMP:19700101T000000Z
LOCATION:
DESCRIPTION:
STATUS:CONFIRMED
SEQUENCE:18
SUMMARY:Stuff
DTSTART;TZID=America/New_York:20070925T160000
CREATED:20071004T144642Z
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU
END:VEVENT
END:VCALENDAR
ICS;
$vObject = Reader::read($ics);
$it = new RecurrenceIterator($vObject, (string)$vObject->VEVENT->UID);
while($it->valid()) {
$it->next();
}
// If we got here, it means we were successful. The bug that was in teh
// system before would fail on the 5th tuesday of the month, if the 5th
// tuesday did not exist.
}
}
?>

View file

@ -0,0 +1,68 @@
<?php
namespace Sabre\VObject;
use DateTime;
use DateTimeZone;
class RecurrenceIteratorInfiniteLoopProblemTest extends \PHPUnit_Framework_TestCase {
/**
* This bug came from a Fruux customer. This would result in a never-ending
* request.
*/
function testFastForwardTooFar() {
$ev = Component::create('VEVENT');
$ev->DTSTART = '20090420T180000Z';
$ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1';
$this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00')));
}
/**
* Different bug, also likely an infinite loop.
*/
function testYearlyByMonthLoop() {
$ev = Component::create('VEVENT');
$ev->UID = 'uuid';
$ev->DTSTART = '20120101T154500';
$ev->DTSTART['TZID'] = 'Europe/Berlin';
$ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
$ev->DTEND = '20120101T164500';
$ev->DTEND['TZID'] = 'Europe/Berlin';
// This recurrence rule by itself is a yearly rule that should happen
// every february.
//
// The BYDAY part expands this to every day of the month, but the
// BYSETPOS limits this to only the 1st day of the month. Very crazy
// way to specify this, and could have certainly been a lot easier.
$cal = Component::create('VCALENDAR');
$cal->add($ev);
$it = new RecurrenceIterator($cal,'uuid');
$it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
$collect = array();
while($it->valid()) {
$collect[] = $it->getDTSTART();
if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
break;
}
$it->next();
}
$this->assertEquals(
array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))),
$collect
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,155 @@
<?php
namespace Sabre\VObject;
class TimezoneUtilTest extends \PHPUnit_Framework_TestCase {
/**
* @dataProvider getMapping
*/
function testCorrectTZ($timezoneName) {
$tz = new \DateTimeZone($timezoneName);
}
function getMapping() {
// PHPUNit requires an array of arrays
return array_map(
function($value) {
return array($value);
},
TimeZoneUtil::$map
);
}
function testExchangeMap() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
X-MICROSOFT-CDO-TZID:2
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$this->assertEquals(new \DateTimeZone('Europe/Sarajevo'), $tz);
}
function testUnknownExchangeId() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
X-MICROSOFT-CDO-TZID:2000
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $tz);
}
function testWindowsTimeZone() {
$tz = TimeZoneUtil::getTimeZone('Eastern Standard Time');
$this->assertEquals(new \DateTimeZone('America/New_York'), $tz);
}
function testFallBack() {
$vobj = <<<HI
BEGIN:VCALENDAR
METHOD:REQUEST
VERSION:2.0
BEGIN:VTIMEZONE
TZID:foo
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20120416T092149Z
DTSTART;TZID="foo":20120418T1
00000
SUMMARY:Begin Unterhaltsreinigung
UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
0100000008FECD2E607780649BE5A4C9EE6418CBC
000
END:VEVENT
END:VCALENDAR
HI;
$tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
$this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $tz);
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Sabre\VObject;
class VersionTest extends \PHPUnit_Framework_TestCase {
function testString() {
$v = Version::VERSION;
$this->assertEquals(-1, version_compare('0.9.0',$v));
$s = Version::STABILITY;
$this->assertTrue($s == 'alpha' || $s == 'beta' || $s =='stable');
}
}

View file

@ -0,0 +1,352 @@
BEGIN:VCARD
VERSION:3.0
N:Benutzer;Test;;;
FN:Test Benutzer
PHOTO;BASE64:
/9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
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
zbVbk4/OrNpefLsnyyg5UUAf/9k=
END:VCARD

View file

@ -0,0 +1,4 @@
<?php
// Composer autoloader
include __DIR__ . '/../vendor/autoload.php';

View file

@ -0,0 +1,17 @@
<phpunit
colors="true"
bootstrap="bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuite name="Sabre_VObject">
<directory>Sabre/</directory>
</testsuite>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../lib/</directory>
</whitelist>
</filter>
</phpunit>