Ver Fonte

Updating vendors

Julio Montoya há 10 anos atrás
pai
commit
d9693efd3c
100 ficheiros alterados com 17189 adições e 0 exclusões
  1. 4 0
      vendor/doctrine/cache/.coveralls.yml
  2. 4 0
      vendor/doctrine/cache/.gitignore
  3. 3 0
      vendor/doctrine/cache/build.properties
  4. 110 0
      vendor/doctrine/cache/build.xml
  5. 191 0
      vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php
  6. 25 0
      vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php
  7. 40 0
      vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php
  8. 61 0
      vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php
  9. 6 0
      vendor/doctrine/cache/tests/travis/php.ini
  10. 35 0
      vendor/doctrine/cache/tests/travis/phpunit.travis.xml
  11. 34 0
      vendor/monolog/monolog/src/Monolog/Handler/ExceptionTestHandler.php
  12. 126 0
      vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php
  13. 68 0
      vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php
  14. 187 0
      vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php
  15. 57 0
      vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php
  16. 87 0
      vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php
  17. 133 0
      vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php
  18. 107 0
      vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
  19. 45 0
      vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php
  20. 761 0
      vendor/sabre/vobject/lib/Cli.php
  21. 587 0
      vendor/sabre/vobject/lib/Component.php
  22. 136 0
      vendor/sabre/vobject/lib/Component/VAlarm.php
  23. 423 0
      vendor/sabre/vobject/lib/Component/VCalendar.php
  24. 431 0
      vendor/sabre/vobject/lib/Component/VCard.php
  25. 140 0
      vendor/sabre/vobject/lib/Component/VEvent.php
  26. 102 0
      vendor/sabre/vobject/lib/Component/VFreeBusy.php
  27. 90 0
      vendor/sabre/vobject/lib/Component/VJournal.php
  28. 53 0
      vendor/sabre/vobject/lib/Component/VTimeZone.php
  29. 176 0
      vendor/sabre/vobject/lib/Component/VTodo.php
  30. 417 0
      vendor/sabre/vobject/lib/DateTimeParser.php
  31. 261 0
      vendor/sabre/vobject/lib/Document.php
  32. 172 0
      vendor/sabre/vobject/lib/ElementList.php
  33. 15 0
      vendor/sabre/vobject/lib/EofException.php
  34. 322 0
      vendor/sabre/vobject/lib/FreeBusyGenerator.php
  35. 937 0
      vendor/sabre/vobject/lib/ITip/Broker.php
  36. 15 0
      vendor/sabre/vobject/lib/ITip/ITipException.php
  37. 141 0
      vendor/sabre/vobject/lib/ITip/Message.php
  38. 18 0
      vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php
  39. 205 0
      vendor/sabre/vobject/lib/Node.php
  40. 373 0
      vendor/sabre/vobject/lib/Parameter.php
  41. 13 0
      vendor/sabre/vobject/lib/ParseException.php
  42. 194 0
      vendor/sabre/vobject/lib/Parser/Json.php
  43. 616 0
      vendor/sabre/vobject/lib/Parser/MimeDir.php
  44. 77 0
      vendor/sabre/vobject/lib/Parser/Parser.php
  45. 518 0
      vendor/sabre/vobject/lib/Property.php
  46. 127 0
      vendor/sabre/vobject/lib/Property/Binary.php
  47. 63 0
      vendor/sabre/vobject/lib/Property/Boolean.php
  48. 49 0
      vendor/sabre/vobject/lib/Property/FlatText.php
  49. 104 0
      vendor/sabre/vobject/lib/Property/Float.php
  50. 61 0
      vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php
  51. 18 0
      vendor/sabre/vobject/lib/Property/ICalendar/Date.php
  52. 362 0
      vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php
  53. 86 0
      vendor/sabre/vobject/lib/Property/ICalendar/Duration.php
  54. 129 0
      vendor/sabre/vobject/lib/Property/ICalendar/Period.php
  55. 203 0
      vendor/sabre/vobject/lib/Property/ICalendar/Recur.php
  56. 72 0
      vendor/sabre/vobject/lib/Property/Integer.php
  57. 333 0
      vendor/sabre/vobject/lib/Property/Text.php
  58. 94 0
      vendor/sabre/vobject/lib/Property/Time.php
  59. 50 0
      vendor/sabre/vobject/lib/Property/Unknown.php
  60. 95 0
      vendor/sabre/vobject/lib/Property/Uri.php
  61. 37 0
      vendor/sabre/vobject/lib/Property/UtcOffset.php
  62. 33 0
      vendor/sabre/vobject/lib/Property/VCard/Date.php
  63. 317 0
      vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php
  64. 33 0
      vendor/sabre/vobject/lib/Property/VCard/DateTime.php
  65. 59 0
      vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php
  66. 69 0
      vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php
  67. 73 0
      vendor/sabre/vobject/lib/Reader.php
  68. 477 0
      vendor/sabre/vobject/lib/Recur/EventIterator.php
  69. 18 0
      vendor/sabre/vobject/lib/Recur/NoInstancesException.php
  70. 174 0
      vendor/sabre/vobject/lib/Recur/RDateIterator.php
  71. 897 0
      vendor/sabre/vobject/lib/Recur/RRuleIterator.php
  72. 21 0
      vendor/sabre/vobject/lib/RecurrenceIterator.php
  73. 116 0
      vendor/sabre/vobject/lib/Splitter/ICalendar.php
  74. 39 0
      vendor/sabre/vobject/lib/Splitter/SplitterInterface.php
  75. 79 0
      vendor/sabre/vobject/lib/Splitter/VCard.php
  76. 70 0
      vendor/sabre/vobject/lib/StringUtil.php
  77. 265 0
      vendor/sabre/vobject/lib/TimeZoneUtil.php
  78. 67 0
      vendor/sabre/vobject/lib/UUIDUtil.php
  79. 424 0
      vendor/sabre/vobject/lib/VCardConverter.php
  80. 19 0
      vendor/sabre/vobject/lib/Version.php
  81. 93 0
      vendor/sabre/vobject/lib/timezonedata/exchangezones.php
  82. 101 0
      vendor/sabre/vobject/lib/timezonedata/lotuszones.php
  83. 153 0
      vendor/sabre/vobject/lib/timezonedata/php-bc.php
  84. 45 0
      vendor/sabre/vobject/lib/timezonedata/php-workaround.php
  85. 114 0
      vendor/sabre/vobject/lib/timezonedata/windowszones.php
  86. 22 0
      vendor/sabre/vobject/tests/VObject/AttachIssueTest.php
  87. 651 0
      vendor/sabre/vobject/tests/VObject/CliTest.php
  88. 179 0
      vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php
  89. 474 0
      vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php
  90. 176 0
      vendor/sabre/vobject/tests/VObject/Component/VCardTest.php
  91. 76 0
      vendor/sabre/vobject/tests/VObject/Component/VEventTest.php
  92. 66 0
      vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php
  93. 101 0
      vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php
  94. 34 0
      vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php
  95. 180 0
      vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php
  96. 528 0
      vendor/sabre/vobject/tests/VObject/ComponentTest.php
  97. 389 0
      vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php
  98. 70 0
      vendor/sabre/vobject/tests/VObject/DocumentTest.php
  99. 33 0
      vendor/sabre/vobject/tests/VObject/ElementListTest.php
  100. 55 0
      vendor/sabre/vobject/tests/VObject/EmClientTest.php

+ 4 - 0
vendor/doctrine/cache/.coveralls.yml

@@ -0,0 +1,4 @@
+# for php-coveralls
+service_name: travis-ci
+src_dir: lib
+coverage_clover: build/logs/clover.xml

+ 4 - 0
vendor/doctrine/cache/.gitignore

@@ -0,0 +1,4 @@
+vendor/
+build/
+phpunit.xml
+composer.lock

+ 3 - 0
vendor/doctrine/cache/build.properties

@@ -0,0 +1,3 @@
+# Version class and file
+project.version_class = Doctrine\\Common\\Cache\\Version
+project.version_file = lib/Doctrine/Common/Cache/Version.php

+ 110 - 0
vendor/doctrine/cache/build.xml

@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<project name="DoctrineCommonCache" default="build" basedir=".">
+    <property file="build.properties" />
+
+    <target name="php">
+        <exec executable="which" outputproperty="php_executable">
+            <arg value="php" />
+        </exec>
+    </target>
+
+    <target name="prepare">
+        <mkdir dir="build" />
+    </target>
+
+    <target name="build" depends="check-git-checkout-clean,prepare,php,composer">
+        <exec executable="${php_executable}">
+            <arg value="build/composer.phar" />
+            <arg value="archive" />
+            <arg value="--dir=build" />
+        </exec>
+    </target>
+
+    <target name="composer" depends="php,composer-check,composer-download">
+        <exec executable="${php_executable}">
+            <arg value="build/composer.phar" />
+            <arg value="install" />
+        </exec>
+    </target>
+
+    <target name="composer-check" depends="prepare">
+        <available file="build/composer.phar" property="composer.present"/>
+    </target>
+
+    <target name="composer-download" unless="composer.present">
+        <exec executable="wget">
+            <arg value="-Obuild/composer.phar" />
+            <arg value="http://getcomposer.org/composer.phar" />
+        </exec>
+    </target>
+
+    <target name="make-release" depends="check-git-checkout-clean,prepare,php">
+        <replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
+        <exec executable="git" failonerror="true" outputproperty="current_git_branch">
+            <arg value="rev-parse" />
+            <arg value="--abbrev-ref" />
+            <arg value="HEAD" />
+        </exec>
+        <exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
+            <arg value="-r" />
+            <arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
+        </exec>
+        <exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
+            <arg value="-r" />
+            <arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
+                if (count($parts) != 3) {
+                    throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
+                }
+                if ('${current_git_branch}' === 'master') {
+                    $parts[1]++;
+                } else {
+                    $parts[2]++;
+                }
+                echo implode('.', $parts);
+            " />
+        </exec>
+
+        <git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
+        <git-tag version="${doctrine.current_version}" />
+        <replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
+        <git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
+    </target>
+
+    <target name="check-git-checkout-clean">
+        <exec executable="git" failonerror="true">
+            <arg value="diff-index" />
+            <arg value="--quiet" />
+            <arg value="HEAD" />
+        </exec>
+    </target>
+
+    <macrodef name="git-commit">
+        <attribute name="file" default="NOT SET"/>
+        <attribute name="message" default="NOT SET"/>
+
+        <sequential>
+            <exec executable="git">
+                <arg value="add" />
+                <arg value="@{file}" />
+            </exec>
+            <exec executable="git">
+                <arg value="commit" />
+                <arg value="-m" />
+                <arg value="@{message}" />
+            </exec>
+        </sequential>
+    </macrodef>
+
+    <macrodef name="git-tag">
+        <attribute name="version" default="NOT SET" />
+
+        <sequential>
+            <exec executable="git">
+                <arg value="tag" />
+                <arg value="-m" />
+                <arg value="v@{version}" />
+                <arg value="v@{version}" />
+            </exec>
+        </sequential>
+    </macrodef>
+</project>

+ 191 - 0
vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php

@@ -0,0 +1,191 @@
+<?php
+/*
+ * 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.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+use MongoBinData;
+use MongoCollection;
+use MongoDate;
+
+/**
+ * MongoDB cache provider.
+ *
+ * @since  1.1
+ * @author Jeremy Mikola <jmikola@gmail.com>
+ */
+class MongoDBCache extends CacheProvider
+{
+    /**
+     * The data field will store the serialized PHP value.
+     */
+    const DATA_FIELD = 'd';
+
+    /**
+     * The expiration field will store a MongoDate value indicating when the
+     * cache entry should expire.
+     *
+     * With MongoDB 2.2+, entries can be automatically deleted by MongoDB by
+     * indexing this field wit the "expireAfterSeconds" option equal to zero.
+     * This will direct MongoDB to regularly query for and delete any entries
+     * whose date is older than the current time. Entries without a date value
+     * in this field will be ignored.
+     *
+     * The cache provider will also check dates on its own, in case expired
+     * entries are fetched before MongoDB's TTLMonitor pass can expire them.
+     *
+     * @see http://docs.mongodb.org/manual/tutorial/expire-data/
+     */
+    const EXPIRATION_FIELD = 'e';
+
+    /**
+     * @var MongoCollection
+     */
+    private $collection;
+
+    /**
+     * Constructor.
+     *
+     * This provider will default to the write concern and read preference
+     * options set on the MongoCollection instance (or inherited from MongoDB or
+     * MongoClient). Using an unacknowledged write concern (< 1) may make the
+     * return values of delete() and save() unreliable. Reading from secondaries
+     * may make contain() and fetch() unreliable.
+     *
+     * @see http://www.php.net/manual/en/mongo.readpreferences.php
+     * @see http://www.php.net/manual/en/mongo.writeconcerns.php
+     * @param MongoCollection $collection
+     */
+    public function __construct(MongoCollection $collection)
+    {
+        $this->collection = $collection;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFetch($id)
+    {
+        $document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD));
+
+        if ($document === null) {
+            return false;
+        }
+
+        if ($this->isExpired($document)) {
+            $this->doDelete($id);
+            return false;
+        }
+
+        return unserialize($document[self::DATA_FIELD]->bin);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doContains($id)
+    {
+        $document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD));
+
+        if ($document === null) {
+            return false;
+        }
+
+        if ($this->isExpired($document)) {
+            $this->doDelete($id);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doSave($id, $data, $lifeTime = 0)
+    {
+        $result = $this->collection->update(
+            array('_id' => $id),
+            array('$set' => array(
+                self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
+                self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
+            )),
+            array('upsert' => true, 'multiple' => false)
+        );
+
+        return isset($result['ok']) ? $result['ok'] == 1 : true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doDelete($id)
+    {
+        $result = $this->collection->remove(array('_id' => $id));
+
+        return isset($result['n']) ? $result['n'] == 1 : true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doFlush()
+    {
+        // Use remove() in lieu of drop() to maintain any collection indexes
+        $result = $this->collection->remove();
+
+        return isset($result['ok']) ? $result['ok'] == 1 : true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doGetStats()
+    {
+        $serverStatus = $this->collection->db->command(array(
+            'serverStatus' => 1,
+            'locks' => 0,
+            'metrics' => 0,
+            'recordStats' => 0,
+            'repl' => 0,
+        ));
+
+        $collStats = $this->collection->db->command(array('collStats' => 1));
+
+        return array(
+            Cache::STATS_HITS => null,
+            Cache::STATS_MISSES => null,
+            Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (integer) $serverStatus['uptime'] : null),
+            Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (integer) $collStats['size'] : null),
+            Cache::STATS_MEMORY_AVAILABLE  => null,
+        );
+    }
+
+    /**
+     * Check if the document is expired.
+     *
+     * @param array $document
+     * @return boolean
+     */
+    private function isExpired(array $document)
+    {
+        return isset($document[self::EXPIRATION_FIELD]) &&
+            $document[self::EXPIRATION_FIELD] instanceof MongoDate &&
+            $document[self::EXPIRATION_FIELD]->sec < time();
+    }
+}

+ 25 - 0
vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php

@@ -0,0 +1,25 @@
+<?php
+/*
+ * 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.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Cache;
+
+class Version
+{
+    const VERSION = '1.4.0-DEV';
+}

+ 40 - 0
vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/BaseFileCacheTest.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Doctrine\Tests\Common\Cache;
+
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+abstract class BaseFileCacheTest extends CacheTest
+{
+    protected $directory;
+
+    public function setUp()
+    {
+        do {
+            $this->directory = sys_get_temp_dir() . '/doctrine_cache_'. uniqid();
+        } while (file_exists($this->directory));
+    }
+
+    public function tearDown()
+    {
+        if ( ! is_dir($this->directory)) {
+            return;
+        }
+
+        $iterator = new RecursiveDirectoryIterator($this->directory);
+
+        foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) {
+            if ($file->isFile()) {
+                @unlink($file->getRealPath());
+            } elseif ($file->isDir()) {
+                @rmdir($file->getRealPath());
+            }
+        }
+    }
+
+    protected function isSharedStorage()
+    {
+        return false;
+    }
+}

+ 61 - 0
vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MongoDBCacheTest.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Doctrine\Tests\Common\Cache;
+
+use Doctrine\Common\Cache\Cache;
+use Doctrine\Common\Cache\MongoDBCache;
+use MongoClient;
+use MongoCollection;
+
+class MongoDBCacheTest extends CacheTest
+{
+    /**
+     * @var MongoCollection
+     */
+    private $collection;
+
+    public function setUp()
+    {
+        if ( ! version_compare(phpversion('mongo'), '1.3.0', '>=')) {
+            $this->markTestSkipped('The ' . __CLASS__ .' requires the use of mongo >= 1.3.0');
+        }
+
+        $mongo = new MongoClient();
+        $this->collection = $mongo->selectCollection('doctrine_common_cache', 'test');
+    }
+
+    public function tearDown()
+    {
+        if ($this->collection instanceof MongoCollection) {
+            $this->collection->drop();
+        }
+    }
+
+    public function testSaveWithNonUtf8String()
+    {
+        // Invalid 2-octet sequence
+        $data = "\xc3\x28";
+
+        $cache = $this->_getCacheDriver();
+
+        $this->assertTrue($cache->save('key', $data));
+        $this->assertEquals($data, $cache->fetch('key'));
+    }
+
+    public function testGetStats()
+    {
+        $cache = $this->_getCacheDriver();
+        $stats = $cache->getStats();
+
+        $this->assertNull($stats[Cache::STATS_HITS]);
+        $this->assertNull($stats[Cache::STATS_MISSES]);
+        $this->assertGreaterThan(0, $stats[Cache::STATS_UPTIME]);
+        $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]);
+        $this->assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]);
+    }
+
+    protected function _getCacheDriver()
+    {
+        return new MongoDBCache($this->collection);
+    }
+}

+ 6 - 0
vendor/doctrine/cache/tests/travis/php.ini

@@ -0,0 +1,6 @@
+extension="mongo.so"
+extension="memcache.so"
+extension="memcached.so"
+
+apc.enabled=1
+apc.enable_cli=1

+ 35 - 0
vendor/doctrine/cache/tests/travis/phpunit.travis.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit bootstrap="../Doctrine/Tests/TestInit.php"
+        convertWarningsToExceptions="true"
+        convertNoticesToExceptions="true"
+        convertErrorsToExceptions="true"
+        backupStaticAttributes="false"
+        processIsolation="false"
+        stopOnFailure="false"
+        backupGlobals="false"
+        syntaxCheck="false"
+        colors="true">
+
+    <logging>
+        <log type="coverage-clover" target="../../build/logs/clover.xml"/>
+    </logging>
+
+    <testsuites>
+        <testsuite name="Doctrine Cache Test Suite">
+            <directory>../Doctrine/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>../../lib/Doctrine/</directory>
+        </whitelist>
+    </filter>
+    
+    <groups>
+        <exclude>
+            <group>performance</group>
+        </exclude>
+    </groups>
+</phpunit>

+ 34 - 0
vendor/monolog/monolog/src/Monolog/Handler/ExceptionTestHandler.php

@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+/**
+ * Used for testing purposes.
+ *
+ * It records all records and gives you access to them for verification. It
+ * throws an exception from handle and handleBatch to test the
+ * WhatFailureGroupHandler Class.
+ *
+ * @author Craig D'Amelio <craig@damelio.ca>
+ */
+class ExceptionTestHandler extends TestHandler
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function handle(array $record) {
+        $return = parent::handle($record);
+        throw new \Exception("ExceptionTestHandler::handle");
+    }
+}

+ 126 - 0
vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php

@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+
+/**
+ * Sends logs to Fleep.io using Webhook integrations
+ *
+ * You'll need a Fleep.io account to use this handler.
+ *
+ * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
+ * @author Ando Roots <ando@sqroot.eu>
+ */
+class FleepHookHandler extends SocketHandler
+{
+    const FLEEP_HOST = 'fleep.io';
+
+    const FLEEP_HOOK_URI = '/hook/';
+
+    /**
+     * @var string Webhook token (specifies the conversation where logs are sent)
+     */
+    protected $token;
+
+    /**
+     * Construct a new Fleep.io Handler.
+     *
+     * For instructions on how to create a new web hook in your conversations
+     * see https://fleep.io/integrations/webhooks/
+     *
+     * @param  string                    $token  Webhook token
+     * @param  bool|int                  $level  The minimum logging level at which this handler will be triggered
+     * @param  bool                      $bubble Whether the messages that are handled can bubble up the stack or not
+     * @throws MissingExtensionException
+     */
+    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
+    {
+        if (!extension_loaded('openssl')) {
+            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
+        }
+
+        $this->token = $token;
+
+        $connectionString = 'ssl://' . self::FLEEP_HOST . ':443';
+        parent::__construct($connectionString, $level, $bubble);
+    }
+
+    /**
+     * Returns the default formatter to use with this handler
+     *
+     * Overloaded to remove empty context and extra arrays from the end of the log message.
+     *
+     * @return LineFormatter
+     */
+    protected function getDefaultFormatter()
+    {
+        return new LineFormatter(null, null, true, true);
+    }
+
+    /**
+     * Handles a log record
+     *
+     * @param array $record
+     */
+    public function write(array $record)
+    {
+        parent::write($record);
+        $this->closeSocket();
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param  array  $record
+     * @return string
+     */
+    protected function generateDataStream($record)
+    {
+        $content = $this->buildContent($record);
+
+        return $this->buildHeader($content) . $content;
+    }
+
+    /**
+     * Builds the header of the API Call
+     *
+     * @param  string $content
+     * @return string
+     */
+    private function buildHeader($content)
+    {
+        $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
+        $header .= "Host: " . self::FLEEP_HOST . "\r\n";
+        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
+        $header .= "Content-Length: " . strlen($content) . "\r\n";
+        $header .= "\r\n";
+
+        return $header;
+    }
+
+    /**
+     * Builds the body of API call
+     *
+     * @param  array  $record
+     * @return string
+     */
+    private function buildContent($record)
+    {
+        $dataArray = array(
+            'message' => $record['formatted']
+        );
+
+        return http_build_query($dataArray);
+    }
+}

+ 68 - 0
vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php

@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+/**
+ * MandrillHandler uses cURL to send the emails to the Mandrill API
+ *
+ * @author Adam Nicholson <adamnicholson10@gmail.com>
+ */
+class MandrillHandler extends MailHandler
+{
+    protected $client;
+    protected $message;
+
+    /**
+     * @param string                  $apiKey  A valid Mandrill API key
+     * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
+     * @param integer                 $level   The minimum logging level at which this handler will be triggered
+     * @param Boolean                 $bubble  Whether the messages that are handled can bubble up the stack or not
+     */
+    public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+
+        if (!$message instanceof \Swift_Message && is_callable($message)) {
+            $message = call_user_func($message);
+        }
+        if (!$message instanceof \Swift_Message) {
+            throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
+        }
+        $this->message = $message;
+        $this->apiKey = $apiKey;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function send($content, array $records)
+    {
+        $message = clone $this->message;
+        $message->setBody($content);
+        $message->setDate(time());
+
+        $ch = curl_init();
+
+        curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json');
+        curl_setopt($ch, CURLOPT_POST, 1);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
+            'key' => $this->apiKey,
+            'raw_message' => (string) $message,
+            'async' => false,
+        )));
+
+        curl_exec($ch);
+        curl_close($ch);
+    }
+}

+ 187 - 0
vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php

@@ -0,0 +1,187 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+/**
+ * Sends notifications through Slack API
+ *
+ * @author Greg Kedzierski <greg@gregkedzierski.com>
+ * @see    https://api.slack.com/
+ */
+class SlackHandler extends SocketHandler
+{
+    /**
+     * Slack API token
+     * @var string
+     */
+    private $token;
+
+    /**
+     * Slack channel (encoded ID or name)
+     * @var string
+     */
+    private $channel;
+
+    /**
+     * Name of a bot
+     * @var string
+     */
+    private $username;
+
+    /**
+     * Emoji icon name
+     * @var string
+     */
+    private $iconEmoji;
+
+    /**
+     * Whether the message should be added to Slack as attachment (plain text otherwise)
+     * @var bool
+     */
+    private $useAttachment;
+
+    /**
+     * @param string      $token         Slack API token
+     * @param string      $channel       Slack channel (encoded ID or name)
+     * @param string      $username      Name of a bot
+     * @param bool        $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise)
+     * @param string|null $iconEmoji     The emoji name to use (or null)
+     * @param int         $level         The minimum logging level at which this handler will be triggered
+     * @param bool        $bubble        Whether the messages that are handled can bubble up the stack or not
+     */
+    public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true)
+    {
+        if (!extension_loaded('openssl')) {
+            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler');
+        }
+
+        parent::__construct('ssl://slack.com:443', $level, $bubble);
+
+        $this->token = $token;
+        $this->channel = $channel;
+        $this->username = $username;
+        $this->iconEmoji = trim($iconEmoji, ':');
+        $this->useAttachment = $useAttachment;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param  array  $record
+     * @return string
+     */
+    protected function generateDataStream($record)
+    {
+        $content = $this->buildContent($record);
+
+        return $this->buildHeader($content) . $content;
+    }
+
+    /**
+     * Builds the body of API call
+     *
+     * @param  array  $record
+     * @return string
+     */
+    private function buildContent($record)
+    {
+        $dataArray = array(
+            'token' => $this->token,
+            'channel' => $this->channel,
+            'username' => $this->username,
+            'text' => '',
+            'attachments' => array()
+        );
+
+        if ($this->useAttachment) {
+            $dataArray['attachments'] = json_encode(
+                array(
+                    array(
+                        'fallback' => $record['message'],
+                        'color' => $this->getAttachmentColor($record['level']),
+                        'fields' => array(
+                            array(
+                                'title' => 'Message',
+                                'value' => $record['message'],
+                                'short' => false
+                            ),
+                            array(
+                                'title' => 'Level',
+                                'value' => $record['level_name'],
+                                'short' => true
+                            )
+                        )
+                    )
+                )
+            );
+        } else {
+            $dataArray['text'] = $record['message'];
+        }
+
+        if ($this->iconEmoji) {
+            $dataArray['icon_emoji'] = ":{$this->iconEmoji}:";
+        }
+
+        return http_build_query($dataArray);
+    }
+
+    /**
+     * Builds the header of the API Call
+     *
+     * @param  string $content
+     * @return string
+     */
+    private function buildHeader($content)
+    {
+        $header = "POST /api/chat.postMessage HTTP/1.1\r\n";
+        $header .= "Host: slack.com\r\n";
+        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
+        $header .= "Content-Length: " . strlen($content) . "\r\n";
+        $header .= "\r\n";
+
+        return $header;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @param array $record
+     */
+    protected function write(array $record)
+    {
+        parent::write($record);
+        $this->closeSocket();
+    }
+
+    /**
+     * Returned a Slack message attachment color associated with
+     * provided level.
+     *
+     * @param  int    $level
+     * @return string
+     */
+    protected function getAttachmentColor($level)
+    {
+        switch (true) {
+            case $level >= Logger::ERROR:
+                return 'danger';
+            case $level >= Logger::WARNING:
+                return 'warning';
+            case $level >= Logger::INFO:
+                return 'good';
+            default:
+                return '#e3e4e6';
+        }
+    }
+}

+ 57 - 0
vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php

@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+/**
+ * Forwards records to multiple handlers suppressing failures of each handler 
+ * and continuing through to give every handler a chance to succeed.
+ *
+ * @author Craig D'Amelio <craig@damelio.ca>
+ */
+class WhatFailureGroupHandler extends GroupHandler
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function handle(array $record)
+    {
+        if ($this->processors) {
+            foreach ($this->processors as $processor) {
+                $record = call_user_func($processor, $record);
+            }
+        }
+
+        foreach ($this->handlers as $handler) {
+            try {
+                $handler->handle($record);
+            } catch (\Exception $e) {
+                // What failure?
+            }
+        }
+
+        return false === $this->bubble;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handleBatch(array $records)
+    {
+        foreach ($this->handlers as $handler) {
+            try {
+                $handler->handleBatch($records);
+            } catch (\Exception $e) {
+                // What failure?
+            }
+        }
+    }
+}

+ 87 - 0
vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php

@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+use Monolog\TestCase;
+
+/**
+ * @coversDefaultClass \Monolog\Handler\FleepHookHandler
+ */
+class FleepHookHandlerTest extends TestCase
+{
+    /**
+     * Default token to use in tests
+     */
+    const TOKEN = '123abc';
+
+    /**
+     * @var FleepHookHandler
+     */
+    private $handler;
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        if (!extension_loaded('openssl')) {
+            $this->markTestSkipped('This test requires openssl extension to run');
+        }
+
+        // Create instances of the handler and logger for convenience
+        $this->handler = new FleepHookHandler(self::TOKEN);
+    }
+
+    /**
+     * @covers ::__construct
+     */
+    public function testConstructorSetsExpectedDefaults()
+    {
+        $this->assertEquals(Logger::DEBUG, $this->handler->getLevel());
+        $this->assertEquals(true, $this->handler->getBubble());
+    }
+
+    /**
+     * @covers ::getDefaultFormatter
+     */
+    public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays()
+    {
+        $record = array(
+            'message' => 'msg',
+            'context' => array(),
+            'level' => Logger::DEBUG,
+            'level_name' => Logger::getLevelName(Logger::DEBUG),
+            'channel' => 'channel',
+            'datetime' => new \DateTime(),
+            'extra' => array(),
+        );
+
+        $expectedFormatter = new LineFormatter(null, null, true, true);
+        $expected = $expectedFormatter->format($record);
+
+        $handlerFormatter = $this->handler->getFormatter();
+        $actual = $handlerFormatter->format($record);
+
+        $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered');
+
+    }
+
+    /**
+     * @covers ::__construct
+     */
+    public function testConnectionStringisConstructedCorrectly()
+    {
+        $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString());
+    }
+
+}

+ 133 - 0
vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php

@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @author Greg Kedzierski <greg@gregkedzierski.com>
+ * @see    https://api.slack.com/
+ */
+class SlackHandlerTest extends TestCase
+{
+    /**
+     * @var resource
+     */
+    private $res;
+
+    /**
+     * @var SlackHandler
+     */
+    private $handler;
+
+    public function setUp()
+    {
+        if (!extension_loaded('openssl')) {
+            $this->markTestSkipped('This test requires openssl to run');
+        }
+    }
+
+    public function testWriteHeader()
+    {
+        $this->createHandler();
+        $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+        fseek($this->res, 0);
+        $content = fread($this->res, 1024);
+
+        $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+    }
+
+    public function testWriteContent()
+    {
+        $this->createHandler();
+        $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+        fseek($this->res, 0);
+        $content = fread($this->res, 1024);
+
+        $this->assertRegexp('/token=myToken&channel=channel1&username=Monolog&text=&attachments=.*$/', $content);
+    }
+    
+    public function testWriteContentWithEmoji()
+    {
+        $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien');
+        $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+        fseek($this->res, 0);
+        $content = fread($this->res, 1024);
+
+        $this->assertRegexp('/icon_emoji=%3Aalien%3A$/', $content);
+    }
+
+    /**
+     * @dataProvider provideLevelColors
+     */
+    public function testWriteContentWithColors($level, $expectedColor)
+    {
+        $this->createHandler();
+        $this->handler->handle($this->getRecord($level, 'test1'));
+        fseek($this->res, 0);
+        $content = fread($this->res, 1024);
+
+        $this->assertRegexp('/color%22%3A%22'.$expectedColor.'/', $content);
+    }
+
+    public function testWriteContentWithPlainTextMessage()
+    {
+        $this->createHandler('myToken', 'channel1', 'Monolog', false);
+        $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+        fseek($this->res, 0);
+        $content = fread($this->res, 1024);
+
+        $this->assertRegexp('/text=test1/', $content);
+    }
+
+    public function provideLevelColors()
+    {
+        return array(
+            array(Logger::DEBUG,    '%23e3e4e6'),   // escaped #e3e4e6
+            array(Logger::INFO,     'good'),
+            array(Logger::NOTICE,   'good'),
+            array(Logger::WARNING,  'warning'),
+            array(Logger::ERROR,    'danger'),
+            array(Logger::CRITICAL, 'danger'),
+            array(Logger::ALERT,    'danger'),
+            array(Logger::EMERGENCY,'danger'),
+        );
+    }
+
+    private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null)
+    {
+        $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true);
+        $this->res = fopen('php://memory', 'a');
+        $this->handler = $this->getMock(
+            '\Monolog\Handler\SlackHandler',
+            array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+            $constructorArgs
+        );
+
+        $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($this->handler, 'localhost:1234');
+
+        $this->handler->expects($this->any())
+            ->method('fsockopen')
+            ->will($this->returnValue($this->res));
+        $this->handler->expects($this->any())
+            ->method('streamSetTimeout')
+            ->will($this->returnValue(true));
+        $this->handler->expects($this->any())
+            ->method('closeSocket')
+            ->will($this->returnValue(true));
+
+        $this->handler->setFormatter($this->getIdentityFormatter());
+    }
+}

+ 107 - 0
vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php

@@ -0,0 +1,107 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+class WhatFailureGroupHandlerTest extends TestCase
+{
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::__construct
+     * @expectedException InvalidArgumentException
+     */
+    public function testConstructorOnlyTakesHandler()
+    {
+        new WhatFailureGroupHandler(array(new TestHandler(), "foo"));
+    }
+
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::__construct
+     * @covers Monolog\Handler\WhatFailureGroupHandler::handle
+     */
+    public function testHandle()
+    {
+        $testHandlers = array(new TestHandler(), new TestHandler());
+        $handler = new WhatFailureGroupHandler($testHandlers);
+        $handler->handle($this->getRecord(Logger::DEBUG));
+        $handler->handle($this->getRecord(Logger::INFO));
+        foreach ($testHandlers as $test) {
+            $this->assertTrue($test->hasDebugRecords());
+            $this->assertTrue($test->hasInfoRecords());
+            $this->assertTrue(count($test->getRecords()) === 2);
+        }
+    }
+
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch
+     */
+    public function testHandleBatch()
+    {
+        $testHandlers = array(new TestHandler(), new TestHandler());
+        $handler = new WhatFailureGroupHandler($testHandlers);
+        $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)));
+        foreach ($testHandlers as $test) {
+            $this->assertTrue($test->hasDebugRecords());
+            $this->assertTrue($test->hasInfoRecords());
+            $this->assertTrue(count($test->getRecords()) === 2);
+        }
+    }
+
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling
+     */
+    public function testIsHandling()
+    {
+        $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING));
+        $handler = new WhatFailureGroupHandler($testHandlers);
+        $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR)));
+        $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING)));
+        $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+    }
+
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::handle
+     */
+    public function testHandleUsesProcessors()
+    {
+        $test = new TestHandler();
+        $handler = new WhatFailureGroupHandler(array($test));
+        $handler->pushProcessor(function ($record) {
+            $record['extra']['foo'] = true;
+
+            return $record;
+        });
+        $handler->handle($this->getRecord(Logger::WARNING));
+        $this->assertTrue($test->hasWarningRecords());
+        $records = $test->getRecords();
+        $this->assertTrue($records[0]['extra']['foo']);
+    }
+
+    /**
+     * @covers Monolog\Handler\WhatFailureGroupHandler::handle
+     */
+    public function testHandleException() {
+        $test = new TestHandler();
+        $exception = new ExceptionTestHandler();
+        $handler = new WhatFailureGroupHandler(array($exception, $test, $exception));
+        $handler->pushProcessor(function ($record) {
+            $record['extra']['foo'] = true;
+
+            return $record;
+        });
+        $handler->handle($this->getRecord(Logger::WARNING));
+        $this->assertTrue($test->hasWarningRecords());
+        $records = $test->getRecords();
+        $this->assertTrue($records[0]['extra']['foo']);
+    }
+}

+ 45 - 0
vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php

@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\Processor\PsrLogMessageProcessor;
+
+class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider getPairs
+     */
+    public function testReplacement($val, $expected)
+    {
+        $proc = new PsrLogMessageProcessor;
+
+        $message = $proc(array(
+            'message' => '{foo}',
+            'context' => array('foo' => $val)
+        ));
+        $this->assertEquals($expected, $message['message']);
+    }
+
+    public function getPairs()
+    {
+        return array(
+            array('foo',    'foo'),
+            array('3',      '3'),
+            array(3,        '3'),
+            array(null,     ''),
+            array(true,     '1'),
+            array(false,    ''),
+            array(new \stdClass, '[object stdClass]'),
+            array(array(), '[array]'),
+        );
+    }
+}

+ 761 - 0
vendor/sabre/vobject/lib/Cli.php

@@ -0,0 +1,761 @@
+<?php
+
+namespace Sabre\VObject;
+
+use
+    InvalidArgumentException;
+
+/**
+ * This is the CLI interface for sabre-vobject.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Cli {
+
+    /**
+     * No output
+     *
+     * @var bool
+     */
+    protected $quiet = false;
+
+    /**
+     * Help display
+     *
+     * @var bool
+     */
+    protected $showHelp = false;
+
+    /**
+     * Wether to spit out 'mimedir' or 'json' format.
+     *
+     * @var string
+     */
+    protected $format;
+
+    /**
+     * JSON pretty print
+     *
+     * @var bool
+     */
+    protected $pretty;
+
+    /**
+     * Source file
+     *
+     * @var string
+     */
+    protected $inputPath;
+
+    /**
+     * Destination file
+     *
+     * @var string
+     */
+    protected $outputPath;
+
+    /**
+     * output stream
+     *
+     * @var resource
+     */
+    protected $stdout;
+
+    /**
+     * stdin
+     *
+     * @var resource
+     */
+    protected $stdin;
+
+    /**
+     * stderr
+     *
+     * @var resource
+     */
+    protected $stderr;
+
+    /**
+     * Input format (one of json or mimedir)
+     *
+     * @var string
+     */
+    protected $inputFormat;
+
+    /**
+     * Makes the parser less strict.
+     *
+     * @var bool
+     */
+    protected $forgiving = false;
+
+    /**
+     * Main function
+     *
+     * @return int
+     */
+    public function main(array $argv) {
+
+        // @codeCoverageIgnoreStart
+        // We cannot easily test this, so we'll skip it. Pretty basic anyway.
+
+        if (!$this->stderr) {
+            $this->stderr = fopen('php://stderr', 'w');
+        }
+        if (!$this->stdout) {
+            $this->stdout = fopen('php://stdout', 'w');
+        }
+        if (!$this->stdin) {
+            $this->stdin = fopen('php://stdin', 'r');
+        }
+
+        // @codeCoverageIgnoreEnd
+
+
+        try {
+
+            list($options, $positional) = $this->parseArguments($argv);
+
+            if (isset($options['q'])) {
+                $this->quiet = true;
+            }
+            $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
+
+            foreach($options as $name=>$value) {
+
+                switch($name) {
+
+                    case 'q' :
+                        // Already handled earlier.
+                        break;
+                    case 'h' :
+                    case 'help' :
+                        $this->showHelp();
+                        return 0;
+                        break;
+                    case 'format' :
+                        switch($value) {
+
+                            // jcard/jcal documents
+                            case 'jcard' :
+                            case 'jcal' :
+
+                            // specific document versions
+                            case 'vcard21' :
+                            case 'vcard30' :
+                            case 'vcard40' :
+                            case 'icalendar20' :
+
+                            // specific formats
+                            case 'json' :
+                            case 'mimedir' :
+
+                            // icalendar/vcad
+                            case 'icalendar' :
+                            case 'vcard' :
+                                $this->format = $value;
+                                break;
+
+                            default :
+                                throw new InvalidArgumentException('Unknown format: ' . $value);
+
+                        }
+                        break;
+                    case 'pretty' :
+                        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+                            $this->pretty = true;
+                        }
+                        break;
+                    case 'forgiving' :
+                        $this->forgiving = true;
+                        break;
+                    case 'inputformat' :
+                        switch($value) {
+                            // json formats
+                            case 'jcard' :
+                            case 'jcal' :
+                            case 'json' :
+                                $this->inputFormat = 'json';
+                                break;
+
+                            // mimedir formats
+                            case 'mimedir' :
+                            case 'icalendar' :
+                            case 'vcard' :
+                            case 'vcard21' :
+                            case 'vcard30' :
+                            case 'vcard40' :
+                            case 'icalendar20' :
+
+                                $this->inputFormat = 'mimedir';
+                                break;
+
+                            default :
+                                throw new InvalidArgumentException('Unknown format: ' . $value);
+
+                        }
+                        break;
+                    default :
+                        throw new InvalidArgumentException('Unknown option: ' . $name);
+
+                }
+
+            }
+
+            if (count($positional) === 0) {
+                $this->showHelp();
+                return 1;
+            }
+
+            if (count($positional) === 1) {
+                throw new InvalidArgumentException('Inputfile is a required argument');
+            }
+
+            if (count($positional) > 3) {
+                throw new InvalidArgumentException('Too many arguments');
+            }
+
+            if (!in_array($positional[0], array('validate','repair','convert','color'))) {
+                throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
+            }
+
+        } catch (InvalidArgumentException $e) {
+            $this->showHelp();
+            $this->log('Error: ' . $e->getMessage(), 'red');
+            return 1;
+        }
+
+        $command = $positional[0];
+
+        $this->inputPath = $positional[1];
+        $this->outputPath = isset($positional[2])?$positional[2]:'-';
+
+        if ($this->outputPath !== '-') {
+            $this->stdout = fopen($this->outputPath, 'w');
+        }
+
+        if (!$this->inputFormat) {
+            if (substr($this->inputPath, -5)==='.json') {
+                $this->inputFormat = 'json';
+            } else {
+                $this->inputFormat = 'mimedir';
+            }
+        }
+        if (!$this->format) {
+            if (substr($this->outputPath,-5)==='.json') {
+                $this->format = 'json';
+            } else {
+                $this->format = 'mimedir';
+            }
+        }
+
+
+        $realCode = 0;
+
+        try {
+
+            while($input = $this->readInput()) {
+
+                $returnCode = $this->$command($input);
+                if ($returnCode!==0) $realCode = $returnCode;
+
+            }
+
+        } catch (EofException $e) {
+            // end of file
+        } catch (\Exception $e) {
+            $this->log('Error: ' . $e->getMessage(),'red');
+            return 2;
+        }
+
+        return $realCode;
+
+    }
+
+    /**
+     * Shows the help message.
+     *
+     * @return void
+     */
+    protected function showHelp() {
+
+        $this->log('Usage:', 'yellow');
+        $this->log("  vobject [options] command [arguments]");
+        $this->log('');
+        $this->log('Options:', 'yellow');
+        $this->log($this->colorize('green', '  -q            ') . "Don't output anything.");
+        $this->log($this->colorize('green', '  -help -h      ') . "Display this help message.");
+        $this->log($this->colorize('green', '  --format      ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
+        $this->log($this->colorize('green', '  --forgiving   ') . "Makes the parser less strict.");
+        $this->log("                vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
+        $this->log($this->colorize('green', '  --inputformat ') . "If the input format cannot be guessed from the extension, it");
+        $this->log("                must be specified here.");
+        // Only PHP 5.4 and up
+        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+            $this->log($this->colorize('green', '  --pretty      ') . "json pretty-print.");
+        }
+        $this->log('');
+        $this->log('Commands:', 'yellow');
+        $this->log($this->colorize('green', '  validate') . ' source_file              Validates a file for correctness.');
+        $this->log($this->colorize('green', '  repair') . ' source_file [output_file]  Repairs a file.');
+        $this->log($this->colorize('green', '  convert') . ' source_file [output_file] Converts a file.');
+        $this->log($this->colorize('green', '  color') . ' source_file                 Colorize a file, useful for debbugging.');
+        $this->log(
+<<<HELP
+
+If source_file is set as '-', STDIN will be used.
+If output_file is omitted, STDOUT will be used.
+All other output is sent to STDERR.
+
+HELP
+        );
+
+        $this->log('Examples:', 'yellow');
+        $this->log('   vobject convert contact.vcf contact.json');
+        $this->log('   vobject convert --format=vcard40 old.vcf new.vcf');
+        $this->log('   vobject convert --inputformat=json --format=mimedir - -');
+        $this->log('   vobject color calendar.ics');
+        $this->log('');
+        $this->log('https://github.com/fruux/sabre-vobject','purple');
+
+    }
+
+    /**
+     * Validates a VObject file
+     *
+     * @param Component $vObj
+     * @return int
+     */
+    protected function validate($vObj) {
+
+        $returnCode = 0;
+
+        switch($vObj->name) {
+            case 'VCALENDAR' :
+                $this->log("iCalendar: " . (string)$vObj->VERSION);
+                break;
+            case 'VCARD' :
+                $this->log("vCard: " . (string)$vObj->VERSION);
+                break;
+        }
+
+        $warnings = $vObj->validate();
+        if (!count($warnings)) {
+            $this->log("  No warnings!");
+        } else {
+
+            $levels = array(
+                1 => 'REPAIRED',
+                2 => 'WARNING',
+                3 => 'ERROR',
+            );
+            $returnCode = 2;
+            foreach($warnings as $warn) {
+
+                $extra = '';
+                if ($warn['node'] instanceof Property) {
+                    $extra = ' (property: "' . $warn['node']->name . '")';
+                }
+                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
+
+            }
+
+        }
+
+        return $returnCode;
+
+    }
+
+    /**
+     * Repairs a VObject file
+     *
+     * @param Component $vObj
+     * @return int
+     */
+    protected function repair($vObj) {
+
+        $returnCode = 0;
+
+        switch($vObj->name) {
+            case 'VCALENDAR' :
+                $this->log("iCalendar: " . (string)$vObj->VERSION);
+                break;
+            case 'VCARD' :
+                $this->log("vCard: " . (string)$vObj->VERSION);
+                break;
+        }
+
+        $warnings = $vObj->validate(Node::REPAIR);
+        if (!count($warnings)) {
+            $this->log("  No warnings!");
+        } else {
+
+            $levels = array(
+                1 => 'REPAIRED',
+                2 => 'WARNING',
+                3 => 'ERROR',
+            );
+            $returnCode = 2;
+            foreach($warnings as $warn) {
+
+                $extra = '';
+                if ($warn['node'] instanceof Property) {
+                    $extra = ' (property: "' . $warn['node']->name . '")';
+                }
+                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
+
+            }
+
+        }
+        fwrite($this->stdout, $vObj->serialize());
+
+        return $returnCode;
+
+    }
+
+    /**
+     * Converts a vObject file to a new format.
+     *
+     * @param Component $vObj
+     * @return int
+     */
+    protected function convert($vObj) {
+
+        $json = false;
+        $convertVersion = null;
+        $forceInput = null;
+
+        switch($this->format) {
+            case 'json' :
+                $json = true;
+                if ($vObj->name === 'VCARD') {
+                    $convertVersion = Document::VCARD40;
+                }
+                break;
+            case 'jcard' :
+                $json = true;
+                $forceInput = 'VCARD';
+                $convertVersion = Document::VCARD40;
+                break;
+            case 'jcal' :
+                $json = true;
+                $forceInput = 'VCALENDAR';
+                break;
+            case 'mimedir' :
+            case 'icalendar' :
+            case 'icalendar20' :
+            case 'vcard' :
+                break;
+            case 'vcard21' :
+                $convertVersion = Document::VCARD21;
+                break;
+            case 'vcard30' :
+                $convertVersion = Document::VCARD30;
+                break;
+            case 'vcard40' :
+                $convertVersion = Document::VCARD40;
+                break;
+
+        }
+
+        if ($forceInput && $vObj->name !== $forceInput) {
+            throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
+        }
+        if ($convertVersion) {
+            $vObj = $vObj->convert($convertVersion);
+        }
+        if ($json) {
+            $jsonOptions = 0;
+            if ($this->pretty) {
+                $jsonOptions = JSON_PRETTY_PRINT;
+            }
+            fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
+        } else {
+            fwrite($this->stdout, $vObj->serialize());
+        }
+
+        return 0;
+
+    }
+
+    /**
+     * Colorizes a file
+     *
+     * @param Component $vObj
+     * @return int
+     */
+    protected function color($vObj) {
+
+        fwrite($this->stdout, $this->serializeComponent($vObj));
+
+    }
+
+    /**
+     * Returns an ansi color string for a color name.
+     *
+     * @param string $color
+     * @return string
+     */
+    protected function colorize($color, $str, $resetTo = 'default') {
+
+        $colors = array(
+            'cyan'    => '1;36',
+            'red'     => '1;31',
+            'yellow'  => '1;33',
+            'blue'    => '0;34',
+            'green'   => '0;32',
+            'default' => '0',
+            'purple'  => '0;35',
+        );
+        return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m";
+
+    }
+
+    /**
+     * Writes out a string in specific color.
+     *
+     * @param string $color
+     * @param string $str
+     * @return void
+     */
+    protected function cWrite($color, $str) {
+
+        fwrite($this->stdout, $this->colorize($color, $str));
+
+    }
+
+    protected function serializeComponent(Component $vObj) {
+
+        $this->cWrite('cyan', 'BEGIN');
+        $this->cWrite('red', ':');
+        $this->cWrite('yellow', $vObj->name . "\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 = $vObj->children;
+        uksort(
+            $vObj->children,
+            function($a, $b) use ($sortScore, $tmp) {
+
+                $sA = $sortScore($a, $tmp);
+                $sB = $sortScore($b, $tmp);
+
+                return $sA - $sB;
+
+            }
+        );
+
+        foreach($vObj->children as $child) {
+            if ($child instanceof Component) {
+                $this->serializeComponent($child);
+            } else {
+                $this->serializeProperty($child);
+            }
+        }
+
+        $this->cWrite('cyan', 'END');
+        $this->cWrite('red', ':');
+        $this->cWrite('yellow', $vObj->name . "\n");
+
+    }
+
+    /**
+     * Colorizes a property.
+     *
+     * @param Property $property
+     * @return void
+     */
+    protected function serializeProperty(Property $property) {
+
+        if ($property->group) {
+            $this->cWrite('default', $property->group);
+            $this->cWrite('red', '.');
+        }
+
+        $str = '';
+        $this->cWrite('yellow', $property->name);
+
+        foreach($property->parameters as $param) {
+
+            $this->cWrite('red',';');
+            $this->cWrite('blue', $param->serialize());
+
+        }
+        $this->cWrite('red',':');
+
+        if ($property instanceof Property\Binary) {
+
+            $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
+
+        } else {
+
+            $parts = $property->getParts();
+            $first1 = true;
+            // Looping through property values
+            foreach($parts as $part) {
+                if ($first1) {
+                    $first1 = false;
+                } else {
+                    $this->cWrite('red', $property->delimiter);
+                }
+                $first2 = true;
+                // Looping through property sub-values
+                foreach((array)$part as $subPart) {
+                    if ($first2) {
+                        $first2 = false;
+                    } else {
+                        // The sub-value delimiter is always comma
+                        $this->cWrite('red', ',');
+                    }
+
+                    $subPart = strtr(
+                        $subPart,
+                        array(
+                            '\\' => $this->colorize('purple', '\\\\', 'green'),
+                            ';'  => $this->colorize('purple', '\;', 'green'),
+                            ','  => $this->colorize('purple', '\,', 'green'),
+                            "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
+                            "\r" => "",
+                        )
+                    );
+
+                    $this->cWrite('green', $subPart);
+                }
+            }
+
+        }
+        $this->cWrite("default", "\n");
+
+    }
+
+    /**
+     * Parses the list of arguments.
+     *
+     * @param array $argv
+     * @return void
+     */
+    protected function parseArguments(array $argv) {
+
+        $positional = array();
+        $options = array();
+
+        for($ii=0; $ii < count($argv); $ii++) {
+
+            // Skipping the first argument.
+            if ($ii===0) continue;
+
+            $v = $argv[$ii];
+
+            if (substr($v,0,2)==='--') {
+                // This is a long-form option.
+                $optionName = substr($v,2);
+                $optionValue = true;
+                if (strpos($optionName,'=')) {
+                    list($optionName, $optionValue) = explode('=', $optionName);
+                }
+                $options[$optionName] = $optionValue;
+            } elseif (substr($v,0,1) === '-' && strlen($v)>1) {
+                // This is a short-form option.
+                foreach(str_split(substr($v,1)) as $option) {
+                    $options[$option] = true;
+                }
+
+            } else {
+
+                $positional[] = $v;
+
+            }
+
+        }
+
+        return array($options, $positional);
+
+    }
+
+    protected $parser;
+
+    /**
+     * Reads the input file
+     *
+     * @return Component
+     */
+    protected function readInput() {
+
+        if (!$this->parser) {
+            if ($this->inputPath!=='-') {
+                $this->stdin = fopen($this->inputPath,'r');
+            }
+
+            if ($this->inputFormat === 'mimedir') {
+                $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
+            } else {
+                $this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
+            }
+        }
+
+        return $this->parser->parse();
+
+    }
+
+    /**
+     * Sends a message to STDERR.
+     *
+     * @param string $msg
+     * @return void
+     */
+    protected function log($msg, $color = 'default') {
+
+        if (!$this->quiet) {
+            if ($color!=='default') {
+                $msg = $this->colorize($color, $msg);
+            }
+            fwrite($this->stderr, $msg . "\n");
+        }
+
+    }
+
+}

+ 587 - 0
vendor/sabre/vobject/lib/Component.php

@@ -0,0 +1,587 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Component
+ *
+ * A component represents a group of properties, such as VCALENDAR, VEVENT, or
+ * VCARD.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Component extends Node {
+
+    /**
+     * Component name.
+     *
+     * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * A list of properties and/or sub-components.
+     *
+     * @var array
+     */
+    public $children = array();
+
+    /**
+     * Creates a new component.
+     *
+     * You can specify the children either in key=>value syntax, in which case
+     * properties will automatically be created, or you can just pass a list of
+     * Component and Property object.
+     *
+     * By default, a set of sensible values will be added to the component. For
+     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
+     * ensure that this does not happen, set $defaults to false.
+     *
+     * @param Document $root
+     * @param string $name such as VCALENDAR, VEVENT.
+     * @param array $children
+     * @param bool $defaults
+     * @return void
+     */
+    public function __construct(Document $root, $name, array $children = array(), $defaults = true) {
+
+        $this->name = strtoupper($name);
+        $this->root = $root;
+
+        if ($defaults) {
+            // This is a terribly convoluted way to do this, but this ensures
+            // that the order of properties as they are specified in both
+            // defaults and the childrens list, are inserted in the object in a
+            // natural way.
+            $list = $this->getDefaults();
+            $nodes = array();
+            foreach($children as $key=>$value) {
+                if ($value instanceof Node) {
+                    if (isset($list[$value->name])) {
+                        unset($list[$value->name]);
+                    }
+                    $nodes[] = $value;
+                } else {
+                    $list[$key] = $value;
+                }
+            }
+            foreach($list as $key=>$value) {
+                $this->add($key, $value);
+            }
+            foreach($nodes as $node) {
+                $this->add($node);
+            }
+        } else {
+            foreach($children as $k=>$child) {
+                if ($child instanceof Node) {
+
+                    // Component or Property
+                    $this->add($child);
+                } else {
+
+                    // Property key=>value
+                    $this->add($k, $child);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Adds a new property or component, and returns the new item.
+     *
+     * This method has 3 possible signatures:
+     *
+     * add(Component $comp) // Adds a new component
+     * add(Property $prop)  // Adds a new property
+     * add($name, $value, array $parameters = array()) // Adds a new property
+     * add($name, array $children = array()) // Adds a new component
+     * by name.
+     *
+     * @return Node
+     */
+    public function add($a1, $a2 = null, $a3 = null) {
+
+        if ($a1 instanceof Node) {
+            if (!is_null($a2)) {
+                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
+            }
+            $a1->parent = $this;
+            $this->children[] = $a1;
+
+            return $a1;
+
+        } elseif(is_string($a1)) {
+
+            $item = $this->root->create($a1, $a2, $a3);
+            $item->parent = $this;
+            $this->children[] = $item;
+
+            return $item;
+
+        } else {
+
+            throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
+
+        }
+
+    }
+
+    /**
+     * This method removes a component or property from this component.
+     *
+     * You can either specify the item by name (like DTSTART), in which case
+     * all properties/components with that name will be removed, or you can
+     * pass an instance of a property or component, in which case only that
+     * exact item will be removed.
+     *
+     * The removed item will be returned. In case there were more than 1 items
+     * removed, only the last one will be returned.
+     *
+     * @param mixed $item
+     * @return void
+     */
+    public function remove($item) {
+
+        if (is_string($item)) {
+            $children = $this->select($item);
+            foreach($children as $k=>$child) {
+                unset($this->children[$k]);
+            }
+            return $child;
+        } else {
+            foreach($this->children as $k => $child) {
+                if ($child===$item) {
+                    unset($this->children[$k]);
+                    return $child;
+                }
+            }
+
+            throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
+
+        }
+
+    }
+
+    /**
+     * Returns an iterable list of children
+     *
+     * @return array
+     */
+    public function children() {
+
+        return $this->children;
+
+    }
+
+    /**
+     * 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;
+
+    }
+
+    /**
+     * 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;
+
+    }
+
+    /**
+     * 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);
+
+                return $sA - $sB;
+
+            }
+        );
+
+        foreach($this->children as $child) $str.=$child->serialize();
+        $str.= "END:" . $this->name . "\r\n";
+
+        return $str;
+
+    }
+
+    /**
+     * This method returns an array, with the representation as it should be
+     * encoded in json. This is used to create jCard or jCal documents.
+     *
+     * @return array
+     */
+    public function jsonSerialize() {
+
+        $components = array();
+        $properties = array();
+
+        foreach($this->children as $child) {
+            if ($child instanceof Component) {
+                $components[] = $child->jsonSerialize();
+            } else {
+                $properties[] = $child->jsonSerialize();
+            }
+        }
+
+        return array(
+            strtolower($this->name),
+            $properties,
+            $components
+        );
+
+    }
+
+    /**
+     * This method should return a list of default property values.
+     *
+     * @return array
+     */
+    protected function getDefaults() {
+
+        return array();
+
+    }
+
+    /* 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.
+     *
+     * To use this, this may look something like this:
+     *
+     * $event = $calendar->VEVENT;
+     *
+     * @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;
+            }
+        } else {
+            $property = $this->root->create($name,$value);
+            $property->parent = $this;
+            if (!is_null($overWrite)) {
+                $this->children[$overWrite] = $property;
+            } else {
+                $this->children[] = $property;
+            }
+        }
+    }
+
+    /**
+     * Removes all properties and components within this component with the
+     * specified name.
+     *
+     * @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;
+        }
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *   * ? - May appear, but not more than once.
+     *
+     * It is also possible to specify defaults and severity levels for
+     * violating the rule.
+     *
+     * See the VEVENT implementation for getValidationRules for a more complex
+     * example.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array();
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $rules = $this->getValidationRules();
+        $defaults = $this->getDefaults();
+
+        $propertyCounters = array();
+
+        $messages = array();
+
+        foreach($this->children as $child) {
+            $name = strtoupper($child->name);
+            if (!isset($propertyCounters[$name])) {
+                $propertyCounters[$name] = 1;
+            } else {
+                $propertyCounters[$name]++;
+            }
+            $messages = array_merge($messages, $child->validate($options));
+        }
+
+        foreach($rules as $propName => $rule) {
+
+            switch($rule) {
+                case '0' :
+                    if (isset($propertyCounters[$propName])) {
+                        $messages[] = array(
+                            'level' => 3,
+                            'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
+                            'node' => $this,
+                        );
+                    }
+                    break;
+                case '1' :
+                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName]!==1) {
+                        $repaired = false;
+                        if ($options & self::REPAIR && isset($defaults[$propName])) {
+                            $this->add($propName, $defaults[$propName]);
+                        }
+                        $messages[] = array(
+                            'level' => $repaired?1:3,
+                            'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
+                            'node' => $this,
+                        );
+                    }
+                    break;
+                case '+' :
+                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
+                        $messages[] = array(
+                            'level' => 3,
+                            'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
+                            'node' => $this,
+                        );
+                    }
+                    break;
+                case '*' :
+                    break;
+                case '?' :
+                    if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
+                        $messages[] = array(
+                            'level' => 3,
+                            'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
+                            'node' => $this,
+                        );
+                    }
+                    break;
+
+            }
+
+        }
+        return $messages;
+
+    }
+
+}

+ 136 - 0
vendor/sabre/vobject/lib/Component/VAlarm.php

@@ -0,0 +1,136 @@
+<?php
+
+namespace Sabre\VObject\Component;
+use Sabre\VObject;
+
+/**
+ * VAlarm component
+ *
+ * This component contains some additional functionality specific for VALARMs.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VAlarm extends VObject\Component {
+
+    /**
+     * Returns a DateTime object when this alarm is going to trigger.
+     *
+     * This ignores repeated alarm, only the first trigger is returned.
+     *
+     * @return DateTime
+     */
+    public function getEffectiveTriggerTime() {
+
+        $trigger = $this->TRIGGER;
+        if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
+            $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
+            $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
+
+            $parentComponent = $this->parent;
+            if ($related === 'START') {
+
+                if ($parentComponent->name === 'VTODO') {
+                    $propName = 'DUE';
+                } else {
+                    $propName = 'DTSTART';
+                }
+
+                $effectiveTrigger = clone $parentComponent->$propName->getDateTime();
+                $effectiveTrigger->add($triggerDuration);
+            } else {
+                if ($parentComponent->name === 'VTODO') {
+                    $endProp = 'DUE';
+                } elseif ($parentComponent->name === 'VEVENT') {
+                    $endProp = 'DTEND';
+                } else {
+                    throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
+                }
+
+                if (isset($parentComponent->$endProp)) {
+                    $effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
+                    $effectiveTrigger->add($triggerDuration);
+                } elseif (isset($parentComponent->DURATION)) {
+                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+                    $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
+                    $effectiveTrigger->add($duration);
+                    $effectiveTrigger->add($triggerDuration);
+                } else {
+                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+                    $effectiveTrigger->add($triggerDuration);
+                }
+            }
+        } else {
+            $effectiveTrigger = $trigger->getDateTime();
+        }
+        return $effectiveTrigger;
+
+    }
+
+    /**
+     * Returns true or false depending on if the event falls in the specified
+     * time-range. This is used for filtering purposes.
+     *
+     * The rules used to determine if an event falls within the specified
+     * time-range is based on the CalDAV specification.
+     *
+     * @param \DateTime $start
+     * @param \DateTime $end
+     * @return bool
+     */
+    public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+        $effectiveTrigger = $this->getEffectiveTriggerTime();
+
+        if (isset($this->DURATION)) {
+            $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
+            $repeat = (string)$this->repeat;
+            if (!$repeat) {
+                $repeat = 1;
+            }
+
+            $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
+
+            foreach($period as $occurrence) {
+
+                if ($start <= $occurrence && $end > $occurrence) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
+        }
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'ACTION' => 1,
+            'TRIGGER' => 1,
+
+            'DURATION' => '?',
+            'REPEAT' => '?',
+
+            'ATTACH' => '?',
+        );
+
+    }
+
+}

+ 423 - 0
vendor/sabre/vobject/lib/Component/VCalendar.php

@@ -0,0 +1,423 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use
+    Sabre\VObject,
+    Sabre\VObject\Component,
+    Sabre\VObject\Recur\EventIterator;
+
+/**
+ * The VCalendar component
+ *
+ * This component adds functionality to a component, specific for a VCALENDAR.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCalendar extends VObject\Document {
+
+    /**
+     * The default name for this component.
+     *
+     * This should be 'VCALENDAR' or 'VCARD'.
+     *
+     * @var string
+     */
+    static public $defaultName = 'VCALENDAR';
+
+    /**
+     * This is a list of components, and which classes they should map to.
+     *
+     * @var array
+     */
+    static public $componentMap = array(
+        'VALARM'    => 'Sabre\\VObject\\Component\\VAlarm',
+        'VEVENT'    => 'Sabre\\VObject\\Component\\VEvent',
+        'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
+        'VJOURNAL'  => 'Sabre\\VObject\\Component\\VJournal',
+        'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone',
+        'VTODO'     => 'Sabre\\VObject\\Component\\VTodo',
+    );
+
+    /**
+     * List of value-types, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $valueMap = array(
+        'BINARY'           => 'Sabre\\VObject\\Property\\Binary',
+        'BOOLEAN'          => 'Sabre\\VObject\\Property\\Boolean',
+        'CAL-ADDRESS'      => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+        'DATE'             => 'Sabre\\VObject\\Property\\ICalendar\\Date',
+        'DATE-TIME'        => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DURATION'         => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+        'FLOAT'            => 'Sabre\\VObject\\Property\\Float',
+        'INTEGER'          => 'Sabre\\VObject\\Property\\Integer',
+        'PERIOD'           => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+        'RECUR'            => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+        'TEXT'             => 'Sabre\\VObject\\Property\\Text',
+        'TIME'             => 'Sabre\\VObject\\Property\\Time',
+        'UNKNOWN'          => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
+        'URI'              => 'Sabre\\VObject\\Property\\Uri',
+        'UTC-OFFSET'       => 'Sabre\\VObject\\Property\\UtcOffset',
+    );
+
+    /**
+     * List of properties, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $propertyMap = array(
+        // Calendar properties
+        'CALSCALE'      => 'Sabre\\VObject\\Property\\FlatText',
+        'METHOD'        => 'Sabre\\VObject\\Property\\FlatText',
+        'PRODID'        => 'Sabre\\VObject\\Property\\FlatText',
+        'VERSION'       => 'Sabre\\VObject\\Property\\FlatText',
+
+        // Component properties
+        'ATTACH'            => 'Sabre\\VObject\\Property\\Uri',
+        'CATEGORIES'        => 'Sabre\\VObject\\Property\\Text',
+        'CLASS'             => 'Sabre\\VObject\\Property\\FlatText',
+        'COMMENT'           => 'Sabre\\VObject\\Property\\FlatText',
+        'DESCRIPTION'       => 'Sabre\\VObject\\Property\\FlatText',
+        'GEO'               => 'Sabre\\VObject\\Property\\Float',
+        'LOCATION'          => 'Sabre\\VObject\\Property\\FlatText',
+        'PERCENT-COMPLETE'  => 'Sabre\\VObject\\Property\\Integer',
+        'PRIORITY'          => 'Sabre\\VObject\\Property\\Integer',
+        'RESOURCES'         => 'Sabre\\VObject\\Property\\Text',
+        'STATUS'            => 'Sabre\\VObject\\Property\\FlatText',
+        'SUMMARY'           => 'Sabre\\VObject\\Property\\FlatText',
+
+        // Date and Time Component Properties
+        'COMPLETED'     => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DTEND'         => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DUE'           => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DTSTART'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DURATION'      => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+        'FREEBUSY'      => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+        'TRANSP'        => 'Sabre\\VObject\\Property\\FlatText',
+
+        // Time Zone Component Properties
+        'TZID'          => 'Sabre\\VObject\\Property\\FlatText',
+        'TZNAME'        => 'Sabre\\VObject\\Property\\FlatText',
+        'TZOFFSETFROM'  => 'Sabre\\VObject\\Property\\UtcOffset',
+        'TZOFFSETTO'    => 'Sabre\\VObject\\Property\\UtcOffset',
+        'TZURL'         => 'Sabre\\VObject\\Property\\Uri',
+
+        // Relationship Component Properties
+        'ATTENDEE'      => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+        'CONTACT'       => 'Sabre\\VObject\\Property\\FlatText',
+        'ORGANIZER'     => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+        'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'RELATED-TO'    => 'Sabre\\VObject\\Property\\FlatText',
+        'URL'           => 'Sabre\\VObject\\Property\\Uri',
+        'UID'           => 'Sabre\\VObject\\Property\\FlatText',
+
+        // Recurrence Component Properties
+        'EXDATE'        => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'RDATE'         => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'RRULE'         => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+        'EXRULE'        => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
+
+        // Alarm Component Properties
+        'ACTION'        => 'Sabre\\VObject\\Property\\FlatText',
+        'REPEAT'        => 'Sabre\\VObject\\Property\\Integer',
+        'TRIGGER'       => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+
+        // Change Management Component Properties
+        'CREATED'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'DTSTAMP'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'SEQUENCE'      => 'Sabre\\VObject\\Property\\Integer',
+
+        // Request Status
+        'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
+
+        // Additions from draft-daboo-valarm-extensions-04
+        'ALARM-AGENT'    => 'Sabre\\VObject\\Property\\Text',
+        'ACKNOWLEDGED'   => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+        'PROXIMITY'      => 'Sabre\\VObject\\Property\\Text',
+        'DEFAULT-ALARM'  => 'Sabre\\VObject\\Property\\Boolean',
+
+    );
+
+    /**
+     * Returns the current document type.
+     *
+     * @return void
+     */
+    public function getDocumentType() {
+
+        return self::ICALENDAR20;
+
+    }
+
+    /**
+     * 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 VObject\Component[]
+     */
+    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;
+
+    }
+
+    /**
+     * Returns the first component that is not a VTIMEZONE, and does not have
+     * an RECURRENCE-ID.
+     *
+     * If there is no such component, null will be returned.
+     *
+     * @param string $componentName filter by component name
+     * @return VObject\Component|null
+     */
+    public function getBaseComponent($componentName = null) {
+
+        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;
+
+            return $component;
+
+        }
+
+    }
+
+    /**
+     * 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 EventIterator($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]);
+
+        }
+
+        // Setting all properties to UTC time.
+        foreach($newEvents as $newEvent) {
+
+            foreach($newEvent->children as $child) {
+                if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
+                    $dt = $child->getDateTimes();
+                    // We only need to update the first timezone, because
+                    // setDateTimes will match all other timezones to the
+                    // first.
+                    $dt[0]->setTimeZone(new \DateTimeZone('UTC'));
+                    $child->setDateTimes($dt);
+                }
+
+            }
+
+            $this->add($newEvent);
+
+        }
+
+        // Removing all VTIMEZONE components
+        unset($this->VTIMEZONE);
+
+    }
+
+    /**
+     * This method should return a list of default property values.
+     *
+     * @return array
+     */
+    protected function getDefaults() {
+
+        return array(
+            'VERSION' => '2.0',
+            'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
+            'CALSCALE' => 'GREGORIAN',
+        );
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'PRODID' => 1,
+            'VERSION' => 1,
+
+            'CALSCALE' => '?',
+            'METHOD' => '?',
+        );
+
+    }
+
+    /**
+     * 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($options = 0) {
+
+        $warnings = parent::validate($options);
+
+        if ($ver = $this->VERSION) {
+            if ((string)$ver !== '2.0') {
+                $warnings[] = array(
+                    'level' => 3,
+                    'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
+                    'node' => $this,
+                );
+            }
+
+        }
+
+        $uidList = array();
+
+        $componentsFound = 0;
+        foreach($this->children as $child) {
+            if($child instanceof Component) {
+                $componentsFound++;
+
+                if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) {
+                    continue;
+                }
+
+                $uid = (string)$child->UID;
+                $isMaster = isset($child->{'RECURRENCE-ID'})?0:1;
+                if (isset($uidList[$uid])) {
+                    $uidList[$uid]['count']++;
+                    if ($isMaster && $uidList[$uid]['hasMaster']) {
+                        $warnings[] = array(
+                            'level' => 3,
+                            'message' => 'More than one master object was found for the object with UID ' . $uid,
+                            'node' => $this,
+                        );
+                    }
+                    $uidList[$uid]['hasMaster']+=$isMaster;
+                } else {
+                    $uidList[$uid] = array(
+                        'count' => 1,
+                        'hasMaster' => $isMaster,
+                    );
+                }
+
+            }
+        }
+
+        if ($componentsFound===0) {
+            $warnings[] = array(
+                'level' => 3,
+                'message' => 'An iCalendar object must have at least 1 component.',
+                'node' => $this,
+            );
+        }
+
+        return $warnings;
+
+    }
+
+}
+

+ 431 - 0
vendor/sabre/vobject/lib/Component/VCard.php

@@ -0,0 +1,431 @@
+<?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-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCard extends VObject\Document {
+
+    /**
+     * The default name for this component.
+     *
+     * This should be 'VCALENDAR' or 'VCARD'.
+     *
+     * @var string
+     */
+    static public $defaultName = 'VCARD';
+
+    /**
+     * Caching the version number
+     *
+     * @var int
+     */
+    private $version = null;
+
+    /**
+     * List of value-types, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $valueMap = array(
+        'BINARY'           => 'Sabre\\VObject\\Property\\Binary',
+        'BOOLEAN'          => 'Sabre\\VObject\\Property\\Boolean',
+        'CONTENT-ID'       => 'Sabre\\VObject\\Property\\FlatText',   // vCard 2.1 only
+        'DATE'             => 'Sabre\\VObject\\Property\\VCard\\Date',
+        'DATE-TIME'        => 'Sabre\\VObject\\Property\\VCard\\DateTime',
+        'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
+        'FLOAT'            => 'Sabre\\VObject\\Property\\Float',
+        'INTEGER'          => 'Sabre\\VObject\\Property\\Integer',
+        'LANGUAGE-TAG'     => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
+        'TIMESTAMP'        => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
+        'TEXT'             => 'Sabre\\VObject\\Property\\Text',
+        'TIME'             => 'Sabre\\VObject\\Property\\Time',
+        'UNKNOWN'          => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
+        'URI'              => 'Sabre\\VObject\\Property\\Uri',
+        'URL'              => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
+        'UTC-OFFSET'       => 'Sabre\\VObject\\Property\\UtcOffset',
+    );
+
+    /**
+     * List of properties, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $propertyMap = array(
+
+        // vCard 2.1 properties and up
+        'N'       => 'Sabre\\VObject\\Property\\Text',
+        'FN'      => 'Sabre\\VObject\\Property\\FlatText',
+        'PHOTO'   => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values.
+        'BDAY'    => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+        'ADR'     => 'Sabre\\VObject\\Property\\Text',
+        'LABEL'   => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+        'TEL'     => 'Sabre\\VObject\\Property\\FlatText',
+        'EMAIL'   => 'Sabre\\VObject\\Property\\FlatText',
+        'MAILER'  => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+        'GEO'     => 'Sabre\\VObject\\Property\\FlatText',
+        'TITLE'   => 'Sabre\\VObject\\Property\\FlatText',
+        'ROLE'    => 'Sabre\\VObject\\Property\\FlatText',
+        'LOGO'    => 'Sabre\\VObject\\Property\\Binary',
+        // 'AGENT'   => 'Sabre\\VObject\\Property\\',      // Todo: is an embedded vCard. Probably rare, so
+                                 // not supported at the moment
+        'ORG'     => 'Sabre\\VObject\\Property\\Text',
+        'NOTE'    => 'Sabre\\VObject\\Property\\FlatText',
+        'REV'     => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
+        'SOUND'   => 'Sabre\\VObject\\Property\\FlatText',
+        'URL'     => 'Sabre\\VObject\\Property\\Uri',
+        'UID'     => 'Sabre\\VObject\\Property\\FlatText',
+        'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
+        'KEY'     => 'Sabre\\VObject\\Property\\FlatText',
+        'TZ'      => 'Sabre\\VObject\\Property\\Text',
+
+        // vCard 3.0 properties
+        'CATEGORIES'  => 'Sabre\\VObject\\Property\\Text',
+        'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
+        'PRODID'      => 'Sabre\\VObject\\Property\\FlatText',
+        'NICKNAME'    => 'Sabre\\VObject\\Property\\Text',
+        'CLASS'       => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+
+        // rfc2739 properties
+        'FBURL'        => 'Sabre\\VObject\\Property\\Uri',
+        'CAPURI'       => 'Sabre\\VObject\\Property\\Uri',
+        'CALURI'       => 'Sabre\\VObject\\Property\\Uri',
+
+        // rfc4770 properties
+        'IMPP'         => 'Sabre\\VObject\\Property\\Uri',
+
+        // vCard 4.0 properties
+        'XML'          => 'Sabre\\VObject\\Property\\FlatText',
+        'ANNIVERSARY'  => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+        'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
+        'LANG'         => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
+        'GENDER'       => 'Sabre\\VObject\\Property\\Text',
+        'KIND'         => 'Sabre\\VObject\\Property\\FlatText',
+
+        // rfc6474 properties
+        'BIRTHPLACE'    => 'Sabre\\VObject\\Property\\FlatText',
+        'DEATHPLACE'    => 'Sabre\\VObject\\Property\\FlatText',
+        'DEATHDATE'     => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+
+        // rfc6715 properties
+        'EXPERTISE'     => 'Sabre\\VObject\\Property\\FlatText',
+        'HOBBY'         => 'Sabre\\VObject\\Property\\FlatText',
+        'INTEREST'      => 'Sabre\\VObject\\Property\\FlatText',
+        'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText',
+
+    );
+
+    /**
+     * Returns the current document type.
+     *
+     * @return void
+     */
+    public function getDocumentType() {
+
+        if (!$this->version) {
+            $version = (string)$this->VERSION;
+            switch($version) {
+                case '2.1' :
+                    $this->version = self::VCARD21;
+                    break;
+                case '3.0' :
+                    $this->version = self::VCARD30;
+                    break;
+                case '4.0' :
+                    $this->version = self::VCARD40;
+                    break;
+                default :
+                    $this->version = self::UNKNOWN;
+                    break;
+
+            }
+        }
+
+        return $this->version;
+
+    }
+
+    /**
+     * Converts the document to a different vcard version.
+     *
+     * Use one of the VCARD constants for the target. This method will return
+     * a copy of the vcard in the new version.
+     *
+     * At the moment the only supported conversion is from 3.0 to 4.0.
+     *
+     * If input and output version are identical, a clone is returned.
+     *
+     * @param int $target
+     * @return VCard
+     */
+    public function convert($target) {
+
+        $converter = new VObject\VCardConverter();
+        return $converter->convert($this, $target);
+
+    }
+
+    /**
+     * VCards with version 2.1, 3.0 and 4.0 are found.
+     *
+     * If the VCARD doesn't know its version, 2.1 is assumed.
+     */
+    const DEFAULT_VERSION = self::VCARD21;
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $warnings = array();
+
+        $versionMap = array(
+            self::VCARD21 => '2.1',
+            self::VCARD30 => '3.0',
+            self::VCARD40 => '4.0',
+        );
+
+        $version = $this->select('VERSION');
+        if (count($version)===1) {
+            $version = (string)$this->VERSION;
+            if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
+                $warnings[] = array(
+                    'level' => 3,
+                    '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 = $versionMap[self::DEFAULT_VERSION];
+                }
+            }
+
+        }
+        $fn = $this->select('FN');
+        if (count($fn)!==1) {
+
+            $repaired = false;
+            if (($options & self::REPAIR) && count($fn) === 0) {
+                // We're going to try to see if we can use the contents of the
+                // N property.
+                if (isset($this->N)) {
+                    $value = explode(';', (string)$this->N);
+                    if (isset($value[1]) && $value[1]) {
+                        $this->FN = $value[1] . ' ' . $value[0];
+                    } else {
+                        $this->FN = $value[0];
+                    }
+                    $repaired = true;
+
+                // Otherwise, the ORG property may work
+                } elseif (isset($this->ORG)) {
+                    $this->FN = (string)$this->ORG;
+                    $repaired = true;
+                }
+
+            }
+            $warnings[] = array(
+                'level' => $repaired?1:3,
+                'message' => 'The FN property must appear in the VCARD component exactly 1 time',
+                'node' => $this,
+            );
+        }
+
+        return array_merge(
+            parent::validate($options),
+            $warnings
+        );
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'ADR'          => '*',
+            'ANNIVERSARY'  => '?',
+            'BDAY'         => '?',
+            'CALADRURI'    => '*',
+            'CALURI'       => '*',
+            'CATEGORIES'   => '*',
+            'CLIENTPIDMAP' => '*',
+            'EMAIL'        => '*',
+            'FBURL'        => '*',
+            'IMPP'         => '*',
+            'GENDER'       => '?',
+            'GEO'          => '*',
+            'KEY'          => '*',
+            'KIND'         => '?',
+            'LANG'         => '*',
+            'LOGO'         => '*',
+            'MEMBER'       => '*',
+            'N'            => '?',
+            'NICKNAME'     => '*',
+            'NOTE'         => '*',
+            'ORG'          => '*',
+            'PHOTO'        => '*',
+            'PRODID'       => '?',
+            'RELATED'      => '*',
+            'REV'          => '?',
+            'ROLE'         => '*',
+            'SOUND'        => '*',
+            'SOURCE'       => '*',
+            'TEL'          => '*',
+            'TITLE'        => '*',
+            'TZ'           => '*',
+            'URL'          => '*',
+            'VERSION'      => '1',
+            'XML'          => '*',
+
+            // FN is commented out, because it's already handled by the
+            // validate function, which may also try to repair it.
+            // 'FN'           => '+',
+
+            // vcard actually specifies this as '?', but in most cases not
+            // having a UID is highly undesirable. So here we're going against
+            // the spec and make it required.
+            //
+            // I would be interested to hear if this is problematic for
+            // anyone, or at least a usecase where this is undesirable.
+            //
+            // If so, I may have to add a facility that allows us to check
+            // specifically for validity in the context of 'DAV'.
+            'UID'          => '1',
+        );
+
+    }
+
+    /**
+     * Returns a preferred field.
+     *
+     * VCards can indicate wether a field such as ADR, TEL or EMAIL is
+     * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
+     * being a number between 1 and 100).
+     *
+     * If neither of those parameters are specified, the first is returned, if
+     * a field with that name does not exist, null is returned.
+     *
+     * @param string $fieldName
+     * @return VObject\Property|null
+     */
+    public function preferred($propertyName) {
+
+        $preferred = null;
+        $lastPref = 101;
+        foreach($this->select($propertyName) as $field) {
+
+            $pref = 101;
+            if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
+                $pref = 1;
+            } elseif (isset($field['PREF'])) {
+                $pref = $field['PREF']->getValue();
+            }
+
+            if ($pref < $lastPref || is_null($preferred)) {
+                $preferred = $field;
+                $lastPref = $pref;
+            }
+
+        }
+        return $preferred;
+
+    }
+
+    /**
+     * This method should return a list of default property values.
+     *
+     * @return array
+     */
+    protected function getDefaults() {
+
+        return array(
+            'VERSION' => '3.0',
+            'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
+        );
+
+    }
+
+    /**
+     * This method returns an array, with the representation as it should be
+     * encoded in json. This is used to create jCard or jCal documents.
+     *
+     * @return array
+     */
+    public function jsonSerialize() {
+
+        // A vcard does not have sub-components, so we're overriding this
+        // method to remove that array element.
+        $properties = array();
+
+        foreach($this->children as $child) {
+            $properties[] = $child->jsonSerialize();
+        }
+
+        return array(
+            strtolower($this->name),
+            $properties,
+        );
+
+    }
+
+    /**
+     * Returns the default class for a property name.
+     *
+     * @param string $propertyName
+     * @return string
+     */
+    public function getClassNameForPropertyName($propertyName) {
+
+        $className = parent::getClassNameForPropertyName($propertyName);
+        // In vCard 4, BINARY no longer exists, and we need URI instead.
+
+        if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) {
+            return 'Sabre\\VObject\\Property\\Uri';
+        }
+        return $className;
+
+    }
+
+}
+

+ 140 - 0
vendor/sabre/vobject/lib/Component/VEvent.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+use Sabre\VObject\Recur\EventIterator;
+
+/**
+ * VEvent component
+ *
+ * This component contains some additional functionality specific for VEVENT's.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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 EventIterator($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->hasTime()) {
+            $effectiveEnd = clone $effectiveStart;
+            $effectiveEnd->modify('+1 day');
+        } else {
+            $effectiveEnd = clone $effectiveStart;
+        }
+        return (
+            ($start <= $effectiveEnd) && ($end > $effectiveStart)
+        );
+
+    }
+
+    /**
+     * This method should return a list of default property values.
+     *
+     * @return array
+     */
+    protected function getDefaults() {
+
+        return array(
+            'UID'     => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
+            'DTSTAMP' => date('Ymd\\THis\\Z'),
+        );
+
+    }
+
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        $hasMethod = isset($this->parent->METHOD);
+        return array(
+            'UID' => 1,
+            'DTSTAMP' => 1,
+            'DTSTART' => $hasMethod?'?':'1',
+            'CLASS' => '?',
+            'CREATED' => '?',
+            'DESCRIPTION' => '?',
+            'GEO' => '?',
+            'LAST-MODIFICATION' => '?',
+            'LOCATION' => '?',
+            'ORGANIZER' => '?',
+            'PRIORITY' => '?',
+            'SEQUENCE' => '?',
+            'STATUS' => '?',
+            'SUMMARY' => '?',
+            'TRANSP' => '?',
+            'URL' => '?',
+            'RECURRENCE-ID' => '?',
+            'RRULE' => '?',
+            'DTEND' => '?',
+            'DURATION' => '?',
+
+            'ATTACH' => '*',
+            'ATTENDEE' => '*',
+            'CATEGORIES' => '*',
+            'COMMENT' => '*',
+            'CONTACT' => '*',
+            'EXDATE' => '*',
+            'REQUEST-STATUS' => '*',
+            'RELATED-TO' => '*',
+            'RESOURCES' => '*',
+            'RDATE' => '*',
+        );
+
+    }
+
+}

+ 102 - 0
vendor/sabre/vobject/lib/Component/VFreeBusy.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * The VFreeBusy component
+ *
+ * This component adds functionality to a component, specific for VFREEBUSY
+ * components.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VFreeBusy extends VObject\Component {
+
+    /**
+     * Checks based on the contained FREEBUSY information, if a timeslot is
+     * available.
+     *
+     * @param DateTime $start
+     * @param Datetime $end
+     * @return bool
+     */
+    public function isFree(\DateTime $start, \Datetime $end) {
+
+        foreach($this->select('FREEBUSY') as $freebusy) {
+
+            // We are only interested in FBTYPE=BUSY (the default),
+            // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
+            if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
+                continue;
+            }
+
+            // The freebusy component can hold more than 1 value, separated by
+            // commas.
+            $periods = explode(',', (string)$freebusy);
+
+            foreach($periods as $period) {
+                // Every period is formatted as [start]/[end]. The start is an
+                // absolute UTC time, the end may be an absolute UTC time, or
+                // duration (relative) value.
+                list($busyStart, $busyEnd) = explode('/', $period);
+
+                $busyStart = VObject\DateTimeParser::parse($busyStart);
+                $busyEnd = VObject\DateTimeParser::parse($busyEnd);
+                if ($busyEnd instanceof \DateInterval) {
+                    $tmp = clone $busyStart;
+                    $tmp->add($busyEnd);
+                    $busyEnd = $tmp;
+                }
+
+                if($start < $busyEnd && $end > $busyStart) {
+                    return false;
+                }
+
+            }
+
+        }
+
+        return true;
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'UID' => 1,
+            'DTSTAMP' => 1,
+
+            'CONTACT' => '?',
+            'DTSTART' => '?',
+            'DTEND' => '?',
+            'ORGANIZER' => '?',
+            'URL' => '?',
+
+            'ATTENDEE' => '*',
+            'COMMENT' => '*',
+            'FREEBUSY' => '*',
+            'REQUEST-STATUS' => '*',
+        );
+
+    }
+
+}
+

+ 90 - 0
vendor/sabre/vobject/lib/Component/VJournal.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * VJournal component
+ *
+ * This component contains some additional functionality specific for VJOURNALs.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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->hasTime()) {
+                $effectiveEnd->modify('+1 day');
+            }
+
+            return ($start <= $effectiveEnd && $end > $dtstart);
+
+        }
+        return false;
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'UID' => 1,
+            'DTSTAMP' => 1,
+
+            'CLASS' => '?',
+            'CREATED' => '?',
+            'DTSTART' => '?',
+            'LAST-MODIFICATION' => '?',
+            'ORGANIZER' => '?',
+            'RECURRENCE-ID' => '?',
+            'SEQUENCE' => '?',
+            'STATUS' => '?',
+            'SUMMARY' => '?',
+            'URL' => '?',
+
+            'RRULE' => '?',
+
+            'ATTACH' => '*',
+            'ATTENDEE' => '*',
+            'CATEGORIES' => '*',
+            'COMMENT' => '*',
+            'CONTACT' => '*',
+            'DESCRIPTION' => '*',
+            'EXDATE' => '*',
+            'RELATED-TO' => '*',
+            'RDATE' => '*',
+        );
+
+    }
+}

+ 53 - 0
vendor/sabre/vobject/lib/Component/VTimeZone.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * The VTimeZone component
+ *
+ * This component adds functionality to a component, specific for VTIMEZONE
+ * components.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VTimeZone extends VObject\Component {
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'TZID' => 1,
+
+            'LAST-MODIFICATION' => '?',
+            'TZURL' => '?',
+
+            // At least 1 STANDARD or DAYLIGHT must appear, or more. But both
+            // cannot appear in the same VTIMEZONE.
+            //
+            // The validator is not specific yet to pick this up, so these
+            // rules are too loose.
+            'STANDARD' => '*',
+            'DAYLIGHT' => '*',
+        );
+
+    }
+
+}
+

+ 176 - 0
vendor/sabre/vobject/lib/Component/VTodo.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * VTodo component
+ *
+ * This component contains some additional functionality specific for VTODOs.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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;
+
+    }
+
+    /**
+     * A simple list of validation rules.
+     *
+     * This is simply a list of properties, and how many times they either
+     * must or must not appear.
+     *
+     * Possible values per property:
+     *   * 0 - Must not appear.
+     *   * 1 - Must appear exactly once.
+     *   * + - Must appear at least once.
+     *   * * - Can appear any number of times.
+     *
+     * @var array
+     */
+    public function getValidationRules() {
+
+        return array(
+            'UID' => 1,
+            'DTSTAMP' => 1,
+
+            'CLASS' => '?',
+            'COMPLETED' => '?',
+            'CREATED' => '?',
+            'DESCRIPTION' => '?',
+            'DTSTART' => '?',
+            'GEO' => '?',
+            'LAST-MODIFICATION' => '?',
+            'LOCATION' => '?',
+            'ORGANIZER' => '?',
+            'PERCENT' => '?',
+            'PRIORITY' => '?',
+            'RECURRENCE-ID' => '?',
+            'SEQUENCE' => '?',
+            'STATUS' => '?',
+            'SUMMARY' => '?',
+            'URL' => '?',
+
+            'RRULE' => '?',
+            'DUE' => '?',
+            'DURATION' => '?',
+
+            'ATTACH' => '*',
+            'ATTENDEE' => '*',
+            'CATEGORIES' => '*',
+            'COMMENT' => '*',
+            'CONTACT' => '*',
+            'EXDATE' => '*',
+            'REQUEST-STATUS' => '*',
+            'RELATED-TO' => '*',
+            'RESOURCES' => '*',
+            'RDATE' => '*',
+        );
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $result = parent::validate($options);
+        if (isset($this->DUE) && isset($this->DTSTART)) {
+
+            $due = $this->DUE;
+            $dtStart = $this->DTSTART;
+
+            if ($due->getValueType() !== $dtStart->getValueType()) {
+
+                $result[] = array(
+                    'level'   => 3,
+                    'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART',
+                    'node' => $due,
+                );
+
+            } elseif ($due->getDateTime() < $dtStart->getDateTime()) {
+
+                $result[] = array(
+                    'level'   => 3,
+                    'message' => 'DUE must occur after DTSTART',
+                    'node' => $due,
+                );
+
+            }
+
+        }
+
+        return $result;
+
+    }
+
+}

+ 417 - 0
vendor/sabre/vobject/lib/DateTimeParser.php

@@ -0,0 +1,417 @@
+<?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-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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('/^([0-9]{4})([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('/^([0-9]{4})([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);
+        }
+
+    }
+
+    /**
+     * This method parses a vCard date and or time value.
+     *
+     * This can be used for the DATE, DATE-TIME, TIMESTAMP and
+     * DATE-AND-OR-TIME value.
+     *
+     * This method returns an array, not a DateTime value.
+     *
+     * The elements in the array are in the following order:
+     * year, month, date, hour, minute, second, timezone
+     *
+     * Almost any part of the string may be omitted. It's for example legal to
+     * just specify seconds, leave out the year, etc.
+     *
+     * Timezone is either returned as 'Z' or as '+08:00'
+     *
+     * For any non-specified values null is returned.
+     *
+     * List of date formats that are supported:
+     * YYYY
+     * YYYY-MM
+     * YYYYMMDD
+     * --MMDD
+     * ---DD
+     *
+     * YYYY-MM-DD
+     * --MM-DD
+     * ---DD
+     *
+     * List of supported time formats:
+     *
+     * HH
+     * HHMM
+     * HHMMSS
+     * -MMSS
+     * --SS
+     *
+     * HH
+     * HH:MM
+     * HH:MM:SS
+     * -MM:SS
+     * --SS
+     *
+     * A full basic-format date-time string looks like :
+     * 20130603T133901
+     *
+     * A full extended-format date-time string looks like :
+     * 2013-06-03T13:39:01
+     *
+     * Times may be postfixed by a timezone offset. This can be either 'Z' for
+     * UTC, or a string like -0500 or +1100.
+     *
+     * @param string $date
+     * @return array
+     */
+    static public function parseVCardDateTime($date) {
+
+        $regex = '/^
+            (?:  # date part
+                (?:
+                    (?: (?P<year> [0-9]{4}) (?: -)?| --)
+                    (?P<month> [0-9]{2})?
+                |---)
+                (?P<date> [0-9]{2})?
+            )?
+            (?:T  # time part
+                (?P<hour> [0-9]{2} | -)
+                (?P<minute> [0-9]{2} | -)?
+                (?P<second> [0-9]{2})?
+
+                (?P<timezone> # timezone offset
+
+                    Z | (?: \+|-)(?: [0-9]{4})
+
+                )?
+
+            )?
+            $/x';
+
+
+        if (!preg_match($regex, $date, $matches)) {
+
+            // Attempting to parse the extended format.
+            $regex = '/^
+                (?: # date part
+                    (?: (?P<year> [0-9]{4}) - | -- )
+                    (?P<month> [0-9]{2}) -
+                    (?P<date> [0-9]{2})
+                )?
+                (?:T # time part
+
+                    (?: (?P<hour> [0-9]{2}) : | -)
+                    (?: (?P<minute> [0-9]{2}) : | -)?
+                    (?P<second> [0-9]{2})?
+
+                    (?P<timezone> # timezone offset
+
+                        Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
+
+                    )?
+
+                )?
+                $/x';
+
+            if (!preg_match($regex, $date, $matches)) {
+                throw new \InvalidArgumentException('Invalid vCard date-time string: ' . $date);
+            }
+
+        }
+        $parts = array(
+            'year',
+            'month',
+            'date',
+            'hour',
+            'minute',
+            'second',
+            'timezone'
+        );
+
+        $result = array();
+        foreach($parts as $part) {
+
+            if (empty($matches[$part])) {
+                $result[$part] = null;
+            } elseif ($matches[$part] === '-' || $matches[$part] === '--') {
+                $result[$part] = null;
+            } else {
+                $result[$part] = $matches[$part];
+            }
+
+        }
+
+        return $result;
+
+    }
+
+    /**
+     * This method parses a vCard TIME value.
+     *
+     * This method returns an array, not a DateTime value.
+     *
+     * The elements in the array are in the following order:
+     * hour, minute, second, timezone
+     *
+     * Almost any part of the string may be omitted. It's for example legal to
+     * just specify seconds, leave out the hour etc.
+     *
+     * Timezone is either returned as 'Z' or as '+08:00'
+     *
+     * For any non-specified values null is returned.
+     *
+     * List of supported time formats:
+     *
+     * HH
+     * HHMM
+     * HHMMSS
+     * -MMSS
+     * --SS
+     *
+     * HH
+     * HH:MM
+     * HH:MM:SS
+     * -MM:SS
+     * --SS
+     *
+     * A full basic-format time string looks like :
+     * 133901
+     *
+     * A full extended-format time string looks like :
+     * 13:39:01
+     *
+     * Times may be postfixed by a timezone offset. This can be either 'Z' for
+     * UTC, or a string like -0500 or +11:00.
+     *
+     * @param string $date
+     * @return array
+     */
+    static public function parseVCardTime($date) {
+
+        $regex = '/^
+            (?P<hour> [0-9]{2} | -)
+            (?P<minute> [0-9]{2} | -)?
+            (?P<second> [0-9]{2})?
+
+            (?P<timezone> # timezone offset
+
+                Z | (?: \+|-)(?: [0-9]{4})
+
+            )?
+            $/x';
+
+
+        if (!preg_match($regex, $date, $matches)) {
+
+            // Attempting to parse the extended format.
+            $regex = '/^
+                (?: (?P<hour> [0-9]{2}) : | -)
+                (?: (?P<minute> [0-9]{2}) : | -)?
+                (?P<second> [0-9]{2})?
+
+                (?P<timezone> # timezone offset
+
+                    Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
+
+                )?
+                $/x';
+
+            if (!preg_match($regex, $date, $matches)) {
+                throw new \InvalidArgumentException('Invalid vCard time string: ' . $date);
+            }
+
+        }
+        $parts = array(
+            'hour',
+            'minute',
+            'second',
+            'timezone'
+        );
+
+        $result = array();
+        foreach($parts as $part) {
+
+            if (empty($matches[$part])) {
+                $result[$part] = null;
+            } elseif ($matches[$part] === '-') {
+                $result[$part] = null;
+            } else {
+                $result[$part] = $matches[$part];
+            }
+
+        }
+
+        return $result;
+
+    }
+}

+ 261 - 0
vendor/sabre/vobject/lib/Document.php

@@ -0,0 +1,261 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Document
+ *
+ * A document is just like a component, except that it's also the top level
+ * element.
+ *
+ * Both a VCALENDAR and a VCARD are considered documents.
+ *
+ * This class also provides a registry for document types.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class Document extends Component {
+
+    /**
+     * Unknown document type
+     */
+    const UNKNOWN = 1;
+
+    /**
+     * vCalendar 1.0
+     */
+    const VCALENDAR10 = 2;
+
+    /**
+     * iCalendar 2.0
+     */
+    const ICALENDAR20 = 3;
+
+    /**
+     * vCard 2.1
+     */
+    const VCARD21 = 4;
+
+    /**
+     * vCard 3.0
+     */
+    const VCARD30 = 5;
+
+    /**
+     * vCard 4.0
+     */
+    const VCARD40 = 6;
+
+    /**
+     * The default name for this component.
+     *
+     * This should be 'VCALENDAR' or 'VCARD'.
+     *
+     * @var string
+     */
+    static public $defaultName;
+
+    /**
+     * List of properties, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $propertyMap = array();
+
+    /**
+     * List of components, along with which classes they map to.
+     *
+     * @var array
+     */
+    static public $componentMap = array();
+
+    /**
+     * List of value-types, and which classes they map to.
+     *
+     * @var array
+     */
+    static public $valueMap = array();
+
+    /**
+     * Creates a new document.
+     *
+     * We're changing the default behavior slightly here. First, we don't want
+     * to have to specify a name (we already know it), and we want to allow
+     * children to be specified in the first argument.
+     *
+     * But, the default behavior also works.
+     *
+     * So the two sigs:
+     *
+     * new Document(array $children = array(), $defaults = true);
+     * new Document(string $name, array $children = array(), $defaults = true)
+     *
+     * @return void
+     */
+    public function __construct() {
+
+        $args = func_get_args();
+        if (count($args)===0 || is_array($args[0])) {
+            array_unshift($args, $this, static::$defaultName);
+            call_user_func_array(array('parent', '__construct'), $args);
+        } else {
+            array_unshift($args, $this);
+            call_user_func_array(array('parent', '__construct'), $args);
+        }
+
+    }
+
+    /**
+     * Returns the current document type.
+     *
+     * @return void
+     */
+    public function getDocumentType() {
+
+        return self::UNKNOWN;
+
+    }
+
+    /**
+     * Creates a new component or property.
+     *
+     * If it's a known component, we will automatically call createComponent.
+     * otherwise, we'll assume it's a property and call createProperty instead.
+     *
+     * @param string $name
+     * @param string $arg1,... Unlimited number of args
+     * @return mixed
+     */
+    public function create($name) {
+
+        if (isset(static::$componentMap[strtoupper($name)])) {
+
+            return call_user_func_array(array($this,'createComponent'), func_get_args());
+
+        } else {
+
+            return call_user_func_array(array($this,'createProperty'), func_get_args());
+
+        }
+
+    }
+
+    /**
+     * Creates a new component
+     *
+     * This method automatically searches for the correct component class, based
+     * on its name.
+     *
+     * You can specify the children either in key=>value syntax, in which case
+     * properties will automatically be created, or you can just pass a list of
+     * Component and Property object.
+     *
+     * By default, a set of sensible values will be added to the component. For
+     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
+     * ensure that this does not happen, set $defaults to false.
+     *
+     * @param string $name
+     * @param array $children
+     * @param bool $defaults
+     * @return Component
+     */
+    public function createComponent($name, array $children = null, $defaults = true) {
+
+        $name = strtoupper($name);
+        $class = 'Sabre\\VObject\\Component';
+
+        if (isset(static::$componentMap[$name])) {
+            $class=static::$componentMap[$name];
+        }
+        if (is_null($children)) $children = array();
+        return new $class($this, $name, $children, $defaults);
+
+    }
+
+    /**
+     * Factory method for creating new properties
+     *
+     * This method automatically searches for the correct property class, based
+     * on its name.
+     *
+     * You can specify the parameters either in key=>value syntax, in which case
+     * parameters will automatically be created, or you can just pass a list of
+     * Parameter objects.
+     *
+     * @param string $name
+     * @param mixed $value
+     * @param array $parameters
+     * @param string $valueType Force a specific valuetype, such as URI or TEXT
+     * @return Property
+     */
+    public function createProperty($name, $value = null, array $parameters = null, $valueType = null) {
+
+        // If there's a . in the name, it means it's prefixed by a groupname.
+        if (($i=strpos($name,'.'))!==false) {
+            $group = substr($name, 0, $i);
+            $name = strtoupper(substr($name, $i+1));
+        } else {
+            $name = strtoupper($name);
+            $group = null;
+        }
+
+        $class = null;
+
+        if ($valueType) {
+            // The valueType argument comes first to figure out the correct
+            // class.
+            $class = $this->getClassNameForPropertyValue($valueType);
+        }
+
+        if (is_null($class) && isset($parameters['VALUE'])) {
+            // If a VALUE parameter is supplied, we should use that.
+            $class = $this->getClassNameForPropertyValue($parameters['VALUE']);
+        }
+        if (is_null($class)) {
+            $class = $this->getClassNameForPropertyName($name);
+        }
+        if (is_null($parameters)) $parameters = array();
+
+        return new $class($this, $name, $value, $parameters, $group);
+
+    }
+
+    /**
+     * This method returns a full class-name for a value parameter.
+     *
+     * For instance, DTSTART may have VALUE=DATE. In that case we will look in
+     * our valueMap table and return the appropriate class name.
+     *
+     * This method returns null if we don't have a specialized class.
+     *
+     * @param string $valueParam
+     * @return void
+     */
+    public function getClassNameForPropertyValue($valueParam) {
+
+        $valueParam = strtoupper($valueParam);
+        if (isset(static::$valueMap[$valueParam])) {
+            return static::$valueMap[$valueParam];
+        }
+
+    }
+
+    /**
+     * Returns the default class for a property name.
+     *
+     * @param string $propertyName
+     * @return string
+     */
+    public function getClassNameForPropertyName($propertyName) {
+
+        if (isset(static::$propertyMap[$propertyName])) {
+            return static::$propertyMap[$propertyName];
+        } else {
+            return 'Sabre\\VObject\\Property\\Unknown';
+        }
+
+    }
+
+}

+ 172 - 0
vendor/sabre/vobject/lib/ElementList.php

@@ -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-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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');
+
+    }
+
+    /* }}} */
+
+}

+ 15 - 0
vendor/sabre/vobject/lib/EofException.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Exception thrown by parser when the end of the stream has been reached,
+ * before this was expected.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class EofException extends ParseException {
+
+}

+ 322 - 0
vendor/sabre/vobject/lib/FreeBusyGenerator.php

@@ -0,0 +1,322 @@
+<?php
+
+namespace Sabre\VObject;
+
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\Recur\EventIterator;
+
+/**
+ * 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-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/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 EventIterator($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->hasTime()) {
+                                $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 VCalendar();
+        }
+
+        $vfreebusy = $calendar->createComponent('VFREEBUSY');
+        $calendar->add($vfreebusy);
+
+        if ($this->start) {
+            $dtstart = $calendar->createProperty('DTSTART');
+            $dtstart->setDateTime($this->start);
+            $vfreebusy->add($dtstart);
+        }
+        if ($this->end) {
+            $dtend = $calendar->createProperty('DTEND');
+            $dtend->setDateTime($this->end);
+            $vfreebusy->add($dtend);
+        }
+        $dtstamp = $calendar->createProperty('DTSTAMP');
+        $dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC')));
+        $vfreebusy->add($dtstamp);
+
+        foreach($busyTimes as $busyTime) {
+
+            $busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
+            $busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
+
+            $prop = $calendar->createProperty(
+                'FREEBUSY',
+                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
+            );
+            $prop['FBTYPE'] = $busyTime[2];
+            $vfreebusy->add($prop);
+
+        }
+
+        return $calendar;
+
+    }
+
+}
+

+ 937 - 0
vendor/sabre/vobject/lib/ITip/Broker.php

@@ -0,0 +1,937 @@
+<?php
+
+namespace Sabre\VObject\ITip;
+
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\DateTimeParser;
+use Sabre\VObject\Reader;
+use Sabre\VObject\Recur\EventIterator;
+
+/**
+ * The ITip\Broker class is a utility class that helps with processing
+ * so-called iTip messages.
+ *
+ * iTip is defined in rfc5546, stands for iCalendar Transport-Independent
+ * Interoperability Protocol, and describes the underlying mechanism for
+ * using iCalendar for scheduling for for example through email (also known as
+ * IMip) and CalDAV Scheduling.
+ *
+ * This class helps by:
+ *
+ * 1. Creating individual invites based on an iCalendar event for each
+ *    attendee.
+ * 2. Generating invite updates based on an iCalendar update. This may result
+ *    in new invites, updates and cancellations for attendees, if that list
+ *    changed.
+ * 3. On the receiving end, it can create a local iCalendar event based on
+ *    a received invite.
+ * 4. It can also process an invite update on a local event, ensuring that any
+ *    overridden properties from attendees are retained.
+ * 5. It can create a accepted or declined iTip reply based on an invite.
+ * 6. It can process a reply from an invite and update an events attendee
+ *     status based on a reply.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Broker {
+
+    /**
+     * This setting determines whether the rules for the SCHEDULE-AGENT
+     * parameter should be followed.
+     *
+     * This is a parameter defined on ATTENDEE properties, introduced by RFC
+     * 6638. This parameter allows a caldav client to tell the server 'Don't do
+     * any scheduling operations'.
+     *
+     * If this setting is turned on, any attendees with SCHEDULE-AGENT set to
+     * CLIENT will be ignored. This is the desired behavior for a CalDAV
+     * server, but if you're writing an iTip application that doesn't deal with
+     * CalDAV, you may want to ignore this parameter.
+     *
+     * @var bool
+     */
+    public $scheduleAgentServerRules = true;
+
+    /**
+     * The broker will try during 'parseEvent' figure out whether the change
+     * was significant.
+     *
+     * It uses a few different ways to do this. One of these ways is seeing if
+     * certain properties changed values. This list of specified here.
+     *
+     * This list is taken from:
+     * * http://tools.ietf.org/html/rfc5546#section-2.1.4
+     *
+     * @var string[]
+     */
+    public $significantChangeProperties = array(
+        'DTSTART',
+        'DTEND',
+        'DURATION',
+        'DUE',
+        'RRULE',
+        'RDATE',
+        'EXDATE',
+        'STATUS',
+    );
+
+    /**
+     * This method is used to process an incoming itip message.
+     *
+     * Examples:
+     *
+     * 1. A user is an attendee to an event. The organizer sends an updated
+     * meeting using a new iTip message with METHOD:REQUEST. This function
+     * will process the message and update the attendee's event accordingly.
+     *
+     * 2. The organizer cancelled the event using METHOD:CANCEL. We will update
+     * the users event to state STATUS:CANCELLED.
+     *
+     * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can
+     * update the organizers event to update the ATTENDEE with its correct
+     * PARTSTAT.
+     *
+     * The $existingObject is updated in-place. If there is no existing object
+     * (because it's a new invite for example) a new object will be created.
+     *
+     * If an existing object does not exist, and the method was CANCEL or
+     * REPLY, the message effectively gets ignored, and no 'existingObject'
+     * will be created.
+     *
+     * The updated $existingObject is also returned from this function.
+     *
+     * If the iTip message was not supported, we will always return false.
+     *
+     * @param Message $itipMessage
+     * @param VCalendar $existingObject
+     * @return VCalendar|null
+     */
+    public function processMessage(Message $itipMessage, VCalendar $existingObject = null) {
+
+        // We only support events at the moment.
+        if ($itipMessage->component !== 'VEVENT') {
+            return false;
+        }
+
+        switch($itipMessage->method) {
+
+            case 'REQUEST' :
+                return $this->processMessageRequest($itipMessage, $existingObject);
+
+            case 'CANCEL' :
+                return $this->processMessageCancel($itipMessage, $existingObject);
+
+            case 'REPLY' :
+                return $this->processMessageReply($itipMessage, $existingObject);
+
+            default :
+                // Unsupported iTip message
+                return null;
+
+        }
+
+        return $existingObject;
+
+    }
+
+    /**
+     * This function parses a VCALENDAR object and figure out if any messages
+     * need to be sent.
+     *
+     * A VCALENDAR object will be created from the perspective of either an
+     * attendee, or an organizer. You must pass a string identifying the
+     * current user, so we can figure out who in the list of attendees or the
+     * organizer we are sending this message on behalf of.
+     *
+     * It's possible to specify the current user as an array, in case the user
+     * has more than one identifying href (such as multiple emails).
+     *
+     * It $oldCalendar is specified, it is assumed that the operation is
+     * updating an existing event, which means that we need to look at the
+     * differences between events, and potentially send old attendees
+     * cancellations, and current attendees updates.
+     *
+     * If $calendar is null, but $oldCalendar is specified, we treat the
+     * operation as if the user has deleted an event. If the user was an
+     * organizer, this means that we need to send cancellation notices to
+     * people. If the user was an attendee, we need to make sure that the
+     * organizer gets the 'declined' message.
+     *
+     * @param VCalendar|string $calendar
+     * @param string|array $userHref
+     * @param VCalendar|string $oldCalendar
+     * @return array
+     */
+    public function parseEvent($calendar = null, $userHref, $oldCalendar = null) {
+
+        if ($oldCalendar) {
+            if (is_string($oldCalendar)) {
+                $oldCalendar = Reader::read($oldCalendar);
+            }
+            if (!isset($oldCalendar->VEVENT)) {
+                // We only support events at the moment
+                return array();
+            }
+
+            $oldEventInfo = $this->parseEventInfo($oldCalendar);
+        } else {
+            $oldEventInfo = array(
+                'organizer' => null,
+                'significantChangeHash' => '',
+                'attendees' => array(),
+            );
+        }
+
+        $userHref = (array)$userHref;
+
+        if (!is_null($calendar)) {
+
+            if (is_string($calendar)) {
+                $calendar = Reader::read($calendar);
+            }
+            if (!isset($calendar->VEVENT)) {
+                // We only support events at the moment
+                return array();
+            }
+            $eventInfo = $this->parseEventInfo($calendar);
+            if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
+                // If there were no attendees on either side of the equation,
+                // we don't need to do anything.
+                return array();
+            }
+            if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
+                // There was no organizer before or after the change.
+                return array();
+            }
+
+            $baseCalendar = $calendar;
+
+            // If the new object didn't have an organizer, the organizer
+            // changed the object from a scheduling object to a non-scheduling
+            // object. We just copy the info from the old object.
+            if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
+                $eventInfo['organizer'] = $oldEventInfo['organizer'];
+                $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
+            }
+
+        } else {
+            // The calendar object got deleted, we need to process this as a
+            // cancellation / decline.
+            if (!$oldCalendar) {
+                // No old and no new calendar, there's no thing to do.
+                return array();
+            }
+
+            $eventInfo = $oldEventInfo;
+
+            if (in_array($eventInfo['organizer'], $userHref)) {
+                // This is an organizer deleting the event.
+                $eventInfo['attendees'] = array();
+                // Increasing the sequence, but only if the organizer deleted
+                // the event.
+                $eventInfo['sequence']++;
+            } else {
+                // This is an attendee deleting the event.
+                foreach($eventInfo['attendees'] as $key=>$attendee) {
+                    if (in_array($attendee['href'], $userHref)) {
+                        $eventInfo['attendees'][$key]['instances'] = array('master' =>
+                            array('id'=>'master', 'partstat' => 'DECLINED')
+                        );
+                    }
+                }
+            }
+            $baseCalendar = $oldCalendar;
+
+        }
+
+        if (in_array($eventInfo['organizer'], $userHref)) {
+            return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
+        } elseif ($oldCalendar) {
+            // We need to figure out if the user is an attendee, but we're only
+            // doing so if there's an oldCalendar, because we only want to
+            // process updates, not creation of new events.
+            foreach($eventInfo['attendees'] as $attendee) {
+                if (in_array($attendee['href'], $userHref)) {
+                    return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
+                }
+            }
+        }
+        return array();
+
+    }
+
+    /**
+     * Processes incoming REQUEST messages.
+     *
+     * This is message from an organizer, and is either a new event
+     * invite, or an update to an existing one.
+     *
+     *
+     * @param Message $itipMessage
+     * @param VCalendar $existingObject
+     * @return VCalendar|null
+     */
+    protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {
+
+        if (!$existingObject) {
+            // This is a new invite, and we're just going to copy over
+            // all the components from the invite.
+            $existingObject = new VCalendar();
+            foreach($itipMessage->message->getComponents() as $component) {
+                $existingObject->add(clone $component);
+            }
+        } else {
+            // We need to update an existing object with all the new
+            // information. We can just remove all existing components
+            // and create new ones.
+            foreach($existingObject->getComponents() as $component) {
+                $existingObject->remove($component);
+            }
+            foreach($itipMessage->message->getComponents() as $component) {
+                $existingObject->add(clone $component);
+            }
+        }
+        return $existingObject;
+
+    }
+
+    /**
+     * Processes incoming CANCEL messages.
+     *
+     * This is a message from an organizer, and means that either an
+     * attendee got removed from an event, or an event got cancelled
+     * altogether.
+     *
+     * @param Message $itipMessage
+     * @param VCalendar $existingObject
+     * @return VCalendar|null
+     */
+    protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {
+
+        if (!$existingObject) {
+            // The event didn't exist in the first place, so we're just
+            // ignoring this message.
+        } else {
+            foreach($existingObject->VEVENT as $vevent) {
+                $vevent->STATUS = 'CANCELLED';
+                $vevent->SEQUENCE = $itipMessage->sequence;
+            }
+        }
+        return $existingObject;
+
+    }
+
+    /**
+     * Processes incoming REPLY messages.
+     *
+     * The message is a reply. This is for example an attendee telling
+     * an organizer he accepted the invite, or declined it.
+     *
+     * @param Message $itipMessage
+     * @param VCalendar $existingObject
+     * @return VCalendar|null
+     */
+    protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {
+
+        // A reply can only be processed based on an existing object.
+        // If the object is not available, the reply is ignored.
+        if (!$existingObject) {
+            return null;
+        }
+        $instances = array();
+        $requestStatus = '2.0;Success';
+
+        // Finding all the instances the attendee replied to.
+        foreach($itipMessage->message->VEVENT as $vevent) {
+            $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master';
+            $attendee = $vevent->ATTENDEE;
+            $instances[$recurId] = $attendee['PARTSTAT']->getValue();
+            if (isset($vevent->{'REQUEST-STATUS'})) {
+                $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
+            }
+        }
+
+        // Now we need to loop through the original organizer event, to find
+        // all the instances where we have a reply for.
+        $masterObject = null;
+        foreach($existingObject->VEVENT as $vevent) {
+            $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master';
+            if ($recurId==='master') {
+                $masterObject = $vevent;
+            }
+            if (isset($instances[$recurId])) {
+                $attendeeFound = false;
+                if (isset($vevent->ATTENDEE)) {
+                    foreach($vevent->ATTENDEE as $attendee) {
+                        if ($attendee->getValue() === $itipMessage->sender) {
+                            $attendeeFound = true;
+                            $attendee['PARTSTAT'] = $instances[$recurId];
+                            $attendee['SCHEDULE-STATUS'] = $requestStatus;
+                            // Un-setting the RSVP status, because we now know
+                            // that the attende already replied.
+                            unset($attendee['RSVP']);
+                            break;
+                        }
+                    }
+                }
+                if (!$attendeeFound) {
+                    // Adding a new attendee. The iTip documentation calls this
+                    // a party crasher.
+                    $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array(
+                        'PARTSTAT' => $instances[$recurId]
+                    ));
+                    if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
+                }
+                unset($instances[$recurId]);
+            }
+        }
+
+        if(!$masterObject) {
+            // No master object, we can't add new instances.
+            return null;
+        }
+        // If we got replies to instances that did not exist in the
+        // original list, it means that new exceptions must be created.
+        foreach($instances as $recurId=>$partstat) {
+
+            $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
+            $found = false;
+            $iterations = 1000;
+            do {
+
+                $newObject = $recurrenceIterator->getEventObject();
+                $recurrenceIterator->next();
+
+                if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) {
+                    $found = true;
+                }
+                $iterations--;
+
+            } while($recurrenceIterator->valid() && !$found && $iterations);
+
+            // Invalid recurrence id. Skipping this object.
+            if (!$found) continue;
+
+            unset(
+                $newObject->RRULE,
+                $newObject->EXDATE,
+                $newObject->RDATE
+            );
+            $attendeeFound = false;
+            if (isset($newObject->ATTENDEE)) {
+                foreach($newObject->ATTENDEE as $attendee) {
+                    if ($attendee->getValue() === $itipMessage->sender) {
+                        $attendeeFound = true;
+                        $attendee['PARTSTAT'] = $partstat;
+                        break;
+                    }
+                }
+            }
+            if (!$attendeeFound) {
+                // Adding a new attendee
+                $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array(
+                    'PARTSTAT' => $partstat
+                ));
+                if ($itipMessage->senderName) {
+                    $attendee['CN'] = $itipMessage->senderName;
+                }
+            }
+            $existingObject->add($newObject);
+
+        }
+        return $existingObject;
+
+    }
+
+    /**
+     * This method is used in cases where an event got updated, and we
+     * potentially need to send emails to attendees to let them know of updates
+     * in the events.
+     *
+     * We will detect which attendees got added, which got removed and create
+     * specific messages for these situations.
+     *
+     * @param VCalendar $calendar
+     * @param array $eventInfo
+     * @param array $oldEventInfo
+     * @return array
+     */
+    protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
+
+        // Merging attendee lists.
+        $attendees = array();
+        foreach($oldEventInfo['attendees'] as $attendee) {
+            $attendees[$attendee['href']] = array(
+                'href' => $attendee['href'],
+                'oldInstances' => $attendee['instances'],
+                'newInstances' => array(),
+                'name' => $attendee['name'],
+                'forceSend' => null,
+            );
+        }
+        foreach($eventInfo['attendees'] as $attendee) {
+            if (isset($attendees[$attendee['href']])) {
+                $attendees[$attendee['href']]['name'] = $attendee['name'];
+                $attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
+                $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
+            } else {
+                $attendees[$attendee['href']] = array(
+                    'href' => $attendee['href'],
+                    'oldInstances' => array(),
+                    'newInstances' => $attendee['instances'],
+                    'name' => $attendee['name'],
+                    'forceSend' => $attendee['forceSend'],
+                );
+            }
+        }
+
+        $messages = array();
+
+        foreach($attendees as $attendee) {
+
+            // An organizer can also be an attendee. We should not generate any
+            // messages for those.
+            if ($attendee['href']===$eventInfo['organizer']) {
+                continue;
+            }
+
+            $message = new Message();
+            $message->uid = $eventInfo['uid'];
+            $message->component = 'VEVENT';
+            $message->sequence = $eventInfo['sequence'];
+            $message->sender = $eventInfo['organizer'];
+            $message->senderName = $eventInfo['organizerName'];
+            $message->recipient = $attendee['href'];
+            $message->recipientName = $attendee['name'];
+
+            if (!$attendee['newInstances']) {
+
+                // If there are no instances the attendee is a part of, it
+                // means the attendee was removed and we need to send him a
+                // CANCEL.
+                $message->method = 'CANCEL';
+
+                // Creating the new iCalendar body.
+                $icalMsg = new VCalendar();
+                $icalMsg->METHOD = $message->method;
+                $event = $icalMsg->add('VEVENT', array(
+                    'UID' => $message->uid,
+                    'SEQUENCE' => $message->sequence,
+                ));
+                if (isset($calendar->VEVENT->SUMMARY)) {
+                    $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
+                }
+                $event->add(clone $calendar->VEVENT->DTSTART);
+                $org = $event->add('ORGANIZER', $eventInfo['organizer']);
+                if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
+                $event->add('ATTENDEE', $attendee['href'], array(
+                    'CN' => $attendee['name'],
+                ));
+                $message->significantChange = true;
+
+            } else {
+
+                // The attendee gets the updated event body
+                $message->method = 'REQUEST';
+
+                // Creating the new iCalendar body.
+                $icalMsg = new VCalendar();
+                $icalMsg->METHOD = $message->method;
+
+                foreach($calendar->select('VTIMEZONE') as $timezone) {
+                    $icalMsg->add(clone $timezone);
+                }
+
+                // We need to find out that this change is significant. If it's
+                // not, systems may opt to not send messages.
+                //
+                // We do this based on the 'significantChangeHash' which is
+                // some value that changes if there's a certain set of
+                // properties changed in the event, or simply if there's a
+                // difference in instances that the attendee is invited to.
+
+                $message->significantChange =
+                    $attendee['forceSend'] === 'REQUEST' ||
+                    array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
+                    $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash'];
+
+                foreach($attendee['newInstances'] as $instanceId => $instanceInfo) {
+
+                    $currentEvent = clone $eventInfo['instances'][$instanceId];
+                    if ($instanceId === 'master') {
+
+                        // We need to find a list of events that the attendee
+                        // is not a part of to add to the list of exceptions.
+                        $exceptions = array();
+                        foreach($eventInfo['instances'] as $instanceId=>$vevent) {
+                            if (!isset($attendee['newInstances'][$instanceId])) {
+                                $exceptions[] = $instanceId;
+                            }
+                        }
+
+                        // If there were exceptions, we need to add it to an
+                        // existing EXDATE property, if it exists.
+                        if ($exceptions) {
+                            if (isset($currentEvent->EXDATE)) {
+                                $currentEvent->EXDATE->setParts(array_merge(
+                                    $currentEvent->EXDATE->getParts(),
+                                    $exceptions
+                                ));
+                            } else {
+                                $currentEvent->EXDATE = $exceptions;
+                            }
+                        }
+
+                        // Cleaning up any scheduling information that
+                        // shouldn't be sent along.
+                        unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
+                        unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
+
+                        foreach($currentEvent->ATTENDEE as $attendee) {
+                            unset($attendee['SCHEDULE-FORCE-SEND']);
+                            unset($attendee['SCHEDULE-STATUS']);
+
+                            // We're adding PARTSTAT=NEEDS-ACTION to ensure that
+                            // iOS shows an "Inbox Item"
+                            if (!isset($attendee['PARTSTAT'])) {
+                                $attendee['PARTSTAT'] = 'NEEDS-ACTION';
+                            }
+
+                        }
+
+                    }
+
+                    $icalMsg->add($currentEvent);
+
+                }
+
+            }
+
+            $message->message = $icalMsg;
+            $messages[] = $message;
+
+        }
+
+        return $messages;
+
+    }
+
+    /**
+     * Parse an event update for an attendee.
+     *
+     * This function figures out if we need to send a reply to an organizer.
+     *
+     * @param VCalendar $calendar
+     * @param array $eventInfo
+     * @param array $oldEventInfo
+     * @param string $attendee
+     * @return Message[]
+     */
+    protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {
+
+        if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') {
+            return array();
+        }
+
+        // Don't bother generating messages for events that have already been
+        // cancelled.
+        if ($eventInfo['status']==='CANCELLED') {
+            return array();
+        }
+
+        $instances = array();
+        foreach($oldEventInfo['attendees'][$attendee]['instances'] as $instance) {
+
+            $instances[$instance['id']] = array(
+                'id' => $instance['id'],
+                'oldstatus' => $instance['partstat'],
+                'newstatus' => null,
+            );
+
+        }
+        foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) {
+
+            if (isset($instances[$instance['id']])) {
+                $instances[$instance['id']]['newstatus'] = $instance['partstat'];
+            } else {
+                $instances[$instance['id']] = array(
+                    'id' => $instance['id'],
+                    'oldstatus' => null,
+                    'newstatus' => $instance['partstat'],
+                );
+            }
+
+        }
+
+        // We need to also look for differences in EXDATE. If there are new
+        // items in EXDATE, it means that an attendee deleted instances of an
+        // event, which means we need to send DECLINED specifically for those
+        // instances.
+        // We only need to do that though, if the master event is not declined.
+        if ($instances['master']['newstatus'] !== 'DECLINED') {
+            foreach($eventInfo['exdate'] as $exDate) {
+
+                if (!in_array($exDate, $oldEventInfo['exdate'])) {
+                    if (isset($instances[$exDate])) {
+                        $instances[$exDate]['newstatus'] = 'DECLINED';
+                    } else {
+                        $instances[$exDate] = array(
+                            'id' => $exDate,
+                            'oldstatus' => null,
+                            'newstatus' => 'DECLINED',
+                        );
+                    }
+                }
+
+            }
+        }
+
+        // Gathering a few extra properties for each instance.
+        foreach($instances as $recurId=>$instanceInfo) {
+
+            if (isset($eventInfo['instances'][$recurId])) {
+                $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
+            } else {
+                $instances[$recurId]['dtstart'] = $recurId;
+            }
+
+        }
+
+        $message = new Message();
+        $message->uid = $eventInfo['uid'];
+        $message->method = 'REPLY';
+        $message->component = 'VEVENT';
+        $message->sequence = $eventInfo['sequence'];
+        $message->sender = $attendee;
+        $message->senderName = $eventInfo['attendees'][$attendee]['name'];
+        $message->recipient = $eventInfo['organizer'];
+        $message->recipientName = $eventInfo['organizerName'];
+
+        $icalMsg = new VCalendar();
+        $icalMsg->METHOD = 'REPLY';
+
+        $hasReply = false;
+
+        foreach($instances as $instance) {
+
+            if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
+                // Skip
+                continue;
+            }
+
+            $event = $icalMsg->add('VEVENT', array(
+                'UID' => $message->uid,
+                'SEQUENCE' => $message->sequence,
+            ));
+            $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():'';
+            // Adding properties from the correct source instance
+            if (isset($eventInfo['instances'][$instance['id']])) {
+                $instanceObj = $eventInfo['instances'][$instance['id']];
+                $event->add(clone $instanceObj->DTSTART);
+                if (isset($instanceObj->SUMMARY)) {
+                    $event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
+                } elseif ($summary) {
+                    $event->add('SUMMARY', $summary);
+                }
+            } else {
+                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
+                // Treat is as a DATE field
+                if (strlen($instance['id']) <= 8) {
+                    $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE'));
+                } else {
+                    $recur = $event->add('DTSTART', $dt);
+                }
+                if ($summary) {
+                    $event->add('SUMMARY', $summary);
+                }
+            }
+            if ($instance['id'] !== 'master') {
+                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
+                // Treat is as a DATE field
+                if (strlen($instance['id']) <= 8) {
+                    $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE'));
+                } else {
+                    $recur = $event->add('RECURRENCE-ID', $dt);
+                }
+            }
+            $organizer = $event->add('ORGANIZER', $message->recipient);
+            if ($message->recipientName) {
+                $organizer['CN'] = $message->recipientName;
+            }
+            $attendee = $event->add('ATTENDEE', $message->sender, array(
+                'PARTSTAT' => $instance['newstatus']
+            ));
+            if ($message->senderName) {
+                $attendee['CN'] = $message->senderName;
+            }
+            $hasReply = true;
+
+        }
+
+        if ($hasReply) {
+            $message->message = $icalMsg;
+            return array($message);
+        } else {
+            return array();
+        }
+
+    }
+
+    /**
+     * Returns attendee information and information about instances of an
+     * event.
+     *
+     * Returns an array with the following keys:
+     *
+     * 1. uid
+     * 2. organizer
+     * 3. organizerName
+     * 4. attendees
+     * 5. instances
+     *
+     * @param VCalendar $calendar
+     * @return array
+     */
+    protected function parseEventInfo(VCalendar $calendar = null) {
+
+        $uid = null;
+        $organizer = null;
+        $organizerName = null;
+        $organizerForceSend = null;
+        $sequence = null;
+        $timezone = null;
+        $status = null;
+        $organizerScheduleAgent = 'SERVER';
+
+        $significantChangeHash = '';
+
+        // Now we need to collect a list of attendees, and which instances they
+        // are a part of.
+        $attendees = array();
+
+        $instances = array();
+        $exdate = array();
+
+        foreach($calendar->VEVENT as $vevent) {
+
+            if (is_null($uid)) {
+                $uid = $vevent->UID->getValue();
+            } else {
+                if ($uid !== $vevent->UID->getValue()) {
+                    throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
+                }
+            }
+
+            if (!isset($vevent->DTSTART)) {
+                throw new ITipException('An event MUST have a DTSTART property.');
+            }
+
+            if (isset($vevent->ORGANIZER)) {
+                if (is_null($organizer)) {
+                    $organizer = $vevent->ORGANIZER->getNormalizedValue();
+                    $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null;
+                } else {
+                    if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
+                        throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
+                    }
+                }
+                $organizerForceSend =
+                    isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
+                    strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
+                    null;
+                $organizerScheduleAgent =
+                    isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
+                    strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
+                    'SERVER';
+            }
+            if (is_null($sequence) && isset($vevent->SEQUENCE)) {
+                $sequence = $vevent->SEQUENCE->getValue();
+            }
+            if (isset($vevent->EXDATE)) {
+                $exdate = $vevent->EXDATE->getParts();
+            }
+            if (isset($vevent->STATUS)) {
+                $status = strtoupper($vevent->STATUS->getValue());
+            }
+
+            $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master';
+            if ($recurId==='master') {
+                $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
+            }
+            if(isset($vevent->ATTENDEE)) {
+                foreach($vevent->ATTENDEE as $attendee) {
+
+                    if ($this->scheduleAgentServerRules &&
+                        isset($attendee['SCHEDULE-AGENT']) &&
+                        strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
+                    ) {
+                        continue;
+                    }
+                    $partStat =
+                        isset($attendee['PARTSTAT']) ?
+                        strtoupper($attendee['PARTSTAT']) :
+                        'NEEDS-ACTION';
+
+                    $forceSend =
+                        isset($attendee['SCHEDULE-FORCE-SEND']) ?
+                        strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
+                        null;
+
+
+                    if (isset($attendees[$attendee->getNormalizedValue()])) {
+                        $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array(
+                            'id' => $recurId,
+                            'partstat' => $partStat,
+                            'force-send' => $forceSend,
+                        );
+                    } else {
+                        $attendees[$attendee->getNormalizedValue()] = array(
+                            'href' => $attendee->getNormalizedValue(),
+                            'instances' => array(
+                                $recurId => array(
+                                    'id' => $recurId,
+                                    'partstat' => $partStat,
+                                ),
+                            ),
+                            'name' => isset($attendee['CN'])?(string)$attendee['CN']:null,
+                            'forceSend' => $forceSend,
+                        );
+                    }
+
+                }
+                $instances[$recurId] = $vevent;
+
+            }
+
+            foreach($this->significantChangeProperties as $prop) {
+                if (isset($vevent->$prop)) {
+                    $significantChangeHash.=$prop.':';
+                    foreach($vevent->select($prop) as $val) {
+                        $significantChangeHash.= $val->getValue().';';
+                    }
+                }
+            }
+
+        }
+        $significantChangeHash = md5($significantChangeHash);
+
+        return compact(
+            'uid',
+            'organizer',
+            'organizerName',
+            'organizerScheduleAgent',
+            'organizerForceSend',
+            'instances',
+            'attendees',
+            'sequence',
+            'exdate',
+            'timezone',
+            'significantChangeHash',
+            'status'
+        );
+
+    }
+
+}

+ 15 - 0
vendor/sabre/vobject/lib/ITip/ITipException.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Sabre\VObject\ITip;
+
+use Exception;
+
+/**
+ * This message is emitted in case of serious problems with iTip messages.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ITipException extends Exception {
+}

+ 141 - 0
vendor/sabre/vobject/lib/ITip/Message.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace Sabre\VObject\ITip;
+
+/**
+ * This class represents an iTip message
+ *
+ * A message holds all the information relevant to the message, including the
+ * object itself.
+ *
+ * It should for the most part be treated as immutable.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Message {
+
+    /**
+     * The object's UID
+     *
+     * @var string
+     */
+    public $uid;
+
+    /**
+     * The component type, such as VEVENT
+     *
+     * @var string
+     */
+    public $component;
+
+    /**
+     * Contains the ITip method, which is something like REQUEST, REPLY or
+     * CANCEL.
+     *
+     * @var string
+     */
+    public $method;
+
+    /**
+     * The current sequence number for the event.
+     *
+     * @var int
+     */
+    public $sequence;
+
+    /**
+     * The senders' email address.
+     *
+     * Note that this does not imply that this has to be used in a From: field
+     * if the message is sent by email. It may also be populated in Reply-To:
+     * or not at all.
+     *
+     * @var string
+     */
+    public $sender;
+
+    /**
+     * The name of the sender. This is often populated from a CN parameter from
+     * either the ORGANIZER or ATTENDEE, depending on the message.
+     *
+     * @var string|null
+     */
+    public $senderName;
+
+    /**
+     * The recipient's email address.
+     *
+     * @var string
+     */
+    public $recipient;
+
+    /**
+     * The name of the recipient. This is usually populated with the CN
+     * parameter from the ATTENDEE or ORGANIZER property, if it's available.
+     *
+     * @var string|null
+     */
+    public $recipientName;
+
+    /**
+     * After the message has been delivered, this should contain a string such
+     * as : 1.1;Sent or 1.2;Delivered.
+     *
+     * In case of a failure, this will hold the error status code.
+     *
+     * See:
+     * http://tools.ietf.org/html/rfc6638#section-7.3
+     *
+     * @var string
+     */
+    public $scheduleStatus;
+
+    /**
+     * The iCalendar / iTip body.
+     *
+     * @var \Sabre\VObject\Component\VCalendar
+     */
+    public $message;
+
+    /**
+     * This will be set to true, if the iTip broker considers the change
+     * 'significant'.
+     *
+     * In practice, this means that we'll only mark it true, if for instance
+     * DTSTART changed. This allows systems to only send iTip messages when
+     * significant changes happened. This is especially useful for iMip, as
+     * normally a ton of messages may be generated for normal calendar use.
+     *
+     * To see the list of properties that are considered 'significant', check
+     * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
+     *
+     * @var bool
+     */
+    public $significantChange = true;
+
+    /**
+     * Returns the schedule status as a string.
+     *
+     * For example:
+     * 1.2
+     *
+     * @return mixed bool|string
+     */
+    public function getScheduleStatus() {
+
+        if(!$this->scheduleStatus) {
+
+            return false;
+
+        } else {
+
+            list($scheduleStatus) = explode(';', $this->scheduleStatus);
+            return $scheduleStatus;
+
+        }
+
+    }
+
+}

+ 18 - 0
vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\VObject\ITip;
+
+/**
+ * SameOrganizerForAllComponentsException
+ *
+ * This exception is emitted when an event is encountered with more than one
+ * component (e.g.: exceptions), but the organizer is not identical in every
+ * component.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SameOrganizerForAllComponentsException extends ITipException {
+
+}

+ 205 - 0
vendor/sabre/vobject/lib/Node.php

@@ -0,0 +1,205 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * A node is the root class for every element in an iCalendar of vCard object.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
+
+    /**
+     * The following constants are used by the validate() method.
+     */
+    const REPAIR = 1;
+
+    /**
+     * Reference to the parent object, if this is not the top object.
+     *
+     * @var Node
+     */
+    public $parent;
+
+    /**
+     * Iterator override
+     *
+     * @var ElementList
+     */
+    protected $iterator = null;
+
+    /**
+     * The root document
+     *
+     * @var Component
+     */
+    protected $root;
+
+    /**
+     * Serializes the node into a mimedir format
+     *
+     * @return string
+     */
+    abstract public function serialize();
+
+    /**
+     * This method returns an array, with the representation as it should be
+     * encoded in json. This is used to create jCard or jCal documents.
+     *
+     * @return array
+     */
+    abstract public function jsonSerialize();
+
+    /* {{{ 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;
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        return array();
+
+    }
+
+    /* }}} */
+
+    /* {{{ Countable interface */
+
+    /**
+     * Returns the number of elements
+     *
+     * @return int
+     */
+    public function count() {
+
+        $it = $this->getIterator();
+        return $it->count();
+
+    }
+
+    /* }}} */
+
+    /* {{{ ArrayAccess Interface */
+
+
+    /**
+     * Checks if an item exists through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return bool
+     */
+    public function offsetExists($offset) {
+
+        $iterator = $this->getIterator();
+        return $iterator->offsetExists($offset);
+
+    }
+
+    /**
+     * Gets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return mixed
+     */
+    public function offsetGet($offset) {
+
+        $iterator = $this->getIterator();
+        return $iterator->offsetGet($offset);
+
+    }
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @param mixed $value
+     * @return void
+     */
+    public function offsetSet($offset, $value) {
+
+        $iterator = $this->getIterator();
+        $iterator->offsetSet($offset,$value);
+
+    // @codeCoverageIgnoreStart
+    //
+    // This method always throws an exception, so we ignore the closing
+    // brace
+    }
+    // @codeCoverageIgnoreEnd
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return void
+     */
+    public function offsetUnset($offset) {
+
+        $iterator = $this->getIterator();
+        $iterator->offsetUnset($offset);
+
+    // @codeCoverageIgnoreStart
+    //
+    // This method always throws an exception, so we ignore the closing
+    // brace
+    }
+    // @codeCoverageIgnoreEnd
+
+    /* }}} */
+}

+ 373 - 0
vendor/sabre/vobject/lib/Parameter.php

@@ -0,0 +1,373 @@
+<?php
+
+namespace Sabre\VObject;
+
+use
+    ArrayObject;
+
+/**
+ * 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-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Parameter extends Node {
+
+    /**
+     * Parameter name
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * vCard 2.1 allows parameters to be encoded without a name.
+     *
+     * We can deduce the parameter name based on it's value.
+     *
+     * @var bool
+     */
+    public $noName = false;
+
+    /**
+     * Parameter value
+     *
+     * @var string
+     */
+    protected $value;
+
+    /**
+     * Sets up the object.
+     *
+     * It's recommended to use the create:: factory method instead.
+     *
+     * @param string $name
+     * @param string $value
+     */
+    public function __construct(Document $root, $name, $value = null) {
+
+        $this->name = strtoupper($name);
+        $this->root = $root;
+        if (is_null($name)) {
+            $this->noName = true;
+            $this->name = static::guessParameterNameByValue($value);
+        }
+
+        // If guessParameterNameByValue() returns an empty string
+        // above, we're actually dealing with a parameter that has no value.
+        // In that case we have to move the value to the name.
+        if ($this->name === '') {
+            $this->noName = false;
+            $this->name = strtoupper($value);
+        } else {
+            $this->setValue($value);
+        }
+
+    }
+
+    /**
+     * Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
+     *
+     * Figuring out what the name should have been. Note that a ton of
+     * these are rather silly in 2014 and would probably rarely be
+     * used, but we like to be complete.
+     *
+     * @param string $value
+     * @return string
+     */
+    public static function guessParameterNameByValue($value) {
+        switch(strtoupper($value)) {
+
+            // Encodings
+            case '7-BIT' :
+            case 'QUOTED-PRINTABLE' :
+            case 'BASE64' :
+                $name = 'ENCODING';
+                break;
+
+            // Common types
+            case 'WORK' :
+            case 'HOME' :
+            case 'PREF' :
+
+                // Delivery Label Type
+            case 'DOM' :
+            case 'INTL' :
+            case 'POSTAL' :
+            case 'PARCEL' :
+
+                // Telephone types
+            case 'VOICE' :
+            case 'FAX' :
+            case 'MSG' :
+            case 'CELL' :
+            case 'PAGER' :
+            case 'BBS' :
+            case 'MODEM' :
+            case 'CAR' :
+            case 'ISDN' :
+            case 'VIDEO' :
+
+                // EMAIL types (lol)
+            case 'AOL' :
+            case 'APPLELINK' :
+            case 'ATTMAIL' :
+            case 'CIS' :
+            case 'EWORLD' :
+            case 'INTERNET' :
+            case 'IBMMAIL' :
+            case 'MCIMAIL' :
+            case 'POWERSHARE' :
+            case 'PRODIGY' :
+            case 'TLX' :
+            case 'X400' :
+
+                // Photo / Logo format types
+            case 'GIF' :
+            case 'CGM' :
+            case 'WMF' :
+            case 'BMP' :
+            case 'DIB' :
+            case 'PICT' :
+            case 'TIFF' :
+            case 'PDF ':
+            case 'PS' :
+            case 'JPEG' :
+            case 'MPEG' :
+            case 'MPEG2' :
+            case 'AVI' :
+            case 'QTIME' :
+
+                // Sound Digital Audio Type
+            case 'WAVE' :
+            case 'PCM' :
+            case 'AIFF' :
+
+                // Key types
+            case 'X509' :
+            case 'PGP' :
+                $name = 'TYPE';
+                break;
+
+            // Value types
+            case 'INLINE' :
+            case 'URL' :
+            case 'CONTENT-ID' :
+            case 'CID' :
+                $name = 'VALUE';
+                break;
+
+            default:
+                $name = '';
+        }
+
+        return $name;
+    }
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * @param string|array $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        $this->value = $value;
+
+    }
+
+    /**
+     * Returns the current value
+     *
+     * This method will always return a string, or null. If there were multiple
+     * values, it will automatically concatinate them (separated by comma).
+     *
+     * @return string|null
+     */
+    public function getValue() {
+
+        if (is_array($this->value)) {
+            return implode(',' , $this->value);
+        } else {
+            return $this->value;
+        }
+
+    }
+
+    /**
+     * Sets multiple values for this parameter.
+     *
+     * @param array $value
+     * @return void
+     */
+    public function setParts(array $value) {
+
+        $this->value = $value;
+
+    }
+
+    /**
+     * Returns all values for this parameter.
+     *
+     * If there were no values, an empty array will be returned.
+     *
+     * @return array
+     */
+    public function getParts() {
+
+        if (is_array($this->value)) {
+            return $this->value;
+        } elseif (is_null($this->value)) {
+            return array();
+        } else {
+            return array($this->value);
+        }
+
+    }
+
+    /**
+     * Adds a value to this parameter
+     *
+     * If the argument is specified as an array, all items will be added to the
+     * parameter value list.
+     *
+     * @param string|array $part
+     * @return void
+     */
+    public function addValue($part) {
+
+        if (is_null($this->value)) {
+            $this->value = $part;
+        } else {
+            $this->value = array_merge((array)$this->value, (array)$part);
+        }
+
+    }
+
+    /**
+     * Checks if this parameter contains the specified value.
+     *
+     * This is a case-insensitive match. It makes sense to call this for for
+     * instance the TYPE parameter, to see if it contains a keyword such as
+     * 'WORK' or 'FAX'.
+     *
+     * @param string $value
+     * @return bool
+     */
+    public function has($value) {
+
+        return in_array(
+            strtolower($value),
+            array_map('strtolower', (array)$this->value)
+        );
+
+    }
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        $value = $this->getParts();
+
+        if (count($value)===0) {
+            return $this->name . '=';
+        }
+
+        if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
+
+            return implode(';', $value);
+
+        }
+
+        return $this->name . '=' . array_reduce(
+            $value,
+            function($out, $item) {
+
+                if (!is_null($out)) $out.=',';
+
+                // If there's no special characters in the string, we'll use the simple
+                // format.
+                //
+                // The list of special characters is defined as:
+                //
+                // Any character except CONTROL, DQUOTE, ";", ":", ","
+                //
+                // by the iCalendar spec:
+                // https://tools.ietf.org/html/rfc5545#section-3.1
+                //
+                // And we add ^ to that because of:
+                // https://tools.ietf.org/html/rfc6868
+                //
+                // But we've found that iCal (7.0, shipped with OSX 10.9)
+                // severaly trips on + characters not being quoted, so we
+                // added + as well.
+                if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
+                    return $out.$item;
+                } else {
+                    // Enclosing in double-quotes, and using RFC6868 for encoding any
+                    // special characters
+                    $out.='"' . strtr(
+                        $item,
+                        array(
+                            '^'  => '^^',
+                            "\n" => '^n',
+                            '"'  => '^\'',
+                        )
+                    ) . '"';
+                    return $out;
+                }
+
+            }
+        );
+
+    }
+
+    /**
+     * This method returns an array, with the representation as it should be
+     * encoded in json. This is used to create jCard or jCal documents.
+     *
+     * @return array
+     */
+    public function jsonSerialize() {
+
+        return $this->value;
+
+    }
+
+    /**
+     * Called when this object is being cast to a string
+     *
+     * @return string
+     */
+    public function __toString() {
+
+        return (string)$this->getValue();
+
+    }
+
+    /**
+     * Returns the iterator for this object
+     *
+     * @return ElementList
+     */
+    public function getIterator() {
+
+        if (!is_null($this->iterator))
+            return $this->iterator;
+
+        return $this->iterator = new ArrayObject((array)$this->value);
+
+    }
+
+}

+ 13 - 0
vendor/sabre/vobject/lib/ParseException.php

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

+ 194 - 0
vendor/sabre/vobject/lib/Parser/Json.php

@@ -0,0 +1,194 @@
+<?php
+
+namespace Sabre\VObject\Parser;
+
+use
+    Sabre\VObject\Component\VCalendar,
+    Sabre\VObject\Component\VCard,
+    Sabre\VObject\ParseException,
+    Sabre\VObject\EofException;
+
+/**
+ * Json Parser.
+ *
+ * This parser parses both the jCal and jCard formats.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Json extends Parser {
+
+    /**
+     * The input data
+     *
+     * @var array
+     */
+    protected $input;
+
+    /**
+     * Root component
+     *
+     * @var Document
+     */
+    protected $root;
+
+    /**
+     * This method starts the parsing process.
+     *
+     * If the input was not supplied during construction, it's possible to pass
+     * it here instead.
+     *
+     * If either input or options are not supplied, the defaults will be used.
+     *
+     * @param resource|string|array|null $input
+     * @param int|null $options
+     * @return array
+     */
+    public function parse($input = null, $options = null) {
+
+        if (!is_null($input)) {
+            $this->setInput($input);
+        }
+        if (is_null($this->input)) {
+            throw new EofException('End of input stream, or no input supplied');
+        }
+
+        if (!is_null($options)) {
+            $this->options = $options;
+        }
+
+        switch($this->input[0]) {
+            case 'vcalendar' :
+                $this->root = new VCalendar(array(), false);
+                break;
+            case 'vcard' :
+                $this->root = new VCard(array(), false);
+                break;
+            default :
+                throw new ParseException('The root component must either be a vcalendar, or a vcard');
+
+        }
+        foreach($this->input[1] as $prop) {
+            $this->root->add($this->parseProperty($prop));
+        }
+        if (isset($this->input[2])) foreach($this->input[2] as $comp) {
+            $this->root->add($this->parseComponent($comp));
+        }
+
+        // Resetting the input so we can throw an feof exception the next time.
+        $this->input = null;
+
+        return $this->root;
+
+    }
+
+    /**
+     * Parses a component
+     *
+     * @param array $jComp
+     * @return \Sabre\VObject\Component
+     */
+    public function parseComponent(array $jComp) {
+
+        // We can remove $self from PHP 5.4 onward.
+        $self = $this;
+
+        $properties = array_map(
+            function($jProp) use ($self) {
+                return $self->parseProperty($jProp);
+            },
+            $jComp[1]
+        );
+
+        if (isset($jComp[2])) {
+
+            $components = array_map(
+                function($jComp) use ($self) {
+                    return $self->parseComponent($jComp);
+                },
+                $jComp[2]
+            );
+
+        } else $components = array();
+
+        return $this->root->createComponent(
+            $jComp[0],
+            array_merge($properties, $components),
+            $defaults = false
+        );
+
+    }
+
+    /**
+     * Parses properties.
+     *
+     * @param array $jProp
+     * @return \Sabre\VObject\Property
+     */
+    public function parseProperty(array $jProp) {
+
+        list(
+            $propertyName,
+            $parameters,
+            $valueType
+        ) = $jProp;
+
+        $propertyName = strtoupper($propertyName);
+
+        // This is the default class we would be using if we didn't know the
+        // value type. We're using this value later in this function.
+        $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
+
+        $parameters = (array)$parameters;
+
+        $value = array_slice($jProp, 3);
+
+        $valueType = strtoupper($valueType);
+
+        if (isset($parameters['group'])) {
+            $propertyName = $parameters['group'] . '.' . $propertyName;
+            unset($parameters['group']);
+        }
+
+        $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
+        $prop->setJsonValue($value);
+
+        // We have to do something awkward here. FlatText as well as Text
+        // represents TEXT values. We have to normalize these here. In the
+        // future we can get rid of FlatText once we're allowed to break BC
+        // again.
+        if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
+            $defaultPropertyClass = 'Sabre\VObject\Property\Text';
+        }
+
+        // If the value type we received (e.g.: TEXT) was not the default value
+        // type for the given property (e.g.: BDAY), we need to add a VALUE=
+        // parameter.
+        if ($defaultPropertyClass !== get_class($prop)) {
+            $prop["VALUE"] = $valueType;
+        }
+
+        return $prop;
+
+    }
+
+    /**
+     * Sets the input data
+     *
+     * @param resource|string|array $input
+     * @return void
+     */
+    public function setInput($input) {
+
+        if (is_resource($input)) {
+            $input = stream_get_contents($input);
+        }
+        if (is_string($input)) {
+            $input = json_decode($input);
+        }
+        $this->input = $input;
+
+    }
+
+}

+ 616 - 0
vendor/sabre/vobject/lib/Parser/MimeDir.php

@@ -0,0 +1,616 @@
+<?php
+
+namespace Sabre\VObject\Parser;
+
+use
+    Sabre\VObject\ParseException,
+    Sabre\VObject\EofException,
+    Sabre\VObject\Component,
+    Sabre\VObject\Property,
+    Sabre\VObject\Component\VCalendar,
+    Sabre\VObject\Component\VCard;
+
+/**
+ * MimeDir parser.
+ *
+ * This class parses iCalendar/vCard files and returns an array.
+ *
+ * The array is identical to the format jCard/jCal use.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class MimeDir extends Parser {
+
+    /**
+     * The input stream.
+     *
+     * @var resource
+     */
+    protected $input;
+
+    /**
+     * Root component
+     *
+     * @var Component
+     */
+    protected $root;
+
+    /**
+     * Parses an iCalendar or vCard file
+     *
+     * Pass a stream or a string. If null is parsed, the existing buffer is
+     * used.
+     *
+     * @param string|resource|null $input
+     * @param int|null $options
+     * @return array
+     */
+    public function parse($input = null, $options = null) {
+
+        $this->root = null;
+        if (!is_null($input)) {
+
+            $this->setInput($input);
+
+        }
+
+        if (!is_null($options)) $this->options = $options;
+
+        $this->parseDocument();
+
+        return $this->root;
+
+    }
+
+    /**
+     * Sets the input buffer. Must be a string or stream.
+     *
+     * @param resource|string $input
+     * @return void
+     */
+    public function setInput($input) {
+
+        // Resetting the parser
+        $this->lineIndex = 0;
+        $this->startLine = 0;
+
+        if (is_string($input)) {
+            // Convering to a stream.
+            $stream = fopen('php://temp', 'r+');
+            fwrite($stream, $input);
+            rewind($stream);
+            $this->input = $stream;
+        } elseif (is_resource($input)) {
+            $this->input = $input;
+        } else {
+            throw new \InvalidArgumentException('This parser can only read from strings or streams.');
+        }
+
+    }
+
+    /**
+     * Parses an entire document.
+     *
+     * @return void
+     */
+    protected function parseDocument() {
+
+        $line = $this->readLine();
+        switch(strtoupper($line)) {
+            case 'BEGIN:VCALENDAR' :
+                $class = isset(VCalendar::$componentMap['VCALENDAR'])
+                    ? VCalendar::$componentMap[$name]
+                    : 'Sabre\\VObject\\Component\\VCalendar';
+                break;
+            case 'BEGIN:VCARD' :
+                $class = isset(VCard::$componentMap['VCARD'])
+                    ? VCard::$componentMap['VCARD']
+                    : 'Sabre\\VObject\\Component\\VCard';
+                break;
+            default :
+                throw new ParseException('This parser only supports VCARD and VCALENDAR files');
+        }
+
+        $this->root = new $class(array(), false);
+
+        while(true) {
+
+            // Reading until we hit END:
+            $line = $this->readLine();
+            if (strtoupper(substr($line,0,4)) === 'END:') {
+                break;
+            }
+            $result = $this->parseLine($line);
+            if ($result) {
+                $this->root->add($result);
+            }
+
+        }
+
+        $name = strtoupper(substr($line, 4));
+        if ($name!==$this->root->name) {
+            throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
+        }
+
+    }
+
+    /**
+     * Parses a line, and if it hits a component, it will also attempt to parse
+     * the entire component
+     *
+     * @param string $line Unfolded line
+     * @return Node
+     */
+    protected function parseLine($line) {
+
+        // Start of a new component
+        if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
+
+            $component = $this->root->createComponent(substr($line,6), array(), false);
+
+            while(true) {
+
+                // Reading until we hit END:
+                $line = $this->readLine();
+                if (strtoupper(substr($line,0,4)) === 'END:') {
+                    break;
+                }
+                $result = $this->parseLine($line);
+                if ($result) {
+                    $component->add($result);
+                }
+
+            }
+
+            $name = strtoupper(substr($line, 4));
+            if ($name!==$component->name) {
+                throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
+            }
+
+            return $component;
+
+        } else {
+
+            // Property reader
+            $property = $this->readProperty($line);
+            if (!$property) {
+                // Ignored line
+                return false;
+            }
+            return $property;
+
+        }
+
+    }
+
+    /**
+     * We need to look ahead 1 line every time to see if we need to 'unfold'
+     * the next line.
+     *
+     * If that was not the case, we store it here.
+     *
+     * @var null|string
+     */
+    protected $lineBuffer;
+
+    /**
+     * The real current line number.
+     */
+    protected $lineIndex = 0;
+
+    /**
+     * In the case of unfolded lines, this property holds the line number for
+     * the start of the line.
+     *
+     * @var int
+     */
+    protected $startLine = 0;
+
+    /**
+     * Contains a 'raw' representation of the current line.
+     *
+     * @var string
+     */
+    protected $rawLine;
+
+    /**
+     * Reads a single line from the buffer.
+     *
+     * This method strips any newlines and also takes care of unfolding.
+     *
+     * @throws \Sabre\VObject\EofException
+     * @return string
+     */
+    protected function readLine() {
+
+        if (!is_null($this->lineBuffer)) {
+            $rawLine = $this->lineBuffer;
+            $this->lineBuffer = null;
+        } else {
+            do {
+                $eof = feof($this->input);
+
+                $rawLine = fgets($this->input);
+
+                if ($eof || (feof($this->input) && $rawLine===false)) {
+                    throw new EofException('End of document reached prematurely');
+                }
+                if ($rawLine === false) {
+                    throw new ParseException('Error reading from input stream');
+                }
+                $rawLine = rtrim($rawLine, "\r\n");
+            } while ($rawLine === ''); // Skipping empty lines
+            $this->lineIndex++;
+        }
+        $line = $rawLine;
+
+        $this->startLine = $this->lineIndex;
+
+        // Looking ahead for folded lines.
+        while (true) {
+
+            $nextLine = rtrim(fgets($this->input), "\r\n");
+            $this->lineIndex++;
+            if (!$nextLine) {
+                break;
+            }
+            if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
+                $line .= substr($nextLine, 1);
+                $rawLine .= "\n " . substr($nextLine, 1);
+            } else {
+                $this->lineBuffer = $nextLine;
+                break;
+            }
+
+        }
+        $this->rawLine = $rawLine;
+        return $line;
+
+    }
+
+    /**
+     * Reads a property or component from a line.
+     *
+     * @return void
+     */
+    protected function readProperty($line) {
+
+        if ($this->options & self::OPTION_FORGIVING) {
+            $propNameToken = 'A-Z0-9\-\._\\/';
+        } else {
+            $propNameToken = 'A-Z0-9\-\.';
+        }
+
+        $paramNameToken = 'A-Z0-9\-';
+        $safeChar = '^";:,';
+        $qSafeChar = '^"';
+
+        $regex = "/
+            ^(?P<name> [$propNameToken]+ ) (?=[;:])        # property name
+            |
+            (?<=:)(?P<propValue> .+)$                      # property value
+            |
+            ;(?P<paramName> [$paramNameToken]+) (?=[=;:])  # parameter name
+            |
+            (=|,)(?P<paramValue>                           # parameter value
+                (?: [$safeChar]*) |
+                \"(?: [$qSafeChar]+)\"
+            ) (?=[;:,])
+            /xi";
+
+        //echo $regex, "\n"; die();
+        preg_match_all($regex, $line, $matches,  PREG_SET_ORDER);
+
+        $property = array(
+            'name' => null,
+            'parameters' => array(),
+            'value' => null
+        );
+
+        $lastParam = null;
+
+        /**
+         * Looping through all the tokens.
+         *
+         * Note that we are looping through them in reverse order, because if a
+         * sub-pattern matched, the subsequent named patterns will not show up
+         * in the result.
+         */
+        foreach($matches as $match) {
+
+            if (isset($match['paramValue'])) {
+                if ($match['paramValue'] && $match['paramValue'][0] === '"') {
+                    $value = substr($match['paramValue'], 1, -1);
+                } else {
+                    $value = $match['paramValue'];
+                }
+
+                $value = $this->unescapeParam($value);
+
+                if (is_null($property['parameters'][$lastParam])) {
+                    $property['parameters'][$lastParam] = $value;
+                } elseif (is_array($property['parameters'][$lastParam])) {
+                    $property['parameters'][$lastParam][] = $value;
+                } else {
+                    $property['parameters'][$lastParam] = array(
+                        $property['parameters'][$lastParam],
+                        $value
+                    );
+                }
+                continue;
+            }
+            if (isset($match['paramName'])) {
+                $lastParam = strtoupper($match['paramName']);
+                if (!isset($property['parameters'][$lastParam])) {
+                    $property['parameters'][$lastParam] = null;
+                }
+                continue;
+            }
+            if (isset($match['propValue'])) {
+                $property['value'] = $match['propValue'];
+                continue;
+            }
+            if (isset($match['name']) && $match['name']) {
+                $property['name'] = strtoupper($match['name']);
+                continue;
+            }
+
+            // @codeCoverageIgnoreStart
+            throw new \LogicException('This code should not be reachable');
+            // @codeCoverageIgnoreEnd
+
+        }
+
+        if (is_null($property['value'])) {
+            $property['value'] = '';
+        }
+        if (!$property['name']) {
+            if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
+                return false;
+            }
+            throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
+        }
+
+        // vCard 2.1 states that parameters may appear without a name, and only
+        // a value. We can deduce the value based on it's name.
+        //
+        // Our parser will get those as parameters without a value instead, so
+        // we're filtering these parameters out first.
+        $namedParameters = array();
+        $namelessParameters = array();
+
+        foreach($property['parameters'] as $name=>$value) {
+            if (!is_null($value)) {
+                $namedParameters[$name] = $value;
+            } else {
+                $namelessParameters[] = $name;
+            }
+        }
+
+        $propObj = $this->root->createProperty($property['name'], null, $namedParameters);
+
+        foreach($namelessParameters as $namelessParameter) {
+            $propObj->add(null, $namelessParameter);
+        }
+
+        if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
+            $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
+        } else {
+            $propObj->setRawMimeDirValue($property['value']);
+        }
+
+        return $propObj;
+
+    }
+
+    /**
+     * Unescapes a property value.
+     *
+     * vCard 2.1 says:
+     *   * Semi-colons must be escaped in some property values, specifically
+     *     ADR, ORG and N.
+     *   * Semi-colons must be escaped in parameter values, because semi-colons
+     *     are also use to separate values.
+     *   * No mention of escaping backslashes with another backslash.
+     *   * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
+     *     span values over more than 1 line.
+     *
+     * vCard 3.0 says:
+     *   * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
+     *     escaped, all time time.
+     *   * Comma's are used for delimeters in multiple values
+     *   * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
+     *     as in some properties semi-colon is used for separators.
+     *   * Properties using semi-colons: N, ADR, GEO, ORG
+     *   * Both ADR and N's individual parts may be broken up further with a
+     *     comma.
+     *   * Properties using commas: NICKNAME, CATEGORIES
+     *
+     * vCard 4.0 (rfc6350) says:
+     *   * Commas must be escaped.
+     *   * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
+     *     delimiter, depending on the property.
+     *   * Backslashes must be escaped
+     *   * Newlines must be escaped as either \N or \n.
+     *   * Some compound properties may contain multiple parts themselves, so a
+     *     comma within a semi-colon delimited property may also be unescaped
+     *     to denote multiple parts _within_ the compound property.
+     *   * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
+     *   * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
+     *
+     * Even though the spec says that commas must always be escaped, the
+     * example for GEO in Section 6.5.2 seems to violate this.
+     *
+     * iCalendar 2.0 (rfc5545) says:
+     *   * Commas or semi-colons may be used as delimiters, depending on the
+     *     property.
+     *   * Commas, semi-colons, backslashes, newline (\N or \n) are always
+     *     escaped, unless they are delimiters.
+     *   * Colons shall not be escaped.
+     *   * Commas can be considered the 'default delimiter' and is described as
+     *     the delimiter in cases where the order of the multiple values is
+     *     insignificant.
+     *   * Semi-colons are described as the delimiter for 'structured values'.
+     *     They are specifically used in Semi-colons are used as a delimiter in
+     *     REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
+     *
+     * Now for the parameters
+     *
+     * If delimiter is not set (null) this method will just return a string.
+     * If it's a comma or a semi-colon the string will be split on those
+     * characters, and always return an array.
+     *
+     * @param string $input
+     * @param string $delimiter
+     * @return string|string[]
+     */
+    static public function unescapeValue($input, $delimiter = ';') {
+
+        $regex = '#  (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
+        if ($delimiter) {
+            $regex .= ' | (' . $delimiter . ')';
+        }
+        $regex .= ') #x';
+
+        $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+        $resultArray = array();
+        $result = '';
+
+        foreach($matches as $match) {
+
+            switch ($match) {
+                case '\\\\' :
+                    $result .='\\';
+                    break;
+                case '\N' :
+                case '\n' :
+                    $result .="\n";
+                    break;
+                case '\;' :
+                    $result .=';';
+                    break;
+                case '\,' :
+                    $result .=',';
+                    break;
+                case $delimiter :
+                    $resultArray[] = $result;
+                    $result = '';
+                    break;
+                default :
+                    $result .= $match;
+                    break;
+
+            }
+
+        }
+
+        $resultArray[] = $result;
+        return $delimiter ? $resultArray : $result;
+
+    }
+
+    /**
+     * Unescapes a parameter value.
+     *
+     * vCard 2.1:
+     *   * Does not mention a mechanism for this. In addition, double quotes
+     *     are never used to wrap values.
+     *   * This means that parameters can simply not contain colons or
+     *     semi-colons.
+     *
+     * vCard 3.0 (rfc2425, rfc2426):
+     *   * Parameters _may_ be surrounded by double quotes.
+     *   * If this is not the case, semi-colon, colon and comma may simply not
+     *     occur (the comma used for multiple parameter values though).
+     *   * If it is surrounded by double-quotes, it may simply not contain
+     *     double-quotes.
+     *   * This means that a parameter can in no case encode double-quotes, or
+     *     newlines.
+     *
+     * vCard 4.0 (rfc6350)
+     *   * Behavior seems to be identical to vCard 3.0
+     *
+     * iCalendar 2.0 (rfc5545)
+     *   * Behavior seems to be identical to vCard 3.0
+     *
+     * Parameter escaping mechanism (rfc6868) :
+     *   * This rfc describes a new way to escape parameter values.
+     *   * New-line is encoded as ^n
+     *   * ^ is encoded as ^^.
+     *   * " is encoded as ^'
+     *
+     * @param string $input
+     * @return void
+     */
+    private function unescapeParam($input) {
+
+        return
+            preg_replace_callback(
+                '#(\^(\^|n|\'))#',
+                function($matches) {
+                    switch($matches[2]) {
+                        case 'n' :
+                            return "\n";
+                        case '^' :
+                            return '^';
+                        case '\'' :
+                            return '"';
+
+                    // @codeCoverageIgnoreStart
+                    }
+                    // @codeCoverageIgnoreEnd
+                },
+                $input
+            );
+    }
+
+    /**
+     * Gets the full quoted printable value.
+     *
+     * We need a special method for this, because newlines have both a meaning
+     * in vCards, and in QuotedPrintable.
+     *
+     * This method does not do any decoding.
+     *
+     * @return string
+     */
+    private function extractQuotedPrintableValue() {
+
+        // We need to parse the raw line again to get the start of the value.
+        //
+        // We are basically looking for the first colon (:), but we need to
+        // skip over the parameters first, as they may contain one.
+        $regex = '/^
+            (?: [^:])+ # Anything but a colon
+            (?: "[^"]")* # A parameter in double quotes
+            : # start of the value we really care about
+            (.*)$
+        /xs';
+
+        preg_match($regex, $this->rawLine, $matches);
+
+        $value = $matches[1];
+        // Removing the first whitespace character from every line. Kind of
+        // like unfolding, but we keep the newline.
+        $value = str_replace("\n ", "\n", $value);
+
+        // Microsoft products don't always correctly fold lines, they may be
+        // missing a whitespace. So if 'forgiving' is turned on, we will take
+        // those as well.
+        if ($this->options & self::OPTION_FORGIVING) {
+            while(substr($value,-1) === '=') {
+                // Reading the line
+                $this->readLine();
+                // Grabbing the raw form
+                $value.="\n" . $this->rawLine;
+            }
+        }
+
+        return $value;
+
+    }
+
+}

+ 77 - 0
vendor/sabre/vobject/lib/Parser/Parser.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace Sabre\VObject\Parser;
+
+/**
+ * Abstract parser.
+ *
+ * This class serves as a base-class for the different parsers.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class Parser {
+
+    /**
+     * Turning on this option makes the parser more forgiving.
+     *
+     * In the case of the MimeDir parser, this means that the parser will
+     * accept slashes and underscores in property names, and it will also
+     * attempt to fix Microsoft vCard 2.1's broken line folding.
+     */
+    const OPTION_FORGIVING = 1;
+
+    /**
+     * If this option is turned on, any lines we cannot parse will be ignored
+     * by the reader.
+     */
+    const OPTION_IGNORE_INVALID_LINES = 2;
+
+    /**
+     * Bitmask of parser options
+     *
+     * @var int
+     */
+    protected $options;
+
+    /**
+     * Creates the parser.
+     *
+     * Optionally, it's possible to parse the input stream here.
+     *
+     * @param mixed $input
+     * @param int $options Any parser options (OPTION constants).
+     * @return void
+     */
+    public function __construct($input = null, $options = 0) {
+
+        if (!is_null($input)) {
+            $this->setInput($input);
+        }
+        $this->options = $options;
+    }
+
+    /**
+     * This method starts the parsing process.
+     *
+     * If the input was not supplied during construction, it's possible to pass
+     * it here instead.
+     *
+     * If either input or options are not supplied, the defaults will be used.
+     *
+     * @param mixed $input
+     * @param int|null $options
+     * @return array
+     */
+    abstract public function parse($input = null, $options = null);
+
+    /**
+     * Sets the input data
+     *
+     * @param mixed $input
+     * @return void
+     */
+    abstract public function setInput($input);
+
+}

+ 518 - 0
vendor/sabre/vobject/lib/Property.php

@@ -0,0 +1,518 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Property
+ *
+ * A property is always in a KEY:VALUE structure, and may optionally contain
+ * parameters.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class Property extends Node {
+
+    /**
+     * Property name.
+     *
+     * This will contain a string such as DTSTART, SUMMARY, FN.
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Property group.
+     *
+     * This is only used in vcards
+     *
+     * @var string
+     */
+    public $group;
+
+    /**
+     * List of parameters
+     *
+     * @var array
+     */
+    public $parameters = array();
+
+    /**
+     * Current value
+     *
+     * @var mixed
+     */
+    protected $value;
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = ';';
+
+    /**
+     * Creates the generic property.
+     *
+     * Parameters must be specified in key=>value syntax.
+     *
+     * @param Component $root The root document
+     * @param string $name
+     * @param string|array|null $value
+     * @param array $parameters List of parameters
+     * @param string $group The vcard property group
+     * @return void
+     */
+    public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
+
+        $this->name = $name;
+        $this->group = $group;
+
+        $this->root = $root;
+
+        foreach($parameters as $k=>$v) {
+            $this->add($k, $v);
+        }
+
+        if (!is_null($value)) {
+            $this->setValue($value);
+        }
+
+    }
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * @param string|array $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        $this->value = $value;
+
+    }
+
+    /**
+     * Returns the current value.
+     *
+     * This method will always return a singular value. If this was a
+     * multi-value object, some decision will be made first on how to represent
+     * it as a string.
+     *
+     * To get the correct multi-value version, use getParts.
+     *
+     * @return string
+     */
+    public function getValue() {
+
+        if (is_array($this->value)) {
+            if (count($this->value)==0) {
+                return null;
+            } elseif (count($this->value)===1) {
+                return $this->value[0];
+            } else {
+                return $this->getRawMimeDirValue($this->value);
+            }
+        } else {
+            return $this->value;
+        }
+
+    }
+
+    /**
+     * Sets a multi-valued property.
+     *
+     * @param array $parts
+     * @return void
+     */
+    public function setParts(array $parts) {
+
+        $this->value = $parts;
+
+    }
+
+    /**
+     * Returns a multi-valued property.
+     *
+     * This method always returns an array, if there was only a single value,
+     * it will still be wrapped in an array.
+     *
+     * @return array
+     */
+    public function getParts() {
+
+        if (is_null($this->value)) {
+            return array();
+        } elseif (is_array($this->value)) {
+            return $this->value;
+        } else {
+            return array($this->value);
+        }
+
+    }
+
+    /**
+     * Adds a new parameter, and returns the new item.
+     *
+     * If a parameter with same name already existed, the values will be
+     * combined.
+     * If nameless parameter is added, we try to guess it's name.
+     *
+     * @param string $name
+     * @param string|null|array $value
+     * @return Node
+     */
+    public function add($name, $value = null) {
+        $noName = false;
+        if ($name === null) {
+            $name = Parameter::guessParameterNameByValue($value);
+            $noName = true;
+        }
+
+        if (isset($this->parameters[strtoupper($name)])) {
+            $this->parameters[strtoupper($name)]->addValue($value);
+        }
+        else {
+            $param = new Parameter($this->root, $name, $value);
+            $param->noName = $noName;
+            $this->parameters[$param->name] = $param;
+        }
+    }
+
+    /**
+     * Returns an iterable list of children
+     *
+     * @return array
+     */
+    public function parameters() {
+
+        return $this->parameters;
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    abstract public function getValueType();
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    abstract public function setRawMimeDirValue($val);
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    abstract public function getRawMimeDirValue();
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        $str = $this->name;
+        if ($this->group) $str = $this->group . '.' . $this->name;
+
+        foreach($this->parameters as $param) {
+
+            $str.=';' . $param->serialize();
+
+        }
+
+        $str.=':' . $this->getRawMimeDirValue();
+
+        $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;
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        return $this->getParts();
+
+    }
+
+    /**
+     * Sets the json value, as it would appear in a jCard or jCal object.
+     *
+     * The value must always be an array.
+     *
+     * @param array $value
+     * @return void
+     */
+    public function setJsonValue(array $value) {
+
+        if (count($value)===1) {
+            $this->setValue(reset($value));
+        } else {
+            $this->setValue($value);
+        }
+
+    }
+
+    /**
+     * This method returns an array, with the representation as it should be
+     * encoded in json. This is used to create jCard or jCal documents.
+     *
+     * @return array
+     */
+    public function jsonSerialize() {
+
+        $parameters = array();
+
+        foreach($this->parameters as $parameter) {
+            if ($parameter->name === 'VALUE') {
+                continue;
+            }
+            $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
+        }
+        // In jCard, we need to encode the property-group as a separate 'group'
+        // parameter.
+        if ($this->group) {
+            $parameters['group'] = $this->group;
+        }
+
+        return array_merge(
+            array(
+                strtolower($this->name),
+                (object)$parameters,
+                strtolower($this->getValueType()),
+            ),
+            $this->getJsonValue()
+        );
+    }
+
+
+    /**
+     * Called when this object is being cast to a string.
+     *
+     * If the property only had a single value, you will get just that. In the
+     * case the property had multiple values, the contents will be escaped and
+     * combined with ,.
+     *
+     * @return string
+     */
+    public function __toString() {
+
+        return (string)$this->getValue();
+
+    }
+
+    /* 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.
+     *
+     * If the parameter does not exist, null is returned.
+     *
+     * @param string $name
+     * @return Node
+     */
+    public function offsetGet($name) {
+
+        if (is_int($name)) return parent::offsetGet($name);
+        $name = strtoupper($name);
+
+        if (!isset($this->parameters[$name])) {
+            return null;
+        }
+
+        return $this->parameters[$name];
+
+    }
+
+    /**
+     * 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);
+            // @codeCoverageIgnoreStart
+            // This will never be reached, because an exception is always
+            // thrown.
+            return;
+            // @codeCoverageIgnoreEnd
+        }
+
+        $param = new Parameter($this->root, $name, $value);
+        $this->parameters[$param->name] = $param;
+
+    }
+
+    /**
+     * 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);
+            // @codeCoverageIgnoreStart
+            // This will never be reached, because an exception is always
+            // thrown.
+            return;
+            // @codeCoverageIgnoreEnd
+        }
+
+        unset($this->parameters[strtoupper($name)]);
+
+    }
+    /* }}} */
+
+    /**
+     * This method is automatically called when the object is cloned.
+     * Specifically, this will ensure all child elements are also cloned.
+     *
+     * @return void
+     */
+    public function __clone() {
+
+        foreach($this->parameters as $key=>$child) {
+            $this->parameters[$key] = clone $child;
+            $this->parameters[$key]->parent = $this;
+        }
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   - Node::REPAIR - If something is broken, and automatic repair may
+     *                    be attempted.
+     *
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $warnings = array();
+
+        // Checking if our value is UTF-8
+        if (!StringUtil::isUTF8($this->getRawMimeDirValue())) {
+
+            $oldValue = $this->getRawMimeDirValue();
+            $level = 3;
+            if ($options & self::REPAIR) {
+                $newValue = StringUtil::convertToUTF8($oldValue);
+                if (true || StringUtil::isUTF8($newValue)) {
+                    $this->setRawMimeDirValue($newValue);
+                    $level = 1;
+                }
+
+            }
+
+
+            if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) {
+                $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')';
+            } else {
+                $message = 'Property is not valid UTF-8! ' . $oldValue;
+            }
+
+            $warnings[] = array(
+                'level' => $level,
+                'message' => $message,
+                'node' => $this,
+            );
+        }
+
+        // Checking if the propertyname does not contain any invalid bytes.
+        if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
+            $warnings[] = array(
+                'level' => 1,
+                'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
+                'node' => $this,
+            );
+            if ($options & self::REPAIR) {
+                // Uppercasing and converting underscores to dashes.
+                $this->name = strtoupper(
+                    str_replace('_', '-', $this->name)
+                );
+                // Removing every other invalid character
+                $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
+
+            }
+
+        }
+
+        // Validating inner parameters
+        foreach($this->parameters as $param) {
+            $warnings = array_merge($warnings, $param->validate($options));
+        }
+
+        return $warnings;
+
+    }
+
+}

+ 127 - 0
vendor/sabre/vobject/lib/Property/Binary.php

@@ -0,0 +1,127 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    LogicException,
+    Sabre\VObject\Property;
+
+/**
+ * BINARY property
+ *
+ * This object represents BINARY values.
+ *
+ * Binary values are most commonly used by the iCalendar ATTACH property, and
+ * the vCard PHOTO property.
+ *
+ * This property will transparently encode and decode to base64.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Binary extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * @param string|array $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        if(is_array($value)) {
+
+            if(count($value) === 1) {
+                $this->value = $value[0];
+            } else {
+                throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
+            }
+
+        } else {
+
+            $this->value = $value;
+
+        }
+
+    }
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->value = base64_decode($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return base64_encode($this->value);
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return 'BINARY';
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        return array(base64_encode($this->getValue()));
+
+    }
+
+    /**
+     * Sets the json value, as it would appear in a jCard or jCal object.
+     *
+     * The value must always be an array.
+     *
+     * @param array $value
+     * @return void
+     */
+    public function setJsonValue(array $value) {
+
+        $value = array_map('base64_decode', $value);
+        parent::setJsonValue($value);
+
+    }
+
+}

+ 63 - 0
vendor/sabre/vobject/lib/Property/Boolean.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    Sabre\VObject\Property;
+
+/**
+ * Boolean property
+ *
+ * This object represents BOOLEAN values. These are always the case-insenstive
+ * string TRUE or FALSE.
+ *
+ * Automatic conversion to PHP's true and false are done.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Boolean extends Property {
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $val = strtoupper($val)==='TRUE'?true:false;
+        $this->setValue($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return $this->value?'TRUE':'FALSE';
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return 'BOOLEAN';
+
+    }
+
+}

+ 49 - 0
vendor/sabre/vobject/lib/Property/FlatText.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+/**
+ * FlatText property
+ *
+ * This object represents certain TEXT values.
+ *
+ * Specifically, this property is used for text values where there is only 1
+ * part. Semi-colons and colons will be de-escaped when deserializing, but if
+ * any semi-colons or commas appear without a backslash, we will not assume
+ * that they are delimiters.
+ *
+ * vCard 2.1 specifically has a whole bunch of properties where this may
+ * happen, as it only defines a delimiter for a few properties.
+ *
+ * vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
+ * delimiter, depending on the property.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class FlatText extends Text {
+
+    /**
+     * Field separator
+     *
+     * @var string
+     */
+    public $delimiter = ',';
+
+    /**
+     * Sets the value as a quoted-printable encoded string.
+     *
+     * Overriding this so we're not splitting on a ; delimiter.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setQuotedPrintableValue($val) {
+
+        $val = quoted_printable_decode($val);
+        $this->setValue($val);
+
+    }
+
+}

+ 104 - 0
vendor/sabre/vobject/lib/Property/Float.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    Sabre\VObject\Property;
+
+/**
+ * Float property
+ *
+ * This object represents FLOAT values. These can be 1 or more floating-point
+ * numbers.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Float extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = ';';
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $val = explode($this->delimiter, $val);
+        foreach($val as &$item) {
+            $item = (float)$item;
+        }
+        $this->setParts($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return implode(
+            $this->delimiter,
+            $this->getParts()
+        );
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "FLOAT";
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $val = array_map(
+            function($item) {
+
+                return (float)$item;
+
+            },
+            $this->getParts()
+        );
+
+        // Special-casing the GEO property.
+        //
+        // See:
+        // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
+        if ($this->name==='GEO') {
+            return array($val);
+        } else {
+            return $val;
+        }
+
+    }
+}

+ 61 - 0
vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+use
+    Sabre\VObject\Property\Text;
+
+/**
+ * CalAddress property
+ *
+ * This object encodes CAL-ADDRESS values, as defined in rfc5545
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalAddress extends Text {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return 'CAL-ADDRESS';
+
+    }
+
+    /**
+     * This returns a normalized form of the value.
+     *
+     * This is primarily used right now to turn mixed-cased schemes in user
+     * uris to lower-case.
+     *
+     * Evolution in particular tends to encode mailto: as MAILTO:.
+     *
+     * @return string
+     */
+    public function getNormalizedValue() {
+
+        $input = $this->getValue();
+        if (!strpos($input, ':')) {
+            return $input;
+        }
+        list($schema, $everythingElse) = explode(':', $input, 2);
+        return strtolower($schema) . ':' . $everythingElse;
+
+    }
+}

+ 18 - 0
vendor/sabre/vobject/lib/Property/ICalendar/Date.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+/**
+ * DateTime property
+ *
+ * This object represents DATE values, as defined here:
+ *
+ * http://tools.ietf.org/html/rfc5545#section-3.3.5
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Date extends DateTime {
+
+}

+ 362 - 0
vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php

@@ -0,0 +1,362 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Parser\MimeDir,
+    Sabre\VObject\DateTimeParser,
+    Sabre\VObject\TimeZoneUtil;
+
+/**
+ * DateTime property
+ *
+ * This object represents DATE-TIME values, as defined here:
+ *
+ * http://tools.ietf.org/html/rfc5545#section-3.3.4
+ *
+ * This particular object has a bit of hackish magic that it may also in some
+ * cases represent a DATE value. This is because it's a common usecase to be
+ * able to change a DATE-TIME into a DATE.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class DateTime extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = ',';
+
+    /**
+     * Sets a multi-valued property.
+     *
+     * You may also specify DateTime objects here.
+     *
+     * @param array $parts
+     * @return void
+     */
+    public function setParts(array $parts) {
+
+        if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
+            $this->setDateTimes($parts);
+        } else {
+            parent::setParts($parts);
+        }
+
+    }
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * Instead of strings, you may also use DateTime here.
+     *
+     * @param string|array|\DateTime $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) {
+            $this->setDateTimes($value);
+        } elseif ($value instanceof \DateTime) {
+            $this->setDateTimes(array($value));
+        } else {
+            parent::setValue($value);
+        }
+
+    }
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue(explode($this->delimiter, $val));
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return implode($this->delimiter, $this->getParts());
+
+    }
+
+    /**
+     * Returns true if this is a DATE-TIME value, false if it's a DATE.
+     *
+     * @return bool
+     */
+    public function hasTime() {
+
+        return strtoupper((string)$this['VALUE']) !== 'DATE';
+
+    }
+
+    /**
+     * Returns a date-time value.
+     *
+     * Note that if this property contained more than 1 date-time, only the
+     * first will be returned. To get an array with multiple values, call
+     * getDateTimes.
+     *
+     * @return \DateTime
+     */
+    public function getDateTime() {
+
+        $dt = $this->getDateTimes();
+        if (!$dt) return null;
+
+        return $dt[0];
+
+    }
+
+    /**
+     * Returns multiple date-time values.
+     *
+     * @return \DateTime[]
+     */
+    public function getDateTimes() {
+
+        // Finding the timezone.
+        $tz = $this['TZID'];
+
+        if ($tz) {
+            $tz = TimeZoneUtil::getTimeZone((string)$tz, $this->root);
+        }
+
+        $dts = array();
+        foreach($this->getParts() as $part) {
+            $dts[] = DateTimeParser::parse($part, $tz);
+        }
+        return $dts;
+
+    }
+
+    /**
+     * Sets the property as a DateTime object.
+     *
+     * @param \DateTime $dt
+     * @param bool isFloating If set to true, timezones will be ignored.
+     * @return void
+     */
+    public function setDateTime(\DateTime $dt, $isFloating = false) {
+
+        $this->setDateTimes(array($dt), $isFloating);
+
+    }
+
+    /**
+     * Sets the property as multiple date-time objects.
+     *
+     * The first value will be used as a reference for the timezones, and all
+     * the otehr values will be adjusted for that timezone
+     *
+     * @param \DateTime[] $dt
+     * @param bool isFloating If set to true, timezones will be ignored.
+     * @return void
+     */
+    public function setDateTimes(array $dt, $isFloating = false) {
+
+        $values = array();
+
+        if($this->hasTime()) {
+
+            $tz = null;
+            $isUtc = false;
+
+            foreach($dt as $d) {
+
+                if ($isFloating) {
+                    $values[] = $d->format('Ymd\\THis');
+                    continue;
+                }
+                if (is_null($tz)) {
+                    $tz = $d->getTimeZone();
+                    $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+                    if (!$isUtc) {
+                        $this->offsetSet('TZID', $tz->getName());
+                    }
+                } else {
+                    $d->setTimeZone($tz);
+                }
+
+                if ($isUtc) {
+                    $values[] = $d->format('Ymd\\THis\\Z');
+                } else {
+                    $values[] = $d->format('Ymd\\THis');
+                }
+
+            }
+            if ($isUtc || $isFloating) {
+                $this->offsetUnset('TZID');
+            }
+
+        } else {
+
+            foreach($dt as $d) {
+
+                $values[] = $d->format('Ymd');
+
+            }
+            $this->offsetUnset('TZID');
+
+        }
+
+        $this->value = $values;
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return $this->hasTime()?'DATE-TIME':'DATE';
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $dts = $this->getDateTimes();
+        $hasTime = $this->hasTime();
+
+        $tz = $dts[0]->getTimeZone();
+        $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+
+        return array_map(
+            function($dt) use ($hasTime, $isUtc) {
+
+                if ($hasTime) {
+                    return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':'');
+                } else {
+                    return $dt->format('Y-m-d');
+                }
+
+            },
+            $dts
+        );
+
+    }
+
+    /**
+     * Sets the json value, as it would appear in a jCard or jCal object.
+     *
+     * The value must always be an array.
+     *
+     * @param array $value
+     * @return void
+     */
+    public function setJsonValue(array $value) {
+
+        // dates and times in jCal have one difference to dates and times in
+        // iCalendar. In jCal date-parts are separated by dashes, and
+        // time-parts are separated by colons. It makes sense to just remove
+        // those.
+        $this->setValue(
+            array_map(
+                function($item) {
+
+                    return strtr($item, array(':'=>'', '-'=>''));
+
+                },
+                $value
+            )
+        );
+
+    }
+    /**
+     * We need to intercept offsetSet, because it may be used to alter the
+     * VALUE from DATE-TIME to DATE or vice-versa.
+     *
+     * @param string $name
+     * @param mixed $value
+     * @return void
+     */
+    public function offsetSet($name, $value) {
+
+        parent::offsetSet($name, $value);
+        if (strtoupper($name)!=='VALUE') {
+            return;
+        }
+
+        // This will ensure that dates are correctly encoded.
+        $this->setDateTimes($this->getDateTimes());
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $messages = parent::validate($options);
+        $valueType = $this->getValueType();
+        $value = $this->getValue();
+        try {
+            switch($valueType) {
+                case 'DATE' :
+                    $foo = DateTimeParser::parseDate($value);
+                    break;
+                case 'DATE-TIME' :
+                    $foo = DateTimeParser::parseDateTime($value);
+                    break;
+            }
+        } catch (\LogicException $e) {
+            $messages[] = array(
+                'level' => 3,
+                'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType,
+                'node' => $this,
+            );
+        }
+        return $messages;
+
+    }
+}

+ 86 - 0
vendor/sabre/vobject/lib/Property/ICalendar/Duration.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Parser\MimeDir,
+    Sabre\VObject\DateTimeParser;
+
+/**
+ * Duration property
+ *
+ * This object represents DURATION values, as defined here:
+ *
+ * http://tools.ietf.org/html/rfc5545#section-3.3.6
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Duration extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = ',';
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue(explode($this->delimiter, $val));
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return implode($this->delimiter, $this->getParts());
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return 'DURATION';
+
+    }
+
+    /**
+     * Returns a DateInterval representation of the Duration property.
+     *
+     * If the property has more than one value, only the first is returned.
+     *
+     * @return \DateInterval
+     */
+    public function getDateInterval() {
+
+        $parts = $this->getParts();
+        $value = $parts[0];
+        return DateTimeParser::parseDuration($value);
+
+    }
+
+}

+ 129 - 0
vendor/sabre/vobject/lib/Property/ICalendar/Period.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Parser\MimeDir,
+    Sabre\VObject\DateTimeParser;
+
+/**
+ * Period property
+ *
+ * This object represents PERIOD values, as defined here:
+ *
+ * http://tools.ietf.org/html/rfc5545#section-3.8.2.6
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Period extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = ',';
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue(explode($this->delimiter, $val));
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return implode($this->delimiter, $this->getParts());
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "PERIOD";
+
+    }
+
+    /**
+     * Sets the json value, as it would appear in a jCard or jCal object.
+     *
+     * The value must always be an array.
+     *
+     * @param array $value
+     * @return void
+     */
+    public function setJsonValue(array $value) {
+
+        $value = array_map(
+            function($item) {
+
+                return strtr(implode('/', $item), array(':' => '', '-' => ''));
+
+            },
+            $value
+        );
+        parent::setJsonValue($value);
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $return = array();
+        foreach($this->getParts() as $item) {
+
+            list($start, $end) = explode('/', $item, 2);
+
+            $start = DateTimeParser::parseDateTime($start);
+
+            // This is a duration value.
+            if ($end[0]==='P') {
+                $return[] = array(
+                    $start->format('Y-m-d\\TH:i:s'),
+                    $end
+                );
+            } else {
+                $end = DateTimeParser::parseDateTime($end);
+                $return[] = array(
+                    $start->format('Y-m-d\\TH:i:s'),
+                    $end->format('Y-m-d\\TH:i:s'),
+                );
+            }
+
+        }
+
+        return $return;
+
+    }
+
+}

+ 203 - 0
vendor/sabre/vobject/lib/Property/ICalendar/Recur.php

@@ -0,0 +1,203 @@
+<?php
+
+namespace Sabre\VObject\Property\ICalendar;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Parser\MimeDir;
+
+/**
+ * Recur property
+ *
+ * This object represents RECUR properties.
+ * These values are just used for RRULE and the now deprecated EXRULE.
+ *
+ * The RRULE property may look something like this:
+ *
+ * RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5.
+ *
+ * This property exposes this as a key=>value array that is accessible using
+ * getParts, and may be set using setParts.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Recur extends Property {
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * @param string|array $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        // If we're getting the data from json, we'll be receiving an object
+        if ($value instanceof \StdClass) {
+            $value = (array)$value;
+        }
+
+        if (is_array($value)) {
+            $newVal = array();
+            foreach($value as $k=>$v) {
+
+                if (is_string($v)) {
+                    $v = strtoupper($v);
+
+                    // The value had multiple sub-values
+                    if (strpos($v,',')!==false) {
+                        $v = explode(',', $v);
+                    }
+                } else {
+                    $v = array_map('strtoupper', $v);
+                }
+
+                $newVal[strtoupper($k)] = $v;
+            }
+            $this->value = $newVal;
+        } elseif (is_string($value)) {
+            $this->value = self::stringToArray($value);
+        } else {
+            throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
+        }
+
+    }
+
+    /**
+     * Returns the current value.
+     *
+     * This method will always return a singular value. If this was a
+     * multi-value object, some decision will be made first on how to represent
+     * it as a string.
+     *
+     * To get the correct multi-value version, use getParts.
+     *
+     * @return string
+     */
+    public function getValue() {
+
+        $out = array();
+        foreach($this->value as $key=>$value) {
+            $out[] = $key . '=' . (is_array($value)?implode(',', $value):$value);
+        }
+        return strtoupper(implode(';',$out));
+
+    }
+
+    /**
+     * Sets a multi-valued property.
+     *
+     * @param array $parts
+     * @return void
+     */
+    public function setParts(array $parts) {
+
+        $this->setValue($parts);
+
+    }
+
+    /**
+     * Returns a multi-valued property.
+     *
+     * This method always returns an array, if there was only a single value,
+     * it will still be wrapped in an array.
+     *
+     * @return array
+     */
+    public function getParts() {
+
+        return $this->value;
+
+    }
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return $this->getValue();
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "RECUR";
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $values = array();
+        foreach($this->getParts() as $k=>$v) {
+            $values[strtolower($k)] = $v;
+        }
+        return array($values);
+
+    }
+
+    /**
+     * Parses an RRULE value string, and turns it into a struct-ish array.
+     *
+     * @param string $value
+     * @return array
+     */
+    static function stringToArray($value) {
+
+        $value = strtoupper($value);
+        $newValue = array();
+        foreach(explode(';', $value) as $part) {
+
+            // Skipping empty parts.
+            if (empty($part)) {
+                continue;
+            }
+            list($partName, $partValue) = explode('=', $part);
+
+            // The value itself had multiple values..
+            if (strpos($partValue,',')!==false) {
+                $partValue=explode(',', $partValue);
+            }
+            $newValue[$partName] = $partValue;
+
+        }
+
+        return $newValue;
+
+    }
+
+}

+ 72 - 0
vendor/sabre/vobject/lib/Property/Integer.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    Sabre\VObject\Property;
+
+/**
+ * Integer property
+ *
+ * This object represents INTEGER values. These are always a single integer.
+ * They may be preceeded by either + or -.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Integer extends Property {
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue((int)$val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return $this->value;
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "INTEGER";
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        return array((int)$this->getValue());
+
+    }
+}

+ 333 - 0
vendor/sabre/vobject/lib/Property/Text.php

@@ -0,0 +1,333 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Component,
+    Sabre\VObject\Parser\MimeDir,
+    Sabre\VObject\Document;
+
+/**
+ * Text property
+ *
+ * This object represents TEXT values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Text extends Property {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string
+     */
+    public $delimiter = ',';
+
+    /**
+     * List of properties that are considered 'structured'.
+     *
+     * @var array
+     */
+    protected $structuredValues = array(
+        // vCard
+        'N',
+        'ADR',
+        'ORG',
+        'GENDER',
+
+        // iCalendar
+        'REQUEST-STATUS',
+    );
+
+    /**
+     * Some text components have a minimum number of components.
+     *
+     * N must for instance be represented as 5 components, separated by ;, even
+     * if the last few components are unused.
+     *
+     * @var array
+     */
+    protected $minimumPropertyValues = array(
+        'N' => 5,
+        'ADR' => 7,
+    );
+
+    /**
+     * Creates the property.
+     *
+     * You can specify the parameters either in key=>value syntax, in which case
+     * parameters will automatically be created, or you can just pass a list of
+     * Parameter objects.
+     *
+     * @param Component $root The root document
+     * @param string $name
+     * @param string|array|null $value
+     * @param array $parameters List of parameters
+     * @param string $group The vcard property group
+     * @return void
+     */
+    public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
+
+        // There's two types of multi-valued text properties:
+        // 1. multivalue properties.
+        // 2. structured value properties
+        //
+        // The former is always separated by a comma, the latter by semi-colon.
+        if (in_array($name, $this->structuredValues)) {
+            $this->delimiter = ';';
+        }
+
+        parent::__construct($root, $name, $value, $parameters, $group);
+
+    }
+
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
+
+    }
+
+    /**
+     * Sets the value as a quoted-printable encoded string.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setQuotedPrintableValue($val) {
+
+        $val = quoted_printable_decode($val);
+
+        // Quoted printable only appears in vCard 2.1, and the only character
+        // that may be escaped there is ;. So we are simply splitting on just
+        // that.
+        //
+        // We also don't have to unescape \\, so all we need to look for is a ;
+        // that's not preceeded with a \.
+        $regex = '# (?<!\\\\) ; #x';
+        $matches = preg_split($regex, $val);
+        $this->setValue($matches);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        $val = $this->getParts();
+
+        if (isset($this->minimumPropertyValues[$this->name])) {
+            $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
+        }
+
+        foreach($val as &$item) {
+
+            if (!is_array($item)) {
+                $item = array($item);
+            }
+
+            foreach($item as &$subItem) {
+                $subItem = strtr(
+                    $subItem,
+                    array(
+                        '\\' => '\\\\',
+                        ';'  => '\;',
+                        ','  => '\,',
+                        "\n" => '\n',
+                        "\r" => "",
+                    )
+                );
+            }
+            $item = implode(',', $item);
+
+        }
+
+        return implode($this->delimiter, $val);
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        // Structured text values should always be returned as a single
+        // array-item. Multi-value text should be returned as multiple items in
+        // the top-array.
+        if (in_array($this->name, $this->structuredValues)) {
+            return array($this->getParts());
+        } else {
+            return $this->getParts();
+        }
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "TEXT";
+
+    }
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        // We need to kick in a special type of encoding, if it's a 2.1 vcard.
+        if ($this->root->getDocumentType() !== Document::VCARD21) {
+            return parent::serialize();
+        }
+
+        $val = $this->getParts();
+
+        if (isset($this->minimumPropertyValues[$this->name])) {
+            $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
+        }
+
+        // Imploding multiple parts into a single value, and splitting the
+        // values with ;.
+        if (count($val)>1) {
+            foreach($val as $k=>$v) {
+                $val[$k] = str_replace(';','\;', $v);
+            }
+            $val = implode(';', $val);
+        } else {
+            $val = $val[0];
+        }
+
+        $str = $this->name;
+        if ($this->group) $str = $this->group . '.' . $this->name;
+        foreach($this->parameters as $param) {
+
+            if ($param->getValue() === 'QUOTED-PRINTABLE') {
+                continue;
+            }
+            $str.=';' . $param->serialize();
+
+        }
+
+
+
+        // If the resulting value contains a \n, we must encode it as
+        // quoted-printable.
+        if (strpos($val,"\n") !== false) {
+
+            $str.=';ENCODING=QUOTED-PRINTABLE:';
+            $lastLine=$str;
+            $out = null;
+
+            // The PHP built-in quoted-printable-encode does not correctly
+            // encode newlines for us. Specifically, the \r\n sequence must in
+            // vcards be encoded as =0D=OA and we must insert soft-newlines
+            // every 75 bytes.
+            for($ii=0;$ii<strlen($val);$ii++) {
+                $ord = ord($val[$ii]);
+                // These characters are encoded as themselves.
+                if ($ord >= 32 && $ord <=126) {
+                    $lastLine.=$val[$ii];
+                } else {
+                    $lastLine.='=' . strtoupper(bin2hex($val[$ii]));
+                }
+                if (strlen($lastLine)>=75) {
+                    // Soft line break
+                    $out.=$lastLine. "=\r\n ";
+                    $lastLine = null;
+                }
+
+            }
+            if (!is_null($lastLine)) $out.= $lastLine . "\r\n";
+            return $out;
+
+        } else {
+            $str.=':' . $val;
+            $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;
+
+
+        }
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   - Node::REPAIR - If something is broken, and automatic repair may
+     *                    be attempted.
+     *
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $warnings = parent::validate($options);
+
+        if (isset($this->minimumPropertyValues[$this->name])) {
+
+            $minimum = $this->minimumPropertyValues[$this->name];
+            $parts = $this->getParts();
+            if (count($parts) < $minimum) {
+                $warnings[] = array(
+                    'level' => 1,
+                    'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts),
+                    'node' => $this,
+                );
+                if ($options & self::REPAIR) {
+                    $parts = array_pad($parts, $minimum, '');
+                    $this->setParts($parts);
+                }
+            }
+
+        }
+        return $warnings;
+
+    }
+}

+ 94 - 0
vendor/sabre/vobject/lib/Property/Time.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use Sabre\VObject\DateTimeParser;
+
+/**
+ * Time property
+ *
+ * This object encodes TIME values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Time extends Text {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "TIME";
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $parts = DateTimeParser::parseVCardTime($this->getValue());
+
+        $timeStr = '';
+
+        // Hour
+        if (!is_null($parts['hour'])) {
+            $timeStr.=$parts['hour'];
+
+            if (!is_null($parts['minute'])) {
+                $timeStr.=':';
+            }
+        } else {
+            // We know either minute or second _must_ be set, so we insert a
+            // dash for an empty value.
+            $timeStr.='-';
+        }
+
+        // Minute
+        if (!is_null($parts['minute'])) {
+            $timeStr.=$parts['minute'];
+
+            if (!is_null($parts['second'])) {
+                $timeStr.=':';
+            }
+        } else {
+            if (isset($parts['second'])) {
+                // Dash for empty minute
+                $timeStr.='-';
+            }
+        }
+
+        // Second
+        if (!is_null($parts['second'])) {
+            $timeStr.=$parts['second'];
+        }
+
+        // Timezone
+        if (!is_null($parts['timezone'])) {
+            $timeStr.=$parts['timezone'];
+        }
+
+        return array($timeStr);
+
+    }
+
+}

+ 50 - 0
vendor/sabre/vobject/lib/Property/Unknown.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use
+    Sabre\VObject\Property,
+    Sabre\VObject\Component,
+    Sabre\VObject\Parser\MimeDir,
+    Sabre\VObject\Document;
+
+/**
+ * Unknown property
+ *
+ * This object represents any properties not recognized by the parser.
+ * This type of value has been introduced by the jCal, jCard specs.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Unknown extends Text {
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        return array($this->getRawMimeDirValue());
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "UNKNOWN";
+
+    }
+
+}

+ 95 - 0
vendor/sabre/vobject/lib/Property/Uri.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use Sabre\VObject\Property;
+
+/**
+ * URI property
+ *
+ * This object encodes URI values. vCard 2.1 calls these URL.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Uri extends Text {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "URI";
+
+    }
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        // Normally we don't need to do any type of unescaping for these
+        // properties, however.. we've noticed that Google Contacts
+        // specifically escapes the colon (:) with a blackslash. While I have
+        // no clue why they thought that was a good idea, I'm unescaping it
+        // anyway.
+        //
+        // Good thing backslashes are not allowed in urls. Makes it easy to
+        // assume that a backslash is always intended as an escape character.
+        if ($this->name === 'URL') {
+            $regex = '#  (?: (\\\\ (?: \\\\ | : ) ) ) #x';
+            $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+            $newVal = '';
+            foreach($matches as $match) {
+                switch($match) {
+                    case '\:' :
+                        $newVal.=':';
+                        break;
+                    default :
+                        $newVal.=$match;
+                        break;
+                }
+            }
+            $this->value = $newVal;
+        } else {
+            $this->value = $val;
+        }
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        if (is_array($this->value)) {
+            return $this->value[0];
+        } else {
+            return $this->value;
+        }
+
+    }
+
+}

+ 37 - 0
vendor/sabre/vobject/lib/Property/UtcOffset.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+/**
+ * UtcOffset property
+ *
+ * This object encodes UTC-OFFSET values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class UtcOffset extends Text {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "UTC-OFFSET";
+
+    }
+}

+ 33 - 0
vendor/sabre/vobject/lib/Property/VCard/Date.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Sabre\VObject\Property\VCard;
+
+use
+    Sabre\VObject\DateTimeParser;
+
+/**
+ * Date property
+ *
+ * This object encodes vCard DATE values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Date extends DateAndOrTime {
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "DATE";
+
+    }
+
+}

+ 317 - 0
vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php

@@ -0,0 +1,317 @@
+<?php
+
+namespace Sabre\VObject\Property\VCard;
+
+use
+    Sabre\VObject\DateTimeParser,
+    Sabre\VObject\Property\Text,
+    Sabre\VObject\Property,
+    DateTime;
+
+/**
+ * DateAndOrTime property
+ *
+ * This object encodes DATE-AND-OR-TIME values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class DateAndOrTime extends Property {
+
+    /**
+     * Field separator
+     *
+     * @var null|string
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "DATE-AND-OR-TIME";
+
+    }
+
+    /**
+     * Sets a multi-valued property.
+     *
+     * You may also specify DateTime objects here.
+     *
+     * @param array $parts
+     * @return void
+     */
+    public function setParts(array $parts) {
+
+        if (count($parts)>1) {
+            throw new \InvalidArgumentException('Only one value allowed');
+        }
+        if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
+            $this->setDateTime($parts[0]);
+        } else {
+            parent::setParts($parts);
+        }
+
+    }
+
+    /**
+     * Updates the current value.
+     *
+     * This may be either a single, or multiple strings in an array.
+     *
+     * Instead of strings, you may also use DateTime here.
+     *
+     * @param string|array|\DateTime $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        if ($value instanceof \DateTime) {
+            $this->setDateTime($value);
+        } else {
+            parent::setValue($value);
+        }
+
+    }
+
+    /**
+     * Sets the property as a DateTime object.
+     *
+     * @param \DateTime $dt
+     * @return void
+     */
+    public function setDateTime(\DateTime $dt) {
+
+        $values = array();
+
+        $tz = null;
+        $isUtc = false;
+
+        $tz = $dt->getTimeZone();
+        $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+
+        if ($isUtc) {
+            $value = $dt->format('Ymd\\THis\\Z');
+        } else {
+            // Calculating the offset.
+            $value = $dt->format('Ymd\\THisO');
+        }
+
+        $this->value = $value;
+
+    }
+
+    /**
+     * Returns a date-time value.
+     *
+     * Note that if this property contained more than 1 date-time, only the
+     * first will be returned. To get an array with multiple values, call
+     * getDateTimes.
+     *
+     * If no time was specified, we will always use midnight (in the default
+     * timezone) as the time.
+     *
+     * If parts of the date were omitted, such as the year, we will grab the
+     * current values for those. So at the time of writing, if the year was
+     * omitted, we would have filled in 2014.
+     *
+     * @return \DateTime
+     */
+    public function getDateTime() {
+
+        $dts = array();
+        $now = new DateTime();
+
+        $tzFormat = $now->getTimezone()->getOffset($now)===0?'\\Z':'O';
+        $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat));
+
+        $value = $this->getValue();
+
+        $dateParts = DateTimeParser::parseVCardDateTime($this->getValue());
+
+        // This sets all the missing parts to the current date/time.
+        // So if the year was missing for a birthday, we're making it 'this
+        // year'.
+        foreach($dateParts as $k=>$v) {
+            if (is_null($v)) {
+                $dateParts[$k] = $nowParts[$k];
+            }
+        }
+        return new DateTime("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $parts = DateTimeParser::parseVCardDateTime($this->getValue());
+
+        $dateStr = '';
+
+        // Year
+        if (!is_null($parts['year'])) {
+            $dateStr.=$parts['year'];
+
+            if (!is_null($parts['month'])) {
+                // If a year and a month is set, we need to insert a separator
+                // dash.
+                $dateStr.='-';
+            }
+
+        } else {
+
+            if (!is_null($parts['month']) || !is_null($parts['date'])) {
+                // Inserting two dashes
+                $dateStr.='--';
+            }
+
+        }
+
+        // Month
+
+        if (!is_null($parts['month'])) {
+            $dateStr.=$parts['month'];
+
+            if (isset($parts['date'])) {
+                // If month and date are set, we need the separator dash.
+                $dateStr.='-';
+            }
+        } else {
+            if (isset($parts['date'])) {
+                // If the month is empty, and a date is set, we need a 'empty
+                // dash'
+                $dateStr.='-';
+            }
+        }
+
+        // Date
+        if (!is_null($parts['date'])) {
+            $dateStr.=$parts['date'];
+        }
+
+
+        // Early exit if we don't have a time string.
+        if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
+            return array($dateStr);
+        }
+
+        $dateStr.='T';
+
+        // Hour
+        if (!is_null($parts['hour'])) {
+            $dateStr.=$parts['hour'];
+
+            if (!is_null($parts['minute'])) {
+                $dateStr.=':';
+            }
+        } else {
+            // We know either minute or second _must_ be set, so we insert a
+            // dash for an empty value.
+            $dateStr.='-';
+        }
+
+        // Minute
+        if (!is_null($parts['minute'])) {
+            $dateStr.=$parts['minute'];
+
+            if (!is_null($parts['second'])) {
+                $dateStr.=':';
+            }
+        } else {
+            if (isset($parts['second'])) {
+                // Dash for empty minute
+                $dateStr.='-';
+            }
+        }
+
+        // Second
+        if (!is_null($parts['second'])) {
+            $dateStr.=$parts['second'];
+        }
+
+        // Timezone
+        if (!is_null($parts['timezone'])) {
+            $dateStr.=$parts['timezone'];
+        }
+
+        return array($dateStr);
+
+    }
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return implode($this->delimiter, $this->getParts());
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   Node::REPAIR - May attempt to automatically repair the problem.
+     *
+     * This method returns an array with detected problems.
+     * Every element has the following properties:
+     *
+     *  * level - problem level.
+     *  * message - A human-readable string describing the issue.
+     *  * node - A reference to the problematic node.
+     *
+     * The level means:
+     *   1 - The issue was repaired (only happens if REPAIR was turned on)
+     *   2 - An inconsequential issue
+     *   3 - A severe issue.
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $messages = parent::validate($options);
+        $value = $this->getValue();
+        try {
+            DateTimeParser::parseVCardDateTime($value);
+        } catch (\InvalidArgumentException $e) {
+            $messages[] = array(
+                'level' => 3,
+                'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property',
+                'node' => $this,
+            );
+        }
+        return $messages;
+
+    }
+}

+ 33 - 0
vendor/sabre/vobject/lib/Property/VCard/DateTime.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Sabre\VObject\Property\VCard;
+
+use
+    Sabre\VObject\DateTimeParser;
+
+/**
+ * DateTime property
+ *
+ * This object encodes DATE-TIME values for vCards.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class DateTime extends DateAndOrTime {
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "DATE-TIME";
+
+    }
+
+}

+ 59 - 0
vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace Sabre\VObject\Property\VCard;
+
+use
+    Sabre\VObject\Property;
+
+/**
+ * LanguageTag property
+ *
+ * This object represents LANGUAGE-TAG values as used in vCards.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class LanguageTag extends Property {
+
+    /**
+     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+     *
+     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+     * not yet done, but parameters are not included.
+     *
+     * @param string $val
+     * @return void
+     */
+    public function setRawMimeDirValue($val) {
+
+        $this->setValue($val);
+
+    }
+
+    /**
+     * Returns a raw mime-dir representation of the value.
+     *
+     * @return string
+     */
+    public function getRawMimeDirValue() {
+
+        return $this->value;
+
+    }
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "LANGUAGE-TAG";
+
+    }
+
+}

+ 69 - 0
vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Sabre\VObject\Property\VCard;
+
+use
+    Sabre\VObject\DateTimeParser,
+    Sabre\VObject\Property\Text;
+
+/**
+ * TimeStamp property
+ *
+ * This object encodes TIMESTAMP values.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class TimeStamp extends Text {
+
+    /**
+     * In case this is a multi-value property. This string will be used as a
+     * delimiter.
+     *
+     * @var string|null
+     */
+    public $delimiter = null;
+
+    /**
+     * Returns the type of value.
+     *
+     * This corresponds to the VALUE= parameter. Every property also has a
+     * 'default' valueType.
+     *
+     * @return string
+     */
+    public function getValueType() {
+
+        return "TIMESTAMP";
+
+    }
+
+    /**
+     * Returns the value, in the format it should be encoded for json.
+     *
+     * This method must always return an array.
+     *
+     * @return array
+     */
+    public function getJsonValue() {
+
+        $parts = DateTimeParser::parseVCardDateTime($this->getValue());
+
+        $dateStr =
+            $parts['year'] . '-' .
+            $parts['month'] . '-' .
+            $parts['date'] . 'T' .
+            $parts['hour'] . ':' .
+            $parts['minute'] . ':' .
+            $parts['second'];
+
+        // Timezone
+        if (!is_null($parts['timezone'])) {
+            $dateStr.=$parts['timezone'];
+        }
+
+        return array($dateStr);
+
+    }
+}

+ 73 - 0
vendor/sabre/vobject/lib/Reader.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * iCalendar/vCard/jCal/jCard reader object.
+ *
+ * This object provides a few (static) convenience methods to quickly access
+ * the parsers.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Reader {
+
+    /**
+     * If this option is passed to the reader, it will be less strict about the
+     * validity of the lines.
+     */
+    const OPTION_FORGIVING = 1;
+
+    /**
+     * If this option is turned on, any lines we cannot parse will be ignored
+     * by the reader.
+     */
+    const OPTION_IGNORE_INVALID_LINES = 2;
+
+    /**
+     * Parses a vCard or iCalendar object, and returns the top component.
+     *
+     * The options argument is a bitfield. Pass any of the OPTIONS constant to
+     * alter the parsers' behaviour.
+     *
+     * You can either supply a string, or a readable stream for input.
+     *
+     * @param string|resource $data
+     * @param int $options
+     * @return Document
+     */
+    static public function read($data, $options = 0) {
+
+        $parser = new Parser\MimeDir();
+        $result = $parser->parse($data, $options);
+
+        return $result;
+
+    }
+
+    /**
+     * Parses a jCard or jCal object, and returns the top component.
+     *
+     * The options argument is a bitfield. Pass any of the OPTIONS constant to
+     * alter the parsers' behaviour.
+     *
+     * You can either a string, a readable stream, or an array for it's input.
+     * Specifying the array is useful if json_decode was already called on the
+     * input.
+     *
+     * @param string|resource|array $data
+     * @param int $options
+     * @return Node
+     */
+    static public function readJson($data, $options = 0) {
+
+        $parser = new Parser\Json();
+        $result = $parser->parse($data, $options);
+
+        return $result;
+
+    }
+
+}

+ 477 - 0
vendor/sabre/vobject/lib/Recur/EventIterator.php

@@ -0,0 +1,477 @@
+<?php
+
+namespace Sabre\VObject\Recur;
+
+use InvalidArgumentException;
+use DateTime;
+use Sabre\VObject\Component;
+use Sabre\VObject\Component\VEvent;
+
+/**
+ * This class is used to determine new for a recurring event, when the next
+ * events occur.
+ *
+ * This iterator may loop infinitely in the future, therefore it is important
+ * that if you use this class, you set hard limits for the amount of iterations
+ * you want to handle.
+ *
+ * Note that currently there is not full support for the entire iCalendar
+ * specification, as it's very complex and contains a lot of permutations
+ * that's not yet used very often in software.
+ *
+ * For the focus has been on features as they actually appear in Calendaring
+ * software, but this may well get expanded as needed / on demand
+ *
+ * The following RRULE properties are supported
+ *   * UNTIL
+ *   * INTERVAL
+ *   * COUNT
+ *   * FREQ=DAILY
+ *     * BYDAY
+ *     * BYHOUR
+ *     * BYMONTH
+ *   * FREQ=WEEKLY
+ *     * BYDAY
+ *     * BYHOUR
+ *     * WKST
+ *   * FREQ=MONTHLY
+ *     * BYMONTHDAY
+ *     * BYDAY
+ *     * BYSETPOS
+ *   * FREQ=YEARLY
+ *     * BYMONTH
+ *     * BYMONTHDAY (only if BYMONTH is also set)
+ *     * BYDAY (only if BYMONTH is also set)
+ *
+ * Anything beyond this is 'undefined', which means that it may get ignored, or
+ * you may get unexpected results. The effect is that in some applications the
+ * specified recurrence may look incorrect, or is missing.
+ *
+ * The recurrence iterator also does not yet support THISANDFUTURE.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class EventIterator implements \Iterator {
+
+    /**
+     * Creates the iterator
+     *
+     * You should pass a VCALENDAR component, as well as the UID of the event
+     * we're going to traverse.
+     *
+     * @param Component $vcal
+     * @param string|null $uid
+     */
+    public function __construct(Component $vcal, $uid = null) {
+
+        $rrule = null;
+        if ($vcal instanceof VEvent) {
+            // Single instance mode.
+            $events = array($vcal);
+        } else {
+            $uid = (string)$uid;
+            if (!$uid) {
+                throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
+            }
+            if (!isset($vcal->VEVENT)) {
+                throw new InvalidArgumentException('No events found in this calendar');
+            }
+            $events = array();
+            foreach($vcal->VEVENT as $event) {
+                if ($event->uid->getValue() === $uid) {
+                    $events[] = $event;
+                }
+            }
+
+        }
+
+        foreach($events as $vevent) {
+
+            if (!isset($vevent->{'RECURRENCE-ID'})) {
+
+                $this->masterEvent = $vevent;
+
+            } else {
+
+                $this->exceptions[$vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeStamp()] = true;
+                $this->overriddenEvents[] = $vevent;
+
+            }
+
+        }
+
+        if (!$this->masterEvent) {
+            // No base event was found. CalDAV does allow cases where only
+            // overridden instances are stored.
+            //
+            // In this particular case, we're just going to grab the first
+            // event and use that instead. This may not always give the
+            // desired result.
+            if (!count($this->overriddenEvents)) {
+                throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
+            }
+            $this->masterEvent = array_shift($this->overriddenEvents);
+        }
+
+        // master event.
+        if (isset($this->masterEvent->RRULE)) {
+            $rrule = $this->masterEvent->RRULE->getParts();
+        } else {
+            // master event has no rrule. We default to something that
+            // iterates once.
+            $rrule = array(
+                'FREQ' => 'DAILY',
+                'COUNT' => 1,
+            );
+        }
+        $this->startDate = $this->masterEvent->DTSTART->getDateTime();
+
+        if (isset($this->masterEvent->EXDATE)) {
+
+            foreach($this->masterEvent->EXDATE as $exDate) {
+
+                foreach($exDate->getDateTimes() as $dt) {
+                    $this->exceptions[$dt->getTimeStamp()] = true;
+                }
+
+            }
+
+        }
+
+        if (isset($this->masterEvent->DTEND)) {
+            $this->eventDuration =
+                $this->masterEvent->DTEND->getDateTime()->getTimeStamp() -
+                $this->startDate->getTimeStamp();
+        } elseif (isset($this->masterEvent->DURATION)) {
+            $duration = $this->masterEvent->DURATION->getDateInterval();
+            $end = clone $this->startDate;
+            $end->add($duration);
+            $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
+        } elseif ($this->masterEvent->DTSTART->getValueType() === 'DATE') {
+            $this->eventDuration = 3600 * 24;
+        } else {
+            $this->eventDuration = 0;
+        }
+
+        if (isset($this->masterEvent->RDATE)) {
+            $this->recurIterator = new RDateIterator(
+                $this->masterEvent->RDATE->getParts(),
+                $this->startDate
+            );
+        } elseif (isset($this->masterEvent->RRULE)) {
+            $this->recurIterator = new RRuleIterator(
+                $this->masterEvent->RRULE->getParts(),
+                $this->startDate
+            );
+        } else {
+            $this->recurIterator = new RRuleIterator(
+                array(
+                    'FREQ' => 'DAILY',
+                    'COUNT' => 1,
+                ),
+                $this->startDate
+            );
+        }
+
+        $this->rewind();
+        if (!$this->valid()) {
+            throw new NoInstancesException('This recurrence rule does not generate any valid instances');
+        }
+
+    }
+
+    /**
+     * Returns the date for the current position of the iterator.
+     *
+     * @return DateTime
+     */
+    public function current() {
+
+        if ($this->currentDate) {
+            return clone $this->currentDate;
+        }
+
+    }
+
+    /**
+     * This method returns the start date for the current iteration of the
+     * event.
+     *
+     * @return DateTime
+     */
+    public function getDtStart() {
+
+        if ($this->currentDate) {
+            return clone $this->currentDate;
+        }
+
+    }
+
+    /**
+     * This method returns the end date for the current iteration of the
+     * event.
+     *
+     * @return DateTime
+     */
+    public function getDtEnd() {
+
+        if (!$this->valid()) {
+            return null;
+        }
+        $end = clone $this->currentDate;
+        $end->modify('+' . $this->eventDuration . ' seconds');
+        return $end;
+
+    }
+
+    /**
+     * Returns a VEVENT for the current iterations of the event.
+     *
+     * This VEVENT will have a recurrence id, and it's DTSTART and DTEND
+     * altered.
+     *
+     * @return VEvent
+     */
+    public function getEventObject() {
+
+        if ($this->currentOverriddenEvent) {
+            return $this->currentOverriddenEvent;
+        }
+
+        $event = clone $this->masterEvent;
+
+        // Ignoring the following block, because PHPUnit's code coverage
+        // ignores most of these lines, and this messes with our stats.
+        //
+        // @codeCoverageIgnoreStart
+        unset(
+            $event->RRULE,
+            $event->EXDATE,
+            $event->RDATE,
+            $event->EXRULE,
+            $event->{'RECURRENCE-ID'}
+        );
+        // @codeCoverageIgnoreEnd
+
+        $event->DTSTART->setDateTime($this->getDtStart());
+        if (isset($event->DTEND)) {
+            $event->DTEND->setDateTime($this->getDtEnd());
+        }
+        if ($this->recurIterator->key() > 0) {
+            $event->add('RECURRENCE-ID', $event->DTSTART->getDateTime());
+        }
+        return $event;
+
+    }
+
+    /**
+     * Returns the current position of the iterator.
+     *
+     * This is for us simply a 0-based index.
+     *
+     * @return int
+     */
+    public function key() {
+
+        // The counter is always 1 ahead.
+        return $this->counter - 1;
+
+    }
+
+    /**
+     * This is called after next, to see if the iterator is still at a valid
+     * position, or if it's at the end.
+     *
+     * @return bool
+     */
+    public function valid() {
+
+        return !!$this->currentDate;
+
+    }
+
+    /**
+     * Sets the iterator back to the starting point.
+     */
+    public function rewind() {
+
+        $this->recurIterator->rewind();
+        // re-creating overridden event index.
+        $index = array();
+        foreach($this->overriddenEvents as $key=>$event) {
+            $stamp = $event->DTSTART->getDateTime()->getTimeStamp();
+            $index[$stamp] = $key;
+        }
+        krsort($index);
+        $this->counter = 0;
+        $this->overriddenEventsIndex = $index;
+        $this->currentOverriddenEvent = null;
+
+        $this->nextDate = null;
+        $this->currentDate = clone $this->startDate;
+
+        $this->next();
+
+    }
+
+    /**
+     * Advances the iterator with one step.
+     *
+     * @return void
+     */
+    public function next() {
+
+        $this->currentOverriddenEvent = null;
+        $this->counter++;
+        if ($this->nextDate) {
+            // We had a stored value.
+            $nextDate = $this->nextDate;
+            $this->nextDate = null;
+        } else {
+            // We need to ask rruleparser for the next date.
+            // We need to do this until we find a date that's not in the
+            // exception list.
+            do {
+                if (!$this->recurIterator->valid()) {
+                    $nextDate = null;
+                    break;
+                }
+                $nextDate = $this->recurIterator->current();
+                $this->recurIterator->next();
+            } while(isset($this->exceptions[$nextDate->getTimeStamp()]));
+
+        }
+
+
+        // $nextDate now contains what rrule thinks is the next one, but an
+        // overridden event may cut ahead.
+        if ($this->overriddenEventsIndex) {
+
+            $offset = end($this->overriddenEventsIndex);
+            $timestamp = key($this->overriddenEventsIndex);
+            if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
+                // Overridden event comes first.
+                $this->currentOverriddenEvent = $this->overriddenEvents[$offset];
+
+                // Putting the rrule next date aside.
+                $this->nextDate = $nextDate;
+                $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime();
+
+                // Ensuring that this item will only be used once.
+                array_pop($this->overriddenEventsIndex);
+
+                // Exit point!
+                return;
+
+            }
+
+        }
+
+        $this->currentDate = $nextDate;
+
+    }
+
+    /**
+     * Quickly jump to a date in the future.
+     *
+     * @param DateTime $dateTime
+     */
+    public function fastForward(DateTime $dateTime) {
+
+        while($this->valid() && $this->getDtEnd() < $dateTime ) {
+            $this->next();
+        }
+
+    }
+
+    /**
+     * Returns true if this recurring event never ends.
+     *
+     * @return bool
+     */
+    public function isInfinite() {
+
+        return $this->recurIterator->isInfinite();
+
+    }
+
+    /**
+     * RRULE parser
+     *
+     * @var RRuleIterator
+     */
+    protected $recurIterator;
+
+    /**
+     * The duration, in seconds, of the master event.
+     *
+     * We use this to calculate the DTEND for subsequent events.
+     */
+    protected $eventDuration;
+
+    /**
+     * A reference to the main (master) event.
+     *
+     * @var VEVENT
+     */
+    protected $masterEvent;
+
+    /**
+     * List of overridden events.
+     *
+     * @var array
+     */
+    protected $overriddenEvents = array();
+
+    /**
+     * Overridden event index.
+     *
+     * Key is timestamp, value is the index of the item in the $overriddenEvent
+     * property.
+     *
+     * @var array
+     */
+    protected $overriddenEventsIndex;
+
+    /**
+     * A list of recurrence-id's that are either part of EXDATE, or are
+     * overridden.
+     *
+     * @var array
+     */
+    protected $exceptions = array();
+
+    /**
+     * Internal event counter
+     *
+     * @var int
+     */
+    protected $counter;
+
+    /**
+     * The very start of the iteration process.
+     *
+     * @var DateTime
+     */
+    protected $startDate;
+
+    /**
+     * Where we are currently in the iteration process
+     *
+     * @var DateTime
+     */
+    protected $currentDate;
+
+    /**
+     * The next date from the rrule parser.
+     *
+     * Sometimes we need to temporary store the next date, because an
+     * overridden event came before.
+     *
+     * @var DateTime
+     */
+    protected $nextDate;
+
+}

+ 18 - 0
vendor/sabre/vobject/lib/Recur/NoInstancesException.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\VObject\Recur;
+
+use Exception;
+
+/**
+ * This exception gets thrown when a recurrence iterator produces 0 instances.
+ *
+ * This may happen when every occurence in a rrule is also in EXDATE.
+ *
+ * @copyright Copyright (C) 2009-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class NoInstancesException extends Exception {
+
+}

+ 174 - 0
vendor/sabre/vobject/lib/Recur/RDateIterator.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace Sabre\VObject\Recur;
+
+use DateTime;
+use InvalidArgumentException;
+use Iterator;
+use Sabre\VObject\DateTimeParser;
+
+
+/**
+ * RRuleParser
+ *
+ * This class receives an RRULE string, and allows you to iterate to get a list
+ * of dates in that recurrence.
+ *
+ * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
+ * 5 items, one for each day.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class RDateIterator implements Iterator {
+
+    /**
+     * Creates the Iterator.
+     *
+     * @param string|array $rrule
+     * @param DateTime $start
+     */
+    public function __construct($rrule, DateTime $start) {
+
+        $this->startDate = $start;
+        $this->parseRDate($rrule);
+        $this->currentDate = clone $this->startDate;
+
+    }
+
+    /* Implementation of the Iterator interface {{{ */
+
+    public function current() {
+
+        if (!$this->valid()) return null;
+        return clone $this->currentDate;
+
+    }
+
+    /**
+     * Returns the current item number.
+     *
+     * @return int
+     */
+    public function key() {
+
+        return $this->counter;
+
+    }
+
+    /**
+     * Returns whether the current item is a valid item for the recurrence
+     * iterator.
+     *
+     * @return bool
+     */
+    public function valid() {
+
+        return ($this->counter <= count($this->dates));
+
+    }
+
+    /**
+     * Resets the iterator.
+     *
+     * @return void
+     */
+    public function rewind() {
+
+        $this->currentDate = clone $this->startDate;
+        $this->counter = 0;
+
+    }
+
+    /**
+     * Goes on to the next iteration.
+     *
+     * @return void
+     */
+    public function next() {
+
+        $this->counter++;
+        if (!$this->valid()) return;
+
+        $this->currentDate =
+            DateTimeParser::parse(
+                $this->dates[$this->counter-1]
+            );
+
+    }
+
+    /* End of Iterator implementation }}} */
+
+    /**
+     * Returns true if this recurring event never ends.
+     *
+     * @return bool
+     */
+    public function isInfinite() {
+
+        return false;
+
+    }
+
+    /**
+     * This method allows you to quickly go to the next occurrence after the
+     * specified date.
+     *
+     * @param DateTime $dt
+     * @return void
+     */
+    public function fastForward(\DateTime $dt) {
+
+        while($this->valid() && $this->currentDate < $dt ) {
+            $this->next();
+        }
+
+    }
+
+    /**
+     * The reference start date/time for the rrule.
+     *
+     * All calculations are based on this initial date.
+     *
+     * @var DateTime
+     */
+    protected $startDate;
+
+    /**
+     * The date of the current iteration. You can get this by calling
+     * ->current().
+     *
+     * @var DateTime
+     */
+    protected $currentDate;
+
+    /**
+     * The current item in the list.
+     *
+     * You can get this number with the key() method.
+     *
+     * @var int
+     */
+    protected $counter = 0;
+
+    /* }}} */
+
+    /**
+     * This method receives a string from an RRULE property, and populates this
+     * class with all the values.
+     *
+     * @param string|array $rrule
+     * @return void
+     */
+    protected function parseRDate($rdate) {
+
+        if (is_string($rdate)) {
+            $rdate = explode(',', $rdate);
+        }
+
+        $this->dates = $rdate;
+
+    }
+
+}

+ 897 - 0
vendor/sabre/vobject/lib/Recur/RRuleIterator.php

@@ -0,0 +1,897 @@
+<?php
+
+namespace Sabre\VObject\Recur;
+
+use DateTime;
+use InvalidArgumentException;
+use Iterator;
+use Sabre\VObject\DateTimeParser;
+use Sabre\VObject\Property;
+
+
+/**
+ * RRuleParser
+ *
+ * This class receives an RRULE string, and allows you to iterate to get a list
+ * of dates in that recurrence.
+ *
+ * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
+ * 5 items, one for each day.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class RRuleIterator implements Iterator {
+
+    /**
+     * Creates the Iterator
+     *
+     * @param string|array $rrule
+     * @param DateTime $start
+     */
+    public function __construct($rrule, DateTime $start) {
+
+        $this->startDate = $start;
+        $this->parseRRule($rrule);
+        $this->currentDate = clone $this->startDate;
+
+    }
+
+    /* Implementation of the Iterator interface {{{ */
+
+    public function current() {
+
+        if (!$this->valid()) return null;
+        return clone $this->currentDate;
+
+    }
+
+    /**
+     * Returns the current item number
+     *
+     * @return int
+     */
+    public function key() {
+
+        return $this->counter;
+
+    }
+
+    /**
+     * Returns whether the current item is a valid item for the recurrence
+     * iterator. This will return false if we've gone beyond the UNTIL or COUNT
+     * statements.
+     *
+     * @return bool
+     */
+    public function valid() {
+
+        if (!is_null($this->count)) {
+            return $this->counter < $this->count;
+        }
+        return is_null($this->until) || $this->currentDate <= $this->until;
+
+    }
+
+    /**
+     * Resets the iterator
+     *
+     * @return void
+     */
+    public function rewind() {
+
+        $this->currentDate = clone $this->startDate;
+        $this->counter = 0;
+
+    }
+
+    /**
+     * Goes on to the next iteration
+     *
+     * @return void
+     */
+    public function next() {
+
+        $previousStamp = $this->currentDate->getTimeStamp();
+
+        // Otherwise, we find the next event in the normal RRULE
+        // sequence.
+        switch($this->frequency) {
+
+            case 'hourly' :
+                $this->nextHourly();
+                break;
+
+            case 'daily' :
+                $this->nextDaily();
+                break;
+
+            case 'weekly' :
+                $this->nextWeekly();
+                break;
+
+            case 'monthly' :
+                $this->nextMonthly();
+                break;
+
+            case 'yearly' :
+                $this->nextYearly();
+                break;
+
+        }
+        $this->counter++;
+
+    }
+
+    /* End of Iterator implementation }}} */
+
+    /**
+     * Returns true if this recurring event never ends.
+     *
+     * @return bool
+     */
+    public function isInfinite() {
+
+        return !$this->count && !$this->until;
+
+    }
+
+    /**
+     * This method allows you to quickly go to the next occurrence after the
+     * specified date.
+     *
+     * @param DateTime $dt
+     * @return void
+     */
+    public function fastForward(\DateTime $dt) {
+
+        while($this->valid() && $this->currentDate < $dt ) {
+            $this->next();
+        }
+
+    }
+
+    /**
+     * The reference start date/time for the rrule.
+     *
+     * All calculations are based on this initial date.
+     *
+     * @var DateTime
+     */
+    protected $startDate;
+
+    /**
+     * The date of the current iteration. You can get this by calling
+     * ->current().
+     *
+     * @var DateTime
+     */
+    protected $currentDate;
+
+    /**
+     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
+     * yearly.
+     *
+     * @var string
+     */
+    protected $frequency;
+
+    /**
+     * The number of recurrences, or 'null' if infinitely recurring.
+     *
+     * @var int
+     */
+    protected $count;
+
+    /**
+     * The interval.
+     *
+     * If for example frequency is set to daily, interval = 2 would mean every
+     * 2 days.
+     *
+     * @var int
+     */
+    protected $interval = 1;
+
+    /**
+     * The last instance of this recurrence, inclusively
+     *
+     * @var \DateTime|null
+     */
+    protected $until;
+
+    /**
+     * Which seconds to recur.
+     *
+     * This is an array of integers (between 0 and 60)
+     *
+     * @var array
+     */
+    protected $bySecond;
+
+    /**
+     * Which minutes to recur
+     *
+     * This is an array of integers (between 0 and 59)
+     *
+     * @var array
+     */
+    protected $byMinute;
+
+    /**
+     * Which hours to recur
+     *
+     * This is an array of integers (between 0 and 23)
+     *
+     * @var array
+     */
+    protected $byHour;
+
+    /**
+     * The current item in the list.
+     *
+     * You can get this number with the key() method.
+     *
+     * @var int
+     */
+    protected $counter = 0;
+
+    /**
+     * Which weekdays to recur.
+     *
+     * This is an array of weekdays
+     *
+     * This may also be preceeded by a positive or negative integer. If present,
+     * this indicates the nth occurrence of a specific day within the monthly or
+     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
+     * the month, or year.
+     *
+     * @var array
+     */
+    protected $byDay;
+
+    /**
+     * Which days of the month to recur
+     *
+     * This is an array of days of the months (1-31). The value can also be
+     * negative. -5 for instance means the 5th last day of the month.
+     *
+     * @var array
+     */
+    protected $byMonthDay;
+
+    /**
+     * Which days of the year to recur.
+     *
+     * This is an array with days of the year (1 to 366). The values can also
+     * be negative. For instance, -1 will always represent the last day of the
+     * year. (December 31st).
+     *
+     * @var array
+     */
+    protected $byYearDay;
+
+    /**
+     * Which week numbers to recur.
+     *
+     * This is an array of integers from 1 to 53. The values can also be
+     * negative. -1 will always refer to the last week of the year.
+     *
+     * @var array
+     */
+    protected $byWeekNo;
+
+    /**
+     * Which months to recur.
+     *
+     * This is an array of integers from 1 to 12.
+     *
+     * @var array
+     */
+    protected $byMonth;
+
+    /**
+     * Which items in an existing st to recur.
+     *
+     * These numbers work together with an existing by* rule. It specifies
+     * exactly which items of the existing by-rule to filter.
+     *
+     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
+     * used to recur the last workday of the month.
+     *
+     * This would be done by setting frequency to 'monthly', byDay to
+     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
+     *
+     * @var array
+     */
+    protected $bySetPos;
+
+    /**
+     * When the week starts.
+     *
+     * @var string
+     */
+    protected $weekStart = 'MO';
+
+    /* Functions that advance the iterator {{{ */
+
+    /**
+     * Does the processing for advancing the iterator for hourly frequency.
+     *
+     * @return void
+     */
+    protected function nextHourly() {
+
+        $this->currentDate->modify('+' . $this->interval . ' hours');
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for daily frequency.
+     *
+     * @return void
+     */
+    protected function nextDaily() {
+
+        if (!$this->byHour && !$this->byDay) {
+            $this->currentDate->modify('+' . $this->interval . ' days');
+            return;
+        }
+
+        if (isset($this->byHour)) {
+            $recurrenceHours = $this->getHours();
+        }
+
+        if (isset($this->byDay)) {
+            $recurrenceDays = $this->getDays();
+        }
+
+        if (isset($this->byMonth)) {
+            $recurrenceMonths = $this->getMonths();
+        }
+
+        do {
+            if ($this->byHour) {
+                if ($this->currentDate->format('G') == '23') {
+                    // to obey the interval rule
+                    $this->currentDate->modify('+' . $this->interval-1 . ' days');
+                }
+
+                $this->currentDate->modify('+1 hours');
+
+            } else {
+                $this->currentDate->modify('+' . $this->interval . ' days');
+
+            }
+
+            // Current month of the year
+            $currentMonth = $this->currentDate->format('n');
+
+            // Current day of the week
+            $currentDay = $this->currentDate->format('w');
+
+            // Current hour of the day
+            $currentHour = $this->currentDate->format('G');
+
+        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)) || ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)));
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for weekly frequency.
+     *
+     * @return void
+     */
+    protected function nextWeekly() {
+
+        if (!$this->byHour && !$this->byDay) {
+            $this->currentDate->modify('+' . $this->interval . ' weeks');
+            return;
+        }
+
+        if ($this->byHour) {
+            $recurrenceHours = $this->getHours();
+        }
+
+        if ($this->byDay) {
+            $recurrenceDays = $this->getDays();
+        }
+
+        // First day of the week:
+        $firstDay = $this->dayMap[$this->weekStart];
+
+        do {
+
+            if ($this->byHour) {
+                $this->currentDate->modify('+1 hours');
+            } else {
+                $this->currentDate->modify('+1 days');
+            }
+
+            // Current day of the week
+            $currentDay = (int) $this->currentDate->format('w');
+
+            // Current hour of the day
+            $currentHour = (int) $this->currentDate->format('G');
+
+            // We need to roll over to the next week
+            if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
+                $this->currentDate->modify('+' . $this->interval-1 . ' weeks');
+
+                // We need to go to the first day of this week, but only if we
+                // are not already on this first day of this week.
+                if($this->currentDate->format('w') != $firstDay) {
+                    $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
+                }
+            }
+
+            // We have a match
+        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
+    }
+
+    /**
+     * Does the processing for advancing the iterator for monthly frequency.
+     *
+     * @return void
+     */
+    protected function nextMonthly() {
+
+        $currentDayOfMonth = $this->currentDate->format('j');
+        if (!$this->byMonthDay && !$this->byDay) {
+
+            // If the current day is higher than the 28th, rollover can
+            // occur to the next month. We Must skip these invalid
+            // entries.
+            if ($currentDayOfMonth < 29) {
+                $this->currentDate->modify('+' . $this->interval . ' months');
+            } else {
+                $increase = 0;
+                do {
+                    $increase++;
+                    $tempDate = clone $this->currentDate;
+                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
+                } while ($tempDate->format('j') != $currentDayOfMonth);
+                $this->currentDate = $tempDate;
+            }
+            return;
+        }
+
+        while(true) {
+
+            $occurrences = $this->getMonthlyOccurrences();
+
+            foreach($occurrences as $occurrence) {
+
+                // The first occurrence thats higher than the current
+                // day of the month wins.
+                if ($occurrence > $currentDayOfMonth) {
+                    break 2;
+                }
+
+            }
+
+            // If we made it all the way here, it means there were no
+            // valid occurrences, and we need to advance to the next
+            // month.
+            //
+            // This line does not currently work in hhvm. Temporary workaround
+            // follows:
+            // $this->currentDate->modify('first day of this month');
+            $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
+            // end of workaround
+            $this->currentDate->modify('+ ' . $this->interval . ' months');
+
+            // This goes to 0 because we need to start counting at the
+            // beginning.
+            $currentDayOfMonth = 0;
+
+        }
+
+        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for yearly frequency.
+     *
+     * @return void
+     */
+    protected function nextYearly() {
+
+        $currentMonth = $this->currentDate->format('n');
+        $currentYear = $this->currentDate->format('Y');
+        $currentDayOfMonth = $this->currentDate->format('j');
+
+        // No sub-rules, so we just advance by year
+        if (!$this->byMonth) {
+
+            // Unless it was a leap day!
+            if ($currentMonth==2 && $currentDayOfMonth==29) {
+
+                $counter = 0;
+                do {
+                    $counter++;
+                    // Here we increase the year count by the interval, until
+                    // we hit a date that's also in a leap year.
+                    //
+                    // We could just find the next interval that's dividable by
+                    // 4, but that would ignore the rule that there's no leap
+                    // year every year that's dividable by a 100, but not by
+                    // 400. (1800, 1900, 2100). So we just rely on the datetime
+                    // functions instead.
+                    $nextDate = clone $this->currentDate;
+                    $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
+                } while ($nextDate->format('n')!=2);
+                $this->currentDate = $nextDate;
+
+                return;
+
+            }
+
+            // The easiest form
+            $this->currentDate->modify('+' . $this->interval . ' years');
+            return;
+
+        }
+
+        $currentMonth = $this->currentDate->format('n');
+        $currentYear = $this->currentDate->format('Y');
+        $currentDayOfMonth = $this->currentDate->format('j');
+
+        $advancedToNewMonth = false;
+
+        // If we got a byDay or getMonthDay filter, we must first expand
+        // further.
+        if ($this->byDay || $this->byMonthDay) {
+
+            while(true) {
+
+                $occurrences = $this->getMonthlyOccurrences();
+
+                foreach($occurrences as $occurrence) {
+
+                    // The first occurrence that's higher than the current
+                    // day of the month wins.
+                    // If we advanced to the next month or year, the first
+                    // occurrence is always correct.
+                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
+                        break 2;
+                    }
+
+                }
+
+                // If we made it here, it means we need to advance to
+                // the next month or year.
+                $currentDayOfMonth = 1;
+                $advancedToNewMonth = true;
+                do {
+
+                    $currentMonth++;
+                    if ($currentMonth>12) {
+                        $currentYear+=$this->interval;
+                        $currentMonth = 1;
+                    }
+                } while (!in_array($currentMonth, $this->byMonth));
+
+                $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+            }
+
+            // If we made it here, it means we got a valid occurrence
+            $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
+            return;
+
+        } else {
+
+            // These are the 'byMonth' rules, if there are no byDay or
+            // byMonthDay sub-rules.
+            do {
+
+                $currentMonth++;
+                if ($currentMonth>12) {
+                    $currentYear+=$this->interval;
+                    $currentMonth = 1;
+                }
+            } while (!in_array($currentMonth, $this->byMonth));
+            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+            return;
+
+        }
+
+    }
+
+    /* }}} */
+
+    /**
+     * This method receives a string from an RRULE property, and populates this
+     * class with all the values.
+     *
+     * @param string|array $rrule
+     * @return void
+     */
+    protected function parseRRule($rrule) {
+
+        if (is_string($rrule)) {
+            $rrule = Property\ICalendar\Recur::stringToArray($rrule);
+        }
+
+        foreach($rrule as $key=>$value) {
+
+            $key = strtoupper($key);
+            switch($key) {
+
+                case 'FREQ' :
+                    $value = strtolower($value);
+                    if (!in_array(
+                        $value,
+                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
+                    )) {
+                        throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
+                    }
+                    $this->frequency = $value;
+                    break;
+
+                case 'UNTIL' :
+                    $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
+
+                    // In some cases events are generated with an UNTIL=
+                    // parameter before the actual start of the event.
+                    //
+                    // Not sure why this is happening. We assume that the
+                    // intention was that the event only recurs once.
+                    //
+                    // So we are modifying the parameter so our code doesn't
+                    // break.
+                    if($this->until < $this->startDate) {
+                        $this->until = $this->startDate;
+                    }
+                    break;
+
+                case 'INTERVAL' :
+                    // No break
+
+                case 'COUNT' :
+                    $val = (int)$value;
+                    if ($val < 1) {
+                        throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!');
+                    }
+                    $key = strtolower($key);
+                    $this->$key = $val;
+                    break;
+
+                case 'BYSECOND' :
+                    $this->bySecond = (array)$value;
+                    break;
+
+                case 'BYMINUTE' :
+                    $this->byMinute = (array)$value;
+                    break;
+
+                case 'BYHOUR' :
+                    $this->byHour = (array)$value;
+                    break;
+
+                case 'BYDAY' :
+                    $value = (array)$value;
+                    foreach($value as $part) {
+                        if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
+                            throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part);
+                        }
+                    }
+                    $this->byDay = $value;
+                    break;
+
+                case 'BYMONTHDAY' :
+                    $this->byMonthDay = (array)$value;
+                    break;
+
+                case 'BYYEARDAY' :
+                    $this->byYearDay = (array)$value;
+                    break;
+
+                case 'BYWEEKNO' :
+                    $this->byWeekNo = (array)$value;
+                    break;
+
+                case 'BYMONTH' :
+                    $this->byMonth = (array)$value;
+                    break;
+
+                case 'BYSETPOS' :
+                    $this->bySetPos = (array)$value;
+                    break;
+
+                case 'WKST' :
+                    $this->weekStart = strtoupper($value);
+                    break;
+
+                default:
+                    throw new \InvalidArgumentException('Not supported: ' . strtoupper($key));
+
+            }
+
+        }
+
+    }
+
+    /**
+     * Mappings between the day number and english day name.
+     *
+     * @var array
+     */
+    protected $dayNames = array(
+        0 => 'Sunday',
+        1 => 'Monday',
+        2 => 'Tuesday',
+        3 => 'Wednesday',
+        4 => 'Thursday',
+        5 => 'Friday',
+        6 => 'Saturday',
+    );
+
+    /**
+     * Returns all the occurrences for a monthly frequency with a 'byDay' or
+     * 'byMonthDay' expansion for the current month.
+     *
+     * The returned list is an array of integers with the day of month (1-31).
+     *
+     * @return array
+     */
+    protected function getMonthlyOccurrences() {
+
+        $startDate = clone $this->currentDate;
+
+        $byDayResults = array();
+
+        // Our strategy is to simply go through the byDays, advance the date to
+        // that point and add it to the results.
+        if ($this->byDay) foreach($this->byDay as $day) {
+
+            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
+
+
+            // Dayname will be something like 'wednesday'. Now we need to find
+            // all wednesdays in this month.
+            $dayHits = array();
+
+            // workaround for missing 'first day of the month' support in hhvm
+            $checkDate = new \DateTime($startDate->format('Y-m-1'));
+            // workaround modify always advancing the date even if the current day is a $dayName in hhvm
+            if ($checkDate->format('l') !== $dayName) {
+                $checkDate->modify($dayName);
+            }
+
+            do {
+                $dayHits[] = $checkDate->format('j');
+                $checkDate->modify('next ' . $dayName);
+            } while ($checkDate->format('n') === $startDate->format('n'));
+
+            // So now we have 'all wednesdays' for month. It is however
+            // possible that the user only really wanted the 1st, 2nd or last
+            // wednesday.
+            if (strlen($day)>2) {
+                $offset = (int)substr($day,0,-2);
+
+                if ($offset>0) {
+                    // It is possible that the day does not exist, such as a
+                    // 5th or 6th wednesday of the month.
+                    if (isset($dayHits[$offset-1])) {
+                        $byDayResults[] = $dayHits[$offset-1];
+                    }
+                } else {
+
+                    // if it was negative we count from the end of the array
+                    $byDayResults[] = $dayHits[count($dayHits) + $offset];
+                }
+            } else {
+                // There was no counter (first, second, last wednesdays), so we
+                // just need to add the all to the list).
+                $byDayResults = array_merge($byDayResults, $dayHits);
+
+            }
+
+        }
+
+        $byMonthDayResults = array();
+        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
+
+            // Removing values that are out of range for this month
+            if ($monthDay > $startDate->format('t') ||
+                $monthDay < 0-$startDate->format('t')) {
+                    continue;
+            }
+            if ($monthDay>0) {
+                $byMonthDayResults[] = $monthDay;
+            } else {
+                // Negative values
+                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
+            }
+        }
+
+        // If there was just byDay or just byMonthDay, they just specify our
+        // (almost) final list. If both were provided, then byDay limits the
+        // list.
+        if ($this->byMonthDay && $this->byDay) {
+            $result = array_intersect($byMonthDayResults, $byDayResults);
+        } elseif ($this->byMonthDay) {
+            $result = $byMonthDayResults;
+        } else {
+            $result = $byDayResults;
+        }
+        $result = array_unique($result);
+        sort($result, SORT_NUMERIC);
+
+        // The last thing that needs checking is the BYSETPOS. If it's set, it
+        // means only certain items in the set survive the filter.
+        if (!$this->bySetPos) {
+            return $result;
+        }
+
+        $filteredResult = array();
+        foreach($this->bySetPos as $setPos) {
+
+            if ($setPos<0) {
+                $setPos = count($result)-($setPos+1);
+            }
+            if (isset($result[$setPos-1])) {
+                $filteredResult[] = $result[$setPos-1];
+            }
+        }
+
+        sort($filteredResult, SORT_NUMERIC);
+        return $filteredResult;
+
+    }
+
+    /**
+     * Simple mapping from iCalendar day names to day numbers
+     *
+     * @var array
+     */
+    protected $dayMap = array(
+        'SU' => 0,
+        'MO' => 1,
+        'TU' => 2,
+        'WE' => 3,
+        'TH' => 4,
+        'FR' => 5,
+        'SA' => 6,
+    );
+
+    protected function getHours()
+    {
+        $recurrenceHours = array();
+        foreach($this->byHour as $byHour) {
+            $recurrenceHours[] = $byHour;
+        }
+
+        return $recurrenceHours;
+    }
+
+    protected function getDays() {
+
+        $recurrenceDays = array();
+        foreach($this->byDay as $byDay) {
+
+            // The day may be preceeded with a positive (+n) or
+            // negative (-n) integer. However, this does not make
+            // sense in 'weekly' so we ignore it here.
+            $recurrenceDays[] = $this->dayMap[substr($byDay,-2)];
+
+        }
+
+        return $recurrenceDays;
+    }
+
+    protected function getMonths() {
+
+        $recurrenceMonths = array();
+        foreach($this->byMonth as $byMonth) {
+            $recurrenceMonths[] = $byMonth;
+        }
+
+        return $recurrenceMonths;
+    }
+}

+ 21 - 0
vendor/sabre/vobject/lib/RecurrenceIterator.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Sabre\VObject;
+
+use Sabre\VObject\Recur\EventIterator;
+
+/**
+ * RecurrenceIterator
+ *
+ * This class is deprecated. Use Sabre\VObject\Recur\EventIterator instead.
+ * This class will be removed from a future version.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @deprecated
+ * @license http://sabre.io/license Modified BSD License
+ */
+class RecurrenceIterator extends EventIterator {
+
+
+}

+ 116 - 0
vendor/sabre/vobject/lib/Splitter/ICalendar.php

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

+ 39 - 0
vendor/sabre/vobject/lib/Splitter/SplitterInterface.php

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

+ 79 - 0
vendor/sabre/vobject/lib/Splitter/VCard.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Sabre\VObject\Splitter;
+
+use
+    Sabre\VObject,
+    Sabre\VObject\Parser\MimeDir;
+
+/**
+ * Splitter
+ *
+ * This class is responsible for splitting up VCard objects.
+ *
+ * It is assumed that the input stream contains 1 or more VCARD objects. This
+ * class checks for BEGIN:VCARD and END:VCARD and parses each encountered
+ * component individually.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Dominik Tobschall
+ * @author Armin Hackmann
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCard implements SplitterInterface {
+
+    /**
+     * File handle
+     *
+     * @var resource
+     */
+    protected $input;
+
+    /**
+     * Persistent parser
+     *
+     * @var MimeDir
+     */
+    protected $parser;
+
+    /**
+     * Constructor
+     *
+     * The splitter should receive an readable file stream as it's input.
+     *
+     * @param resource $input
+     * @param int $options Parser options, see the OPTIONS constants.
+     */
+    public function __construct($input, $options = 0) {
+
+        $this->input = $input;
+        $this->parser = new MimeDir($input, $options);
+
+    }
+
+    /**
+     * Every time getNext() is called, a new object will be parsed, until we
+     * hit the end of the stream.
+     *
+     * When the end is reached, null will be returned.
+     *
+     * @return Sabre\VObject\Component|null
+     */
+    public function getNext() {
+
+        try {
+            $object = $this->parser->parse();
+
+            if (!$object instanceof VObject\Component\VCard) {
+                throw new VObject\ParseException('The supplied input contained non-VCARD data.');
+            }
+
+        } catch (VObject\EofException $e) {
+            return null;
+        }
+
+        return $object;
+
+    }
+
+}

+ 70 - 0
vendor/sabre/vobject/lib/StringUtil.php

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

+ 265 - 0
vendor/sabre/vobject/lib/TimeZoneUtil.php

@@ -0,0 +1,265 @@
+<?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-2014 fruux GmbH (https://fruux.com/).
+ * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class TimeZoneUtil {
+
+    public static $map = null;
+
+    /**
+     * List of microsoft exchange timezone ids.
+     *
+     * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
+     */
+    public static $microsoftExchangeMap = array(
+        0  => 'UTC',
+        31 => 'Africa/Casablanca',
+
+        // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
+        // I'm not even kidding.. We handle this special case in the
+        // getTimeZone method.
+        2  => 'Europe/Lisbon',
+        1  => 'Europe/London',
+        4  => 'Europe/Berlin',
+        6  => 'Europe/Prague',
+        3  => 'Europe/Paris',
+        69 => 'Africa/Luanda', // This was a best guess
+        7  => 'Europe/Athens',
+        5  => 'Europe/Bucharest',
+        49 => 'Africa/Cairo',
+        50 => 'Africa/Harare',
+        59 => 'Europe/Helsinki',
+        27 => 'Asia/Jerusalem',
+        26 => 'Asia/Baghdad',
+        74 => 'Asia/Kuwait',
+        51 => 'Europe/Moscow',
+        56 => 'Africa/Nairobi',
+        25 => 'Asia/Tehran',
+        24 => 'Asia/Muscat', // Best guess
+        54 => 'Asia/Baku',
+        48 => 'Asia/Kabul',
+        58 => 'Asia/Yekaterinburg',
+        47 => 'Asia/Karachi',
+        23 => 'Asia/Calcutta',
+        62 => 'Asia/Kathmandu',
+        46 => 'Asia/Almaty',
+        71 => 'Asia/Dhaka',
+        66 => 'Asia/Colombo',
+        61 => 'Asia/Rangoon',
+        22 => 'Asia/Bangkok',
+        64 => 'Asia/Krasnoyarsk',
+        45 => 'Asia/Shanghai',
+        63 => 'Asia/Irkutsk',
+        21 => 'Asia/Singapore',
+        73 => 'Australia/Perth',
+        75 => 'Asia/Taipei',
+        20 => 'Asia/Tokyo',
+        72 => 'Asia/Seoul',
+        70 => 'Asia/Yakutsk',
+        19 => 'Australia/Adelaide',
+        44 => 'Australia/Darwin',
+        18 => 'Australia/Brisbane',
+        76 => 'Australia/Sydney',
+        43 => 'Pacific/Guam',
+        42 => 'Australia/Hobart',
+        68 => 'Asia/Vladivostok',
+        41 => 'Asia/Magadan',
+        17 => 'Pacific/Auckland',
+        40 => 'Pacific/Fiji',
+        67 => 'Pacific/Tongatapu',
+        29 => 'Atlantic/Azores',
+        53 => 'Atlantic/Cape_Verde',
+        30 => 'America/Noronha',
+         8 => 'America/Sao_Paulo', // Best guess
+        32 => 'America/Argentina/Buenos_Aires',
+        60 => 'America/Godthab',
+        28 => 'America/St_Johns',
+         9 => 'America/Halifax',
+        33 => 'America/Caracas',
+        65 => 'America/Santiago',
+        35 => 'America/Bogota',
+        10 => 'America/New_York',
+        34 => 'America/Indiana/Indianapolis',
+        55 => 'America/Guatemala',
+        11 => 'America/Chicago',
+        37 => 'America/Mexico_City',
+        36 => 'America/Edmonton',
+        38 => 'America/Phoenix',
+        12 => 'America/Denver', // Best guess
+        13 => 'America/Los_Angeles', // Best guess
+        14 => 'America/Anchorage',
+        15 => 'Pacific/Honolulu',
+        16 => 'Pacific/Midway',
+        39 => 'Pacific/Kwajalein',
+    );
+
+    /**
+     * This method will try to find out the correct timezone for an iCalendar
+     * date-time value.
+     *
+     * You must pass the contents of the TZID parameter, as well as the full
+     * calendar.
+     *
+     * If the lookup fails, this method will return the default PHP timezone
+     * (as configured using date_default_timezone_set, or the date.timezone ini
+     * setting).
+     *
+     * Alternatively, if $failIfUncertain is set to true, it will throw an
+     * exception if we cannot accurately determine the timezone.
+     *
+     * @param string $tzid
+     * @param Sabre\VObject\Component $vcalendar
+     * @return DateTimeZone
+     */
+    static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
+
+        // First we will just see if the tzid is a support timezone identifier.
+        //
+        // The only exception is if the timezone starts with (. This is to
+        // handle cases where certain microsoft products generate timezone
+        // identifiers that for instance look like:
+        //
+        // (GMT+01.00) Sarajevo/Warsaw/Zagreb
+        //
+        // Since PHP 5.5.10, the first bit will be used as the timezone and
+        // this method will return just GMT+01:00. This is wrong, because it
+        // doesn't take DST into account.
+        if ($tzid[0]!=='(') {
+
+            // PHP has a bug that logs PHP warnings even it shouldn't:
+            // https://bugs.php.net/bug.php?id=67881
+            //
+            // That's why we're checking if we'll be able to successfull instantiate
+            // \DateTimeZone() before doing so. Otherwise we could simply instantiate
+            // and catch the exception.
+            $tzIdentifiers = \DateTimeZone::listIdentifiers();
+
+            try {
+                if (
+                    (in_array($tzid, $tzIdentifiers)) ||
+                    (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
+                    (in_array($tzid, self::getIdentifiersBC()))
+                ) {
+                    return new \DateTimeZone($tzid);
+                }
+            } catch(\Exception $e) {
+            }
+
+        }
+
+        self::loadTzMaps();
+
+        // Next, we check if the tzid is somewhere in our tzid map.
+        if (isset(self::$map[$tzid])) {
+            return new \DateTimeZone(self::$map[$tzid]);
+        }
+
+        // Maybe the author was hyper-lazy and just included an offset. We
+        // support it, but we aren't happy about it.
+        if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
+
+            // Note that the path in the source will never be taken from PHP 5.5.10
+            // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
+            // already gets returned early in this function. Once we drop support
+            // for versions under PHP 5.5.10, this bit can be taken out of the
+            // source.
+            // @codeCoverageIgnoreStart
+            return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
+            // @codeCoverageIgnoreEnd
+        }
+
+        if ($vcalendar) {
+
+            // If that didn't work, we will scan VTIMEZONE objects
+            foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
+
+                if ((string)$vtimezone->TZID === $tzid) {
+
+                    // Some clients add 'X-LIC-LOCATION' with the olson name.
+                    if (isset($vtimezone->{'X-LIC-LOCATION'})) {
+
+                        $lic = (string)$vtimezone->{'X-LIC-LOCATION'};
+
+                        // Libical generators may specify strings like
+                        // "SystemV/EST5EDT". For those we must remove the
+                        // SystemV part.
+                        if (substr($lic,0,8)==='SystemV/') {
+                            $lic = substr($lic,8);
+                        }
+
+                        return self::getTimeZone($lic, null, $failIfUncertain);
+
+                    }
+                    // Microsoft may add a magic number, which we also have an
+                    // answer for.
+                    if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
+                        $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
+
+                        // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
+                        if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
+                            return new \DateTimeZone('Europe/Sarajevo');
+                        }
+
+                        if (isset(self::$microsoftExchangeMap[$cdoId])) {
+                            return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
+                        }
+                    }
+
+                }
+
+            }
+
+        }
+
+        if ($failIfUncertain) {
+            throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
+        }
+
+        // If we got all the way here, we default to UTC.
+        return new \DateTimeZone(date_default_timezone_get());
+
+    }
+
+    /**
+     * This method will load in all the tz mapping information, if it's not yet
+     * done.
+     */
+    static public function loadTzMaps() {
+
+        if (!is_null(self::$map)) return;
+
+        self::$map = array_merge(
+            include __DIR__ .  '/timezonedata/windowszones.php',
+            include __DIR__ .  '/timezonedata/lotuszones.php',
+            include __DIR__ .  '/timezonedata/exchangezones.php',
+            include __DIR__ .  '/timezonedata/php-workaround.php'
+        );
+
+    }
+
+    /**
+     * This method returns an array of timezone identifiers, that are supported
+     * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers()
+     *
+     * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
+     * - It's not supported by some PHP versions as well as HHVM.
+     * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
+     * (See timezonedata/php-bc.php and timezonedata php-workaround.php)
+     *
+     * @return array
+     */
+    static public function getIdentifiersBC() {
+        return include __DIR__ .  '/timezonedata/php-bc.php';
+    }
+
+}

+ 67 - 0
vendor/sabre/vobject/lib/UUIDUtil.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * UUID Utility
+ *
+ * This class has static methods to generate and validate UUID's.
+ * UUIDs are used a decent amount within various *DAV standards, so it made
+ * sense to include it.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class UUIDUtil {
+
+    /**
+     * Returns a pseudo-random v4 UUID
+     *
+     * This function is based on a comment by Andrew Moore on php.net
+     *
+     * @see http://www.php.net/manual/en/function.uniqid.php#94959
+     * @return string
+     */
+    static public function getUUID() {
+
+        return sprintf(
+
+            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+
+            // 32 bits for "time_low"
+            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
+
+            // 16 bits for "time_mid"
+            mt_rand( 0, 0xffff ),
+
+            // 16 bits for "time_hi_and_version",
+            // four most significant bits holds version number 4
+            mt_rand( 0, 0x0fff ) | 0x4000,
+
+            // 16 bits, 8 bits for "clk_seq_hi_res",
+            // 8 bits for "clk_seq_low",
+            // two most significant bits holds zero and one for variant DCE1.1
+            mt_rand( 0, 0x3fff ) | 0x8000,
+
+            // 48 bits for "node"
+            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
+        );
+    }
+
+    /**
+     * Checks if a string is a valid UUID.
+     *
+     * @param string $uuid
+     * @return bool
+     */
+    static public function validateUUID($uuid) {
+
+        return preg_match(
+            '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
+            $uuid
+        ) == true;
+
+    }
+
+}

+ 424 - 0
vendor/sabre/vobject/lib/VCardConverter.php

@@ -0,0 +1,424 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * This utility converts vcards from one version to another.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH. All rights reserved.
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCardConverter {
+
+    /**
+     * Converts a vCard object to a new version.
+     *
+     * targetVersion must be one of:
+     *   Document::VCARD21
+     *   Document::VCARD30
+     *   Document::VCARD40
+     *
+     * Currently only 3.0 and 4.0 as input and output versions.
+     *
+     * 2.1 has some minor support for the input version, it's incomplete at the
+     * moment though.
+     *
+     * If input and output version are identical, a clone is returned.
+     *
+     * @param Component\VCard $input
+     * @param int $targetVersion
+     */
+    public function convert(Component\VCard $input, $targetVersion) {
+
+        $inputVersion = $input->getDocumentType();
+        if ($inputVersion===$targetVersion) {
+            return clone $input;
+        }
+
+        if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) {
+            throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
+        }
+        if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) {
+            throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
+        }
+
+        $newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0';
+
+        $output = new Component\VCard(array(
+            'VERSION' => $newVersion,
+        ));
+
+        foreach($input->children as $property) {
+
+            $this->convertProperty($input, $output, $property, $targetVersion);
+
+        }
+
+        return $output;
+
+    }
+
+    /**
+     * Handles conversion of a single property.
+     *
+     * @param Component\VCard $input
+     * @param Component\VCard $output
+     * @param Property $property
+     * @param int $targetVersion
+     * @return void
+     */
+    protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {
+
+        // Skipping these, those are automatically added.
+        if (in_array($property->name, array('VERSION', 'PRODID'))) {
+            return;
+        }
+
+        $parameters = $property->parameters();
+
+        $valueType = null;
+        if (isset($parameters['VALUE'])) {
+            $valueType = $parameters['VALUE']->getValue();
+            unset($parameters['VALUE']);
+        }
+        if (!$valueType) {
+            $valueType = $property->getValueType();
+        }
+
+        $newProperty = null;
+
+        if ($targetVersion===Document::VCARD30) {
+
+            if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
+
+                $newProperty = $this->convertUriToBinary($output, $property, $parameters);
+
+            } elseif ($property instanceof Property\VCard\DateAndOrTime) {
+
+                // In vCard 4, the birth year may be optional. This is not the
+                // case for vCard 3. Apple has a workaround for this that
+                // allows applications that support Apple's extension still
+                // omit birthyears in vCard 3, but applications that do not
+                // support this, will just use a random birthyear. We're
+                // choosing 1604 for the birthyear, because that's what apple
+                // uses.
+                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
+                if (is_null($parts['year'])) {
+                    $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
+                    $newProperty = $output->createProperty(
+                        $property->name,
+                        $newValue,
+                        array(
+                            'X-APPLE-OMIT-YEAR' => '1604'
+                        ),
+                        $valueType
+                    );
+
+                }
+
+            } elseif ($property->name === 'KIND') {
+
+                switch(strtolower($property->getValue())) {
+                    case 'org' :
+                        // OS X addressbook property.
+                        $newProperty = $output->createProperty('X-ABSHOWAS','COMPANY');
+                        break;
+                    case 'individual' :
+                        // Individual is implied, so we can just skip it.
+                        return;
+
+                    case 'group' :
+                        // OS X addressbook property
+                        $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP');
+                        break;
+                }
+
+
+            }
+
+        } elseif ($targetVersion===Document::VCARD40) {
+
+            // These properties were removed in vCard 4.0
+            if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) {
+                return;
+            }
+
+            if ($property instanceof Property\Binary) {
+
+                $newProperty = $this->convertBinaryToUri($output, $property, $parameters);
+
+            } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
+
+                // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
+                // then we're stripping the year from the vcard 4 value.
+                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
+                if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) {
+                    $newValue = '--' . $parts['month'] . '-' . $parts['date'];
+                    $newProperty = $output->createProperty(
+                        $property->name,
+                        $newValue,
+                        array(),
+                        $valueType
+                    );
+                }
+
+                // Regardless if the year matched or not, we do need to strip
+                // X-APPLE-OMIT-YEAR.
+                unset($parameters['X-APPLE-OMIT-YEAR']);
+
+            } else {
+                switch($property->name) {
+                    case 'X-ABSHOWAS' :
+                        if (strtoupper($property->getValue()) === 'COMPANY') {
+                            $newProperty = $output->createProperty('KIND','org');
+                        }
+                        break;
+                    case 'X-ADDRESSBOOKSERVER-KIND' :
+                        if (strtoupper($property->getValue()) === 'GROUP') {
+                            $newProperty = $output->createProperty('KIND','group');
+                        }
+                        break;
+                }
+
+            }
+
+        }
+
+
+        if (is_null($newProperty)) {
+
+            $newProperty = $output->createProperty(
+                $property->name,
+                $property->getParts(),
+                array(), // no parameters yet
+                $valueType
+            );
+
+        }
+
+        // set property group
+        $newProperty->group = $property->group;
+
+        if ($targetVersion===Document::VCARD40) {
+            $this->convertParameters40($newProperty, $parameters);
+        } else {
+            $this->convertParameters30($newProperty, $parameters);
+        }
+
+        // Lastly, we need to see if there's a need for a VALUE parameter.
+        //
+        // We can do that by instantating a empty property with that name, and
+        // seeing if the default valueType is identical to the current one.
+        $tempProperty = $output->createProperty($newProperty->name);
+        if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
+            $newProperty['VALUE'] = $newProperty->getValueType();
+        }
+
+        $output->add($newProperty);
+
+
+    }
+
+    /**
+     * Converts a BINARY property to a URI property.
+     *
+     * vCard 4.0 no longer supports BINARY properties.
+     *
+     * @param Component\VCard $output
+     * @param Property\Uri $property The input property.
+     * @param $parameters List of parameters that will eventually be added to
+     *                    the new property.
+     * @return Property\Uri
+     */
+    protected function convertBinaryToUri(Component\VCard $output, Property\Binary $property, array &$parameters) {
+
+        $newProperty = $output->createProperty(
+            $property->name,
+            null, // no value
+            array(), // no parameters yet
+            'URI' // Forcing the BINARY type
+        );
+
+        $mimeType = 'application/octet-stream';
+
+        // See if we can find a better mimetype.
+        if (isset($parameters['TYPE'])) {
+
+            $newTypes = array();
+            foreach($parameters['TYPE']->getParts() as $typePart) {
+                if (in_array(
+                    strtoupper($typePart),
+                    array('JPEG','PNG','GIF')
+                )) {
+                    $mimeType = 'image/' . strtolower($typePart);
+                } else {
+                    $newTypes[] = $typePart;
+                }
+            }
+
+            // If there were any parameters we're not converting to a
+            // mime-type, we need to keep them.
+            if ($newTypes) {
+                $parameters['TYPE']->setParts($newTypes);
+            } else {
+                unset($parameters['TYPE']);
+            }
+
+        }
+
+        $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($property->getValue()));
+
+        return $newProperty;
+
+    }
+
+    /**
+     * Converts a URI property to a BINARY property.
+     *
+     * In vCard 4.0 attachments are encoded as data: uri. Even though these may
+     * be valid in vCard 3.0 as well, we should convert those to BINARY if
+     * possible, to improve compatibility.
+     *
+     * @param Component\VCard $output
+     * @param Property\Uri $property The input property.
+     * @param $parameters List of parameters that will eventually be added to
+     *                    the new property.
+     * @return Property\Binary|null
+     */
+    protected function convertUriToBinary(Component\VCard $output, Property\Uri $property, array &$parameters) {
+
+        $value = $property->getValue();
+
+        // Only converting data: uris
+        if (substr($value, 0, 5)!=='data:') {
+            return;
+        }
+
+        $newProperty = $output->createProperty(
+            $property->name,
+            null, // no value
+            array(), // no parameters yet
+            'BINARY'
+        );
+
+        $mimeType = substr($value, 5, strpos($value, ',')-5);
+        if (strpos($mimeType, ';')) {
+            $mimeType = substr($mimeType,0,strpos($mimeType, ';'));
+            $newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1)));
+        } else {
+            $newProperty->setValue(substr($value, strpos($value,',')+1));
+        }
+        unset($value);
+
+        $newProperty['ENCODING'] = 'b';
+        switch($mimeType) {
+
+            case 'image/jpeg' :
+                $newProperty['TYPE'] = 'JPEG';
+                break;
+            case 'image/png' :
+                $newProperty['TYPE'] = 'PNG';
+                break;
+            case 'image/gif' :
+                $newProperty['TYPE'] = 'GIF';
+                break;
+
+        }
+
+
+        return $newProperty;
+
+    }
+
+    /**
+     * Adds parameters to a new property for vCard 4.0
+     *
+     * @param Property $newProperty
+     * @param array $parameters
+     * @return void
+     */
+    protected function convertParameters40(Property $newProperty, array $parameters) {
+
+        // Adding all parameters.
+        foreach($parameters as $param) {
+
+            // vCard 2.1 allowed parameters with no name
+            if ($param->noName) $param->noName = false;
+
+            switch($param->name) {
+
+                // We need to see if there's any TYPE=PREF, because in vCard 4
+                // that's now PREF=1.
+                case 'TYPE' :
+                    foreach($param->getParts() as $paramPart) {
+
+                        if (strtoupper($paramPart)==='PREF') {
+                            $newProperty->add('PREF','1');
+                        } else {
+                            $newProperty->add($param->name, $paramPart);
+                        }
+
+                    }
+                    break;
+                // These no longer exist in vCard 4
+                case 'ENCODING' :
+                case 'CHARSET' :
+                    break;
+
+                default :
+                    $newProperty->add($param->name, $param->getParts());
+                    break;
+
+            }
+
+        }
+
+    }
+
+    /**
+     * Adds parameters to a new property for vCard 3.0
+     *
+     * @param Property $newProperty
+     * @param array $parameters
+     * @return void
+     */
+    protected function convertParameters30(Property $newProperty, array $parameters) {
+
+        // Adding all parameters.
+        foreach($parameters as $param) {
+
+            // vCard 2.1 allowed parameters with no name
+            if ($param->noName) $param->noName = false;
+
+            switch($param->name) {
+
+                case 'ENCODING' :
+                    // This value only existed in vCard 2.1, and should be
+                    // removed for anything else.
+                    if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') {
+                        $newProperty->add($param->name, $param->getParts());
+                    }
+                    break;
+
+                /*
+                 * Converting PREF=1 to TYPE=PREF.
+                 *
+                 * Any other PREF numbers we'll drop.
+                 */
+                case 'PREF' :
+                    if ($param->getValue()=='1') {
+                        $newProperty->add('TYPE','PREF');
+                    }
+                    break;
+
+                default :
+                    $newProperty->add($param->name, $param->getParts());
+                    break;
+
+            }
+
+        }
+
+    }
+}

+ 19 - 0
vendor/sabre/vobject/lib/Version.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * This class contains the version number for the VObject package
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Version {
+
+    /**
+     * Full version number
+     */
+    const VERSION = '3.3.3';
+
+}

+ 93 - 0
vendor/sabre/vobject/lib/timezonedata/exchangezones.php

@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * 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
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+return array(
+    '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',
+);

+ 101 - 0
vendor/sabre/vobject/lib/timezonedata/lotuszones.php

@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * The following list are timezone names that could be generated by
+ * Lotus / Domino
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+return array(
+    'Dateline'               => 'Etc/GMT-12',
+    'Samoa'                  => 'Pacific/Apia',
+    'Hawaiian'               => 'Pacific/Honolulu',
+    'Alaskan'                => 'America/Anchorage',
+    'Pacific'                => 'America/Los_Angeles',
+    'Pacific Standard Time'  => 'America/Los_Angeles',
+    'Mexico Standard Time 2' => 'America/Chihuahua',
+    'Mountain'               => 'America/Denver',
+    // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones.
+    'US Mountain'            => 'America/Phoenix',
+    'Canada Central'         => 'America/Edmonton',
+    'Central America'        => 'America/Guatemala',
+    'Central'                => 'America/Chicago',
+    // 'Central Standard Time'  => 'America/Mexico_City', // conflict with windows timezones.
+    'Mexico'                 => 'America/Mexico_City',
+    'Eastern'                => 'America/New_York',
+    'SA Pacific'             => 'America/Bogota',
+    'US Eastern'             => 'America/Indiana/Indianapolis',
+    'Venezuela'              => 'America/Caracas',
+    'Atlantic'               => 'America/Halifax',
+    'Central Brazilian'      => 'America/Manaus',
+    'Pacific SA'             => 'America/Santiago',
+    'SA Western'             => 'America/La_Paz',
+    'Newfoundland'           => 'America/St_Johns',
+    'Argentina'              => 'America/Argentina/Buenos_Aires',
+    'E. South America'       => 'America/Belem',
+    'Greenland'              => 'America/Godthab',
+    'Montevideo'             => 'America/Montevideo',
+    'SA Eastern'             => 'America/Belem',
+    // 'Mid-Atlantic'           => 'Etc/GMT-2', // conflict with windows timezones.
+    'Azores'                 => 'Atlantic/Azores',
+    'Cape Verde'             => 'Atlantic/Cape_Verde',
+    'Greenwich'              => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
+    'Morocco'                => 'Africa/Casablanca',
+    'Central Europe'         => 'Europe/Prague',
+    'Central European'       => 'Europe/Sarajevo',
+    'Romance'                => 'Europe/Paris',
+    'W. Central Africa'      => 'Africa/Lagos', // Best guess
+    'W. Europe'              => 'Europe/Amsterdam',
+    'E. Europe'              => 'Europe/Minsk',
+    'Egypt'                  => 'Africa/Cairo',
+    'FLE'                    => 'Europe/Helsinki',
+    'GTB'                    => 'Europe/Athens',
+    'Israel'                 => 'Asia/Jerusalem',
+    'Jordan'                 => 'Asia/Amman',
+    'Middle East'            => 'Asia/Beirut',
+    'Namibia'                => 'Africa/Windhoek',
+    'South Africa'           => 'Africa/Harare',
+    'Arab'                   => 'Asia/Kuwait',
+    'Arabic'                 => 'Asia/Baghdad',
+    'E. Africa'              => 'Africa/Nairobi',
+    'Georgian'               => 'Asia/Tbilisi',
+    'Russian'                => 'Europe/Moscow',
+    'Iran'                   => 'Asia/Tehran',
+    'Arabian'                => 'Asia/Muscat',
+    'Armenian'               => 'Asia/Yerevan',
+    'Azerbijan'              => 'Asia/Baku',
+    'Caucasus'               => 'Asia/Yerevan',
+    'Mauritius'              => 'Indian/Mauritius',
+    'Afghanistan'            => 'Asia/Kabul',
+    'Ekaterinburg'           => 'Asia/Yekaterinburg',
+    'Pakistan'               => 'Asia/Karachi',
+    'West Asia'              => 'Asia/Tashkent',
+    'India'                  => 'Asia/Calcutta',
+    'Sri Lanka'              => 'Asia/Colombo',
+    'Nepal'                  => 'Asia/Kathmandu',
+    'Central Asia'           => 'Asia/Dhaka',
+    'N. Central Asia'        => 'Asia/Almaty',
+    'Myanmar'                => 'Asia/Rangoon',
+    'North Asia'             => 'Asia/Krasnoyarsk',
+    'SE Asia'                => 'Asia/Bangkok',
+    'China'                  => 'Asia/Shanghai',
+    'North Asia East'        => 'Asia/Irkutsk',
+    'Singapore'              => 'Asia/Singapore',
+    'Taipei'                 => 'Asia/Taipei',
+    'W. Australia'           => 'Australia/Perth',
+    'Korea'                  => 'Asia/Seoul',
+    'Tokyo'                  => 'Asia/Tokyo',
+    'Yakutsk'                => 'Asia/Yakutsk',
+    'AUS Central'            => 'Australia/Darwin',
+    'Cen. Australia'         => 'Australia/Adelaide',
+    'AUS Eastern'            => 'Australia/Sydney',
+    'E. Australia'           => 'Australia/Brisbane',
+    'Tasmania'               => 'Australia/Hobart',
+    'Vladivostok'            => 'Asia/Vladivostok',
+    'West Pacific'           => 'Pacific/Guam',
+    'Central Pacific'        => 'Asia/Magadan',
+    'Fiji'                   => 'Pacific/Fiji',
+    'New Zealand'            => 'Pacific/Auckland',
+    'Tonga'                  => 'Pacific/Tongatapu',
+);

+ 153 - 0
vendor/sabre/vobject/lib/timezonedata/php-bc.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * A list of additional PHP timezones that are returned by
+ * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
+ * valid for new DateTimeZone()
+ *
+ * This list does not include those timezone identifiers that we have to map to
+ * a different identifier for some PHP versions (see php-workaround.php).
+ *
+ * Instead of using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
+ * directly, we use this file because DateTimeZone::ALL_WITH_BC is not properly
+ * supported by all PHP version and HHVM.
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+ return array(
+    'Africa/Asmera',
+    'Africa/Timbuktu',
+    'America/Argentina/ComodRivadavia',
+    'America/Atka',
+    'America/Buenos_Aires',
+    'America/Catamarca',
+    'America/Coral_Harbour',
+    'America/Cordoba',
+    'America/Ensenada',
+    'America/Fort_Wayne',
+    'America/Indianapolis',
+    'America/Jujuy',
+    'America/Knox_IN',
+    'America/Louisville',
+    'America/Mendoza',
+    'America/Montreal',
+    'America/Porto_Acre',
+    'America/Rosario',
+    'America/Shiprock',
+    'America/Virgin',
+    'Antarctica/South_Pole',
+    'Asia/Ashkhabad',
+    'Asia/Calcutta',
+    'Asia/Chungking',
+    'Asia/Dacca',
+    'Asia/Istanbul',
+    'Asia/Katmandu',
+    'Asia/Macao',
+    'Asia/Saigon',
+    'Asia/Tel_Aviv',
+    'Asia/Thimbu',
+    'Asia/Ujung_Pandang',
+    'Asia/Ulan_Bator',
+    'Atlantic/Faeroe',
+    'Atlantic/Jan_Mayen',
+    'Australia/ACT',
+    'Australia/Canberra',
+    'Australia/LHI',
+    'Australia/North',
+    'Australia/NSW',
+    'Australia/Queensland',
+    'Australia/South',
+    'Australia/Tasmania',
+    'Australia/Victoria',
+    'Australia/West',
+    'Australia/Yancowinna',
+    'Brazil/Acre',
+    'Brazil/DeNoronha',
+    'Brazil/East',
+    'Brazil/West',
+    'Canada/Atlantic',
+    'Canada/Central',
+    'Canada/East-Saskatchewan',
+    'Canada/Eastern',
+    'Canada/Mountain',
+    'Canada/Newfoundland',
+    'Canada/Pacific',
+    'Canada/Saskatchewan',
+    'Canada/Yukon',
+    'CET',
+    'Chile/Continental',
+    'Chile/EasterIsland',
+    'EET',
+    'EST',
+    'Etc/GMT',
+    'Etc/GMT+0',
+    'Etc/GMT+1',
+    'Etc/GMT+10',
+    'Etc/GMT+11',
+    'Etc/GMT+12',
+    'Etc/GMT+2',
+    'Etc/GMT+3',
+    'Etc/GMT+4',
+    'Etc/GMT+5',
+    'Etc/GMT+6',
+    'Etc/GMT+7',
+    'Etc/GMT+8',
+    'Etc/GMT+9',
+    'Etc/GMT-0',
+    'Etc/GMT-1',
+    'Etc/GMT-10',
+    'Etc/GMT-11',
+    'Etc/GMT-12',
+    'Etc/GMT-13',
+    'Etc/GMT-14',
+    'Etc/GMT-2',
+    'Etc/GMT-3',
+    'Etc/GMT-4',
+    'Etc/GMT-5',
+    'Etc/GMT-6',
+    'Etc/GMT-7',
+    'Etc/GMT-8',
+    'Etc/GMT-9',
+    'Etc/GMT0',
+    'Etc/Greenwich',
+    'Etc/UCT',
+    'Etc/Universal',
+    'Etc/UTC',
+    'Etc/Zulu',
+    'Europe/Belfast',
+    'Europe/Nicosia',
+    'Europe/Tiraspol',
+    'GB',
+    'GMT',
+    'GMT+0',
+    'GMT-0',
+    'HST',
+    'MET',
+    'Mexico/BajaNorte',
+    'Mexico/BajaSur',
+    'Mexico/General',
+    'MST',
+    'NZ',
+    'Pacific/Ponape',
+    'Pacific/Samoa',
+    'Pacific/Truk',
+    'Pacific/Yap',
+    'PRC',
+    'ROC',
+    'ROK',
+    'UCT',
+    'US/Alaska',
+    'US/Aleutian',
+    'US/Arizona',
+    'US/Central',
+    'US/East-Indiana',
+    'US/Eastern',
+    'US/Hawaii',
+    'US/Indiana-Starke',
+    'US/Michigan',
+    'US/Mountain',
+    'US/Pacific',
+    'US/Pacific-New',
+    'US/Samoa',
+    'WET',
+);

+ 45 - 0
vendor/sabre/vobject/lib/timezonedata/php-workaround.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * A list of PHP timezones that were supported until 5.5.9, removed in
+ * PHP 5.5.10 and re-introduced in PHP 5.5.17
+ *
+ * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) returns them,
+ * but they are invalid for new DateTimeZone(). Fixed in PHP 5.5.17.
+ * https://bugs.php.net/bug.php?id=66985
+ *
+ * Some more info here:
+ * http://evertpot.com/php-5-5-10-timezone-changes/
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+return array(
+    'CST6CDT'   => 'America/Chicago',
+    'Cuba'      => 'America/Havana',
+    'Egypt'     => 'Africa/Cairo',
+    'Eire'      => 'Europe/Dublin',
+    'EST5EDT'   => 'America/New_York',
+    'Factory'   => 'UTC',
+    'GB-Eire'   => 'Europe/London',
+    'GMT0'      => 'UTC',
+    'Greenwich' => 'UTC',
+    'Hongkong'  => 'Asia/Hong_Kong',
+    'Iceland'   => 'Atlantic/Reykjavik',
+    'Iran'      => 'Asia/Tehran',
+    'Israel'    => 'Asia/Jerusalem',
+    'Jamaica'   => 'America/Jamaica',
+    'Japan'     => 'Asia/Tokyo',
+    'Kwajalein' => 'Pacific/Kwajalein',
+    'Libya'     => 'Africa/Tripoli',
+    'MST7MDT'   => 'America/Denver',
+    'Navajo'    => 'America/Denver',
+    'NZ-CHAT'   => 'Pacific/Chatham',
+    'Poland'    => 'Europe/Warsaw',
+    'Portugal'  => 'Europe/Lisbon',
+    'PST8PDT'   => 'America/Los_Angeles',
+    'Singapore' => 'Asia/Singapore',
+    'Turkey'    => 'Europe/Istanbul',
+    'Universal' => 'UTC',
+    'W-SU'      => 'Europe/Moscow',
+    'Zulu'      => 'UTC',
+);

+ 114 - 0
vendor/sabre/vobject/lib/timezonedata/windowszones.php

@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Automatically generated timezone file
+ *
+ * Last update: 2014-10-03T07:58:31-04:00
+ * Source: http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml
+ *
+ * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+
+return array (
+  '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',
+  'Atlantic Standard Time' => 'America/Halifax',
+  'Azerbaijan Standard Time' => 'Asia/Baku',
+  'Azores Standard Time' => 'Atlantic/Azores',
+  'Bahia Standard Time' => 'America/Bahia',
+  '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. 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/Bucharest',
+  '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',
+  'Kaliningrad Standard Time' => 'Europe/Kaliningrad',
+  'Korea Standard Time' => 'Asia/Seoul',
+  'Libya Standard Time' => 'Africa/Tripoli',
+  'Line Islands Standard Time' => 'Pacific/Kiritimati',
+  'Magadan Standard Time' => 'Asia/Magadan',
+  'Mauritius Standard Time' => 'Indian/Mauritius',
+  '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',
+  'Turkey Standard Time' => 'Europe/Istanbul',
+  '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',
+);

+ 22 - 0
vendor/sabre/vobject/tests/VObject/AttachIssueTest.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace Sabre\VObject;
+
+class AttachIssueTest extends \PHPUnit_Framework_TestCase {
+
+    function testRead() {
+
+        $event = <<<ICS
+BEGIN:VCALENDAR\r
+BEGIN:VEVENT\r
+ATTACH;FMTTYPE=;ENCODING=:Zm9v\r
+END:VEVENT\r
+END:VCALENDAR\r
+
+ICS;
+        $obj = Reader::read($event);
+        $this->assertEquals($event, $obj->serialize());
+
+    }
+
+}

+ 651 - 0
vendor/sabre/vobject/tests/VObject/CliTest.php

@@ -0,0 +1,651 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Tests the cli.
+ *
+ * Warning: these tests are very rudimentary.
+ */
+class CliTest extends \PHPUnit_Framework_TestCase {
+
+    public function setUp() {
+
+        $this->cli = new CliMock();
+        $this->cli->stderr = fopen('php://memory','r+');
+        $this->cli->stdout = fopen('php://memory','r+');
+
+    }
+
+    public function testInvalidArg() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', '--hi'))
+        );
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+    }
+
+    public function testQuiet() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', '-q'))
+        );
+        $this->assertTrue($this->cli->quiet);
+
+        rewind($this->cli->stderr);
+        $this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr)));
+
+    }
+
+    public function testHelp() {
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', '-h'))
+        );
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+    }
+
+    public function testFormat() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', '--format=jcard'))
+        );
+
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+        $this->assertEquals('jcard', $this->cli->format);
+
+    }
+
+    public function testFormatInvalid() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', '--format=foo'))
+        );
+
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+        $this->assertNull($this->cli->format);
+
+    }
+
+    public function testInputFormatInvalid() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', '--inputformat=foo'))
+        );
+
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+        $this->assertNull($this->cli->format);
+
+    }
+
+
+    public function testNoInputFile() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', 'color'))
+        );
+
+        rewind($this->cli->stderr);
+        $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+    }
+
+    public function testTooManyArgs() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', 'color', 'a', 'b', 'c'))
+        );
+
+    }
+
+    public function testUnknownCommand() {
+
+        $this->assertEquals(
+            1,
+            $this->cli->main(array('vobject', 'foo', '-'))
+        );
+
+    }
+
+    public function testConvertJson() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<ICS
+BEGIN:VCARD
+VERSION:3.0
+FN:Cowboy Henk
+END:VCARD
+ICS
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', 'convert','--format=json', '-'))
+        );
+
+        rewind($this->cli->stdout);
+        $version = Version::VERSION;
+        $this->assertEquals(
+            '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject '. $version .'\/\/EN"],["fn",{},"text","Cowboy Henk"]]]',
+            stream_get_contents($this->cli->stdout)
+        );
+
+    }
+
+    public function testConvertJCardPretty() {
+
+        if (version_compare(PHP_VERSION, '5.4.0') < 0) {
+            $this->markTestSkipped('This test required PHP 5.4.0');
+        }
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<ICS
+BEGIN:VCARD
+VERSION:3.0
+FN:Cowboy Henk
+END:VCARD
+ICS
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', 'convert','--format=jcard', '--pretty', '-'))
+        );
+
+        rewind($this->cli->stdout);
+        $version = Version::VERSION;
+
+        // PHP 5.5.12 changed the output
+
+        $expected = <<<JCARD
+[
+    "vcard",
+    [
+        [
+            "versi
+JCARD;
+
+          $this->assertStringStartsWith(
+            $expected,
+            stream_get_contents($this->cli->stdout)
+        );
+
+    }
+
+    public function testConvertJCalFail() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<ICS
+BEGIN:VCARD
+VERSION:3.0
+FN:Cowboy Henk
+END:VCARD
+ICS
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'convert','--format=jcal', '--inputformat=mimedir', '-'))
+        );
+
+    }
+
+    public function testConvertMimeDir() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<JCARD
+[
+    "vcard",
+    [
+        [
+            "version",
+            {
+
+            },
+            "text",
+            "4.0"
+        ],
+        [
+            "prodid",
+            {
+
+            },
+            "text",
+            "-\/\/Sabre\/\/Sabre VObject 3.1.0\/\/EN"
+        ],
+        [
+            "fn",
+            {
+
+            },
+            "text",
+            "Cowboy Henk"
+        ]
+    ]
+]
+JCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-'))
+        );
+
+        rewind($this->cli->stdout);
+        $expected = <<<VCF
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCF;
+
+          $this->assertEquals(
+            strtr($expected, array("\n"=>"\r\n")),
+            stream_get_contents($this->cli->stdout)
+        );
+
+    }
+
+    public function testConvertDefaultFormats() {
+
+        $inputStream = fopen('php://memory','r+');
+        $outputFile = SABRE_TEMPDIR . 'bar.json';
+
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'convert','foo.json',$outputFile))
+        );
+
+        $this->assertEquals('json', $this->cli->inputFormat);
+        $this->assertEquals('json', $this->cli->format);
+
+    }
+
+    public function testConvertDefaultFormats2() {
+
+        $outputFile = SABRE_TEMPDIR . 'bar.ics';
+
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'convert','foo.ics',$outputFile))
+        );
+
+        $this->assertEquals('mimedir', $this->cli->inputFormat);
+        $this->assertEquals('mimedir', $this->cli->format);
+
+    }
+
+    public function testVCard3040() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:3.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-'))
+        );
+
+        rewind($this->cli->stdout);
+
+        $version = Version::VERSION;
+        $expected = <<<VCF
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject $version//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCF;
+
+          $this->assertEquals(
+            strtr($expected, array("\n"=>"\r\n")),
+            stream_get_contents($this->cli->stdout)
+        );
+
+    }
+
+    public function testVCard4030() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        $this->assertEquals(
+            0,
+            $this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-'))
+        );
+
+        $version = Version::VERSION;
+
+        rewind($this->cli->stdout);
+        $expected = <<<VCF
+BEGIN:VCARD
+VERSION:3.0
+PRODID:-//Sabre//Sabre VObject $version//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCF;
+
+          $this->assertEquals(
+            strtr($expected, array("\n"=>"\r\n")),
+            stream_get_contents($this->cli->stdout)
+        );
+
+    }
+
+    public function testVCard4021() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+FN:Cowboy Henk
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+
+        // vCard 2.1 is not supported yet, so this returns a failure.
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'convert','--format=vcard21', '--pretty', '-'))
+        );
+
+    }
+
+    function testValidate() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+UID:foo
+FN:Cowboy Henk
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        $result = $this->cli->main(array('vobject', 'validate', '-'));
+
+        $this->assertEquals(
+            0,
+            $result
+        );
+
+    }
+
+    function testValidateFail() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCALENDAR
+VERSION:2.0
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'validate', '-'))
+        );
+
+    }
+
+    function testValidateFail2() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCALENDAR
+VERSION:5.0
+END:VCALENDAR
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'validate', '-'))
+        );
+
+    }
+
+    function testRepair() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:5.0
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+        $this->assertEquals(
+            2,
+            $this->cli->main(array('vobject', 'repair', '-'))
+        );
+
+        rewind($this->cli->stdout);
+        $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\nEND:VCARD\r\n", stream_get_contents($this->cli->stdout));
+
+    }
+
+    function testRepairNothing() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 3.1.0//EN
+BEGIN:VEVENT
+UID:foo
+DTSTAMP:20140122T233226Z
+DTSTART:20140101T120000Z
+END:VEVENT
+END:VCALENDAR
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+
+        $result = $this->cli->main(array('vobject', 'repair', '-'));
+
+        rewind($this->cli->stderr);
+        $error = stream_get_contents($this->cli->stderr);
+
+        $this->assertEquals(
+            0,
+            $result,
+            "This should have been error free. stderr output:\n" . $error
+        );
+
+    }
+
+    /**
+     * Note: this is a very shallow test, doesn't dig into the actual output,
+     * but just makes sure there's no errors thrown.
+     *
+     * The colorizer is not a critical component, it's mostly a debugging tool.
+     */
+    function testColorCalendar() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        $version = Version::VERSION;
+
+        /**
+         * This object is not valid, but it's designed to hit every part of the
+         * colorizer source.
+         */
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject {$version}//EN
+BEGIN:VTIMEZONE
+END:VTIMEZONE
+BEGIN:VEVENT
+ATTENDEE;RSVP=TRUE:mailto:foo@example.org
+REQUEST-STATUS:5;foo
+ATTACH:blabla
+END:VEVENT
+END:VCALENDAR
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+
+        $result = $this->cli->main(array('vobject', 'color', '-'));
+
+        rewind($this->cli->stderr);
+        $error = stream_get_contents($this->cli->stderr);
+
+        $this->assertEquals(
+            0,
+            $result,
+            "This should have been error free. stderr output:\n" . $error
+        );
+
+    }
+
+    /**
+     * Note: this is a very shallow test, doesn't dig into the actual output,
+     * but just makes sure there's no errors thrown.
+     *
+     * The colorizer is not a critical component, it's mostly a debugging tool.
+     */
+    function testColorVCard() {
+
+        $inputStream = fopen('php://memory','r+');
+
+        $version = Version::VERSION;
+
+        /**
+         * This object is not valid, but it's designed to hit every part of the
+         * colorizer source.
+         */
+        fwrite($inputStream, <<<VCARD
+BEGIN:VCARD
+VERSION:4.0
+PRODID:-//Sabre//Sabre VObject {$version}//EN
+ADR:1;2;3;4a,4b;5;6
+group.TEL:123454768
+END:VCARD
+
+VCARD
+    );
+        rewind($inputStream);
+        $this->cli->stdin = $inputStream;
+        // vCard 2.1 is not supported yet, so this returns a failure.
+
+        $result = $this->cli->main(array('vobject', 'color', '-'));
+
+        rewind($this->cli->stderr);
+        $error = stream_get_contents($this->cli->stderr);
+
+        $this->assertEquals(
+            0,
+            $result,
+            "This should have been error free. stderr output:\n" . $error
+        );
+
+    }
+}
+
+class CliMock extends Cli {
+
+    public $log = array();
+
+    public $quiet = false;
+
+    public $format;
+
+    public $pretty;
+
+    public $stdin;
+
+    public $stdout;
+
+    public $stderr;
+
+    public $inputFormat;
+
+    public $outputFormat;
+
+}

+ 179 - 0
vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject\Component;
+use DateTime;
+use Sabre\VObject\Reader;
+
+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();
+
+        $calendar = new VCalendar();
+
+        // Hard date and time        
+        $valarm1 = $calendar->createComponent('VALARM');
+        $valarm1->add(
+            $calendar->createProperty('TRIGGER', '20120312T130000Z', array('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 = $calendar->createComponent('VALARM');
+        $valarm2->add(
+            $calendar->createProperty('TRIGGER', '-P1D', array('VALUE' => 'DURATION'))
+        );
+
+        $vevent2 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm3->add( $calendar->createProperty('TRIGGER', '-P1D', array('VALUE'=>'DURATION', 'RELATED' => 'END')) );
+
+        $vevent3 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm4->TRIGGER = '-P1D';
+        $valarm4->TRIGGER['VALUE'] = 'DURATION';
+        $valarm4->TRIGGER['RELATED']= 'END';
+
+        $vtodo4 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm5->TRIGGER = '-P1D';
+        $valarm5->TRIGGER['VALUE'] = 'DURATION';
+        $valarm5->REPEAT = 10;
+        $valarm5->DURATION = 'P1D';
+
+        $vevent5 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm6->TRIGGER = '-P1D';
+        $valarm6->TRIGGER['VALUE'] = 'DURATION';
+        $valarm6->DURATION = 'P1D';
+
+        $vevent6 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm7->TRIGGER = '-P1D';
+        $valarm7->TRIGGER['VALUE'] = 'DURATION';
+        $valarm7->TRIGGER['RELATED']= 'END';
+
+        $vevent7 = $calendar->createComponent('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 = $calendar->createComponent('VALARM');
+        $valarm7->TRIGGER = '-P1D';
+        $valarm7->TRIGGER['VALUE'] = 'DURATION';
+        $valarm7->TRIGGER['RELATED']= 'END';
+
+        $vevent7 = $calendar->createComponent('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() {
+
+        $calendar = new VCalendar();
+        $valarm = $calendar->createComponent('VALARM');
+        $valarm->TRIGGER = '-P1D';
+        $valarm->TRIGGER['RELATED'] = 'END';
+
+        $vjournal = $calendar->createComponent('VJOURNAL');
+        $vjournal->add($valarm);
+
+        $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
+
+    }
+
+    /**
+     * This bug was found and reported on the mailing list.
+     */
+    public function testInTimeRangeBuggy() {
+
+$input = <<<BLA
+BEGIN:VCALENDAR
+BEGIN:VTODO
+DTSTAMP:20121003T064931Z
+UID:b848cb9a7bb16e464a06c222ca1f8102@examle.com
+STATUS:NEEDS-ACTION
+DUE:20121005T000000Z
+SUMMARY:Task 1
+CATEGORIES:AlarmCategory
+BEGIN:VALARM
+TRIGGER:-PT10M
+ACTION:DISPLAY
+DESCRIPTION:Task 1
+END:VALARM
+END:VTODO
+END:VCALENDAR
+BLA;
+
+        $vobj = Reader::read($input);
+
+        $this->assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00')));
+
+    }
+
+}
+

+ 474 - 0
vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php

@@ -0,0 +1,474 @@
+<?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: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:20111202T120000Z
+DTEND:20111202T130000Z
+RECURRENCE-ID:20111202T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111209T120000Z
+DTEND:20111209T130000Z
+RECURRENCE-ID:20111209T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111216T120000Z
+DTEND:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111223T120000Z
+DTEND:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111230T120000Z
+DTEND:20111230T130000Z
+RECURRENCE-ID:20111230T120000Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $tests[] = array($input, $output);
+
+        // 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:20111202T120000Z
+DTEND: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:20111216T120000Z
+DTEND:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111223T120000Z
+DTEND:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111230T120000Z
+DTEND: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')
+        );
+
+    }
+
+    function testGetDocumentType() {
+
+        $vcard = new VCalendar();
+        $vcard->VERSION = '2.0';
+        $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType());
+
+    }
+
+    function testValidateCorrect() {
+
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+DTSTAMP:20140122T233226Z
+UID:foo
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(array(), $vcal->validate(), 'Got an error');
+
+    }
+
+    function testValidateNoVersion() {
+
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateWrongVersion() {
+
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:3.0
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateNoProdId() {
+
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateDoubleCalScale() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+CALSCALE:GREGORIAN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateDoubleMethod() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateTwoMasterEvents() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(1, count($vcal->validate()));
+
+    }
+
+    function testValidateOneMasterEvent() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+        $this->assertEquals(0, count($vcal->validate()));
+
+    }
+
+    function testGetBaseComponent() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+SUMMARY:test
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+
+        $result = $vcal->getBaseComponent();
+        $this->assertEquals('test', $result->SUMMARY->getValue());
+
+    }
+
+    function testGetBaseComponentNoResult() {
+
+        $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+SUMMARY:test
+RECURRENCE-ID;VALUE=DATE:20111202
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+        $vcal = VObject\Reader::read($input);
+
+        $result = $vcal->getBaseComponent();
+        $this->assertNull($result);
+
+    }
+}

+ 176 - 0
vendor/sabre/vobject/tests/VObject/Component/VCardTest.php

@@ -0,0 +1,176 @@
+<?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\nUID:foo\r\nEND:VCARD\r\n",
+            array(),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+        );
+
+        // No VERSION
+        $tests[] = array(
+            "BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+            array(
+                'VERSION MUST appear exactly once in a VCARD component',
+            ),
+            "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+        );
+
+        // Unknown version
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\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:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+        );
+
+        // No FN
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\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\nUID:foo\r\nEND:VCARD\r\n",
+        );
+        // No FN, N fallback
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\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\nUID:foo\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\nUID:foo\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\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n",
+        );
+
+        // No FN, ORG fallback
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\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\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n",
+        );
+        return $tests;
+
+    }
+
+    function testGetDocumentType() {
+
+        $vcard = new VCard(array(), false);
+        $vcard->VERSION = '2.1';
+        $this->assertEquals(VCard::VCARD21, $vcard->getDocumentType());
+
+        $vcard = new VCard(array(), false);
+        $vcard->VERSION = '3.0';
+        $this->assertEquals(VCard::VCARD30, $vcard->getDocumentType());
+
+        $vcard = new VCard(array(), false);
+        $vcard->VERSION = '4.0';
+        $this->assertEquals(VCard::VCARD40, $vcard->getDocumentType());
+
+        $vcard = new VCard(array(), false);
+        $this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType());
+    }
+
+    function testPreferredNoPref() {
+
+        $vcard = <<<VCF
+BEGIN:VCARD
+VERSION:3.0
+EMAIL:1@example.org
+EMAIL:2@example.org
+END:VCARD
+VCF;
+
+        $vcard = VObject\Reader::read($vcard);
+        $this->assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue());
+
+    }
+
+    function testPreferredWithPref() {
+
+        $vcard = <<<VCF
+BEGIN:VCARD
+VERSION:3.0
+EMAIL:1@example.org
+EMAIL;TYPE=PREF:2@example.org
+END:VCARD
+VCF;
+
+        $vcard = VObject\Reader::read($vcard);
+        $this->assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue());
+
+    }
+
+    function testPreferredWith40Pref() {
+
+        $vcard = <<<VCF
+BEGIN:VCARD
+VERSION:4.0
+EMAIL:1@example.org
+EMAIL;PREF=3:2@example.org
+EMAIL;PREF=2:3@example.org
+END:VCARD
+VCF;
+
+        $vcard = VObject\Reader::read($vcard);
+        $this->assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue());
+
+    }
+
+    function testPreferredNotFound() {
+
+        $vcard = <<<VCF
+BEGIN:VCARD
+VERSION:4.0
+END:VCARD
+VCF;
+
+        $vcard = VObject\Reader::read($vcard);
+        $this->assertNull($vcard->preferred('EMAIL'));
+
+    }
+}

+ 76 - 0
vendor/sabre/vobject/tests/VObject/Component/VEventTest.php

@@ -0,0 +1,76 @@
+<?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();
+
+        $calendar = new VCalendar();
+
+        $vevent = $calendar->createComponent('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;
+
+    }
+
+}
+

+ 66 - 0
vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+use Sabre\VObject\Reader;
+
+class VFreeBusyTest extends \PHPUnit_Framework_TestCase {
+
+    function testIsFree() {
+
+        $input = <<<BLA
+BEGIN:VCALENDAR
+BEGIN:VFREEBUSY
+FREEBUSY;FBTYPE=FREE:20120912T000500Z/PT1H
+FREEBUSY;FBTYPE=BUSY:20120912T010000Z/20120912T020000Z
+FREEBUSY;FBTYPE=BUSY-TENTATIVE:20120912T020000Z/20120912T030000Z
+FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20120912T030000Z/20120912T040000Z
+FREEBUSY;FBTYPE=BUSY:20120912T050000Z/20120912T060000Z,20120912T080000Z/20120912T090000Z
+FREEBUSY;FBTYPE=BUSY:20120912T100000Z/PT1H
+END:VFREEBUSY
+END:VCALENDAR
+BLA;
+
+        $obj = VObject\Reader::read($input);
+        $vfb = $obj->VFREEBUSY;
+
+        $tz = new \DateTimeZone('UTC');
+
+        $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz)));
+        $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz)));
+        $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz)));
+
+        // Checking whether the end time is treated as non-inclusive
+        $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz)));
+        $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz)));
+        $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz)));
+
+    }
+
+    public function testValidate() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VFREEBUSY
+UID:some-random-id
+DTSTAMP:20140402T180200Z
+END:VFREEBUSY
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(), $messages);
+
+    }
+
+}

+ 101 - 0
vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject\Component;
+use Sabre\VObject\Reader;
+
+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 testValidate() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VJOURNAL
+UID:12345678
+DTSTAMP:20140402T174100Z
+END:VJOURNAL
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(), $messages);
+
+    }
+
+    public function testValidateBroken() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VJOURNAL
+UID:12345678
+DTSTAMP:20140402T174100Z
+URL:http://example.org/
+URL:http://example.com/
+END:VJOURNAL
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(
+            array("URL MUST NOT appear more than once in a VJOURNAL component"),
+            $messages
+        );
+
+    }
+
+    public function timeRangeTestData() {
+
+        $calendar = new VCalendar();
+
+        $tests = array();
+
+        $vjournal = $calendar->createComponent('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 = $calendar->createComponent('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 = $calendar->createComponent('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;
+    }
+
+
+
+}
+

+ 34 - 0
vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+use Sabre\VObject\Reader;
+
+class VTimeZoneTest extends \PHPUnit_Framework_TestCase {
+
+    public function testValidate() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VTIMEZONE
+TZID:America/Toronto
+END:VTIMEZONE
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(), $messages);
+
+    }
+
+}

+ 180 - 0
vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use
+    Sabre\VObject\Component,
+    Sabre\VObject\Reader;
+
+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();
+
+        $calendar = new VCalendar();
+
+        $vtodo = $calendar->createComponent('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 = $calendar->createComponent('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 = $calendar->createComponent('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 = $calendar->createComponent('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 = $calendar->createComponent('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 = $calendar->createComponent('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;
+
+    }
+
+    public function testValidate() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VTODO
+UID:1234-21355-123156
+DTSTAMP:20140402T183400Z
+END:VTODO
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(), $messages);
+
+    }
+
+    public function testValidateInvalid() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VTODO
+END:VTODO
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(
+            "UID MUST appear exactly once in a VTODO component",
+            "DTSTAMP MUST appear exactly once in a VTODO component",
+        ), $messages);
+
+    }
+
+    public function testValidateDUEDTSTARTMisMatch() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VTODO
+UID:FOO
+DTSTART;VALUE=DATE-TIME:20140520T131600Z
+DUE;VALUE=DATE:20140520
+DTSTAMP;VALUE=DATE-TIME:20140520T131600Z
+END:VTODO
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(
+            "The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART",
+        ), $messages);
+
+    }
+
+    public function testValidateDUEbeforeDTSTART() {
+
+        $input = <<<HI
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:YoYo
+BEGIN:VTODO
+UID:FOO
+DTSTART;VALUE=DATE:20140520
+DUE;VALUE=DATE:20140518
+DTSTAMP;VALUE=DATE-TIME:20140520T131600Z
+END:VTODO
+END:VCALENDAR
+HI;
+
+        $obj = Reader::read($input);
+
+        $warnings = $obj->validate();
+        $messages = array();
+        foreach($warnings as $warning) {
+            $messages[] = $warning['message'];
+        }
+
+        $this->assertEquals(array(
+            "DUE must occur after DTSTART",
+        ), $messages);
+
+    }
+
+}
+

+ 528 - 0
vendor/sabre/vobject/tests/VObject/ComponentTest.php

@@ -0,0 +1,528 @@
+<?php
+
+namespace Sabre\VObject;
+
+use
+    Sabre\VObject\Component\VCalendar,
+    Sabre\VObject\Component\VCard;
+
+class ComponentTest extends \PHPUnit_Framework_TestCase {
+
+    function testIterate() {
+
+        $comp = new VCalendar(array(), false);
+
+        $sub = $comp->createComponent('VEVENT');
+        $comp->add($sub);
+
+        $sub = $comp->createComponent('VTODO');
+        $comp->add($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 VCalendar(array(), false);
+
+        $sub = $comp->createComponent('VEVENT');
+        $comp->add($sub);
+
+        $sub = $comp->createComponent('VTODO');
+        $comp->add($sub);
+
+        $event = $comp->vevent;
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $event);
+        $this->assertEquals('VEVENT', $event->name);
+
+        $this->assertInternalType('null', $comp->vjournal);
+
+    }
+
+    function testMagicGetGroups() {
+
+        $comp = new VCard();
+
+        $sub = $comp->createProperty('GROUP1.EMAIL','1@1.com');
+        $comp->add($sub);
+
+        $sub = $comp->createProperty('GROUP2.EMAIL','2@2.com');
+        $comp->add($sub);
+
+        $sub = $comp->createProperty('EMAIL','3@3.com');
+        $comp->add($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 VCalendar();
+
+        $sub = $comp->createComponent('VEVENT');
+        $comp->add($sub);
+
+        $sub = $comp->createComponent('VTODO');
+        $comp->add($sub);
+
+        $this->assertTrue(isset($comp->vevent));
+        $this->assertTrue(isset($comp->vtodo));
+        $this->assertFalse(isset($comp->vjournal));
+
+    }
+
+    function testMagicSetScalar() {
+
+        $comp = new VCalendar();
+        $comp->myProp = 'myValue';
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+        $this->assertEquals('myValue',(string)$comp->MYPROP);
+
+
+    }
+
+    function testMagicSetScalarTwice() {
+
+        $comp = new VCalendar(array(), false);
+        $comp->myProp = 'myValue';
+        $comp->myProp = 'myValue';
+
+        $this->assertEquals(1,count($comp->children()));
+        $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+        $this->assertEquals('myValue',(string)$comp->MYPROP);
+
+    }
+
+    function testMagicSetArray() {
+
+        $comp = new VCalendar();
+        $comp->ORG = array('Acme Inc', 'Section 9');
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->ORG);
+        $this->assertEquals(array('Acme Inc', 'Section 9'),$comp->ORG->getParts());
+
+    }
+
+    function testMagicSetComponent() {
+
+        $comp = new VCalendar();
+
+        // Note that 'myProp' is ignored here.
+        $comp->myProp = $comp->createComponent('VEVENT');
+
+        $this->assertEquals(1, count($comp));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testMagicSetTwice() {
+
+        $comp = new VCalendar(array(), false);
+
+        $comp->VEVENT = $comp->createComponent('VEVENT');
+        $comp->VEVENT = $comp->createComponent('VEVENT');
+
+        $this->assertEquals(1, count($comp->children()));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testArrayAccessGet() {
+
+        $comp = new VCalendar(array(), false);
+
+        $event = $comp->createComponent('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 VCalendar();
+
+        $event = $comp->createComponent('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 VCalendar();
+        $comp['hey'] = 'hi there';
+
+    }
+    /**
+     * @expectedException LogicException
+     */
+    function testArrayAccessUnset() {
+
+        $comp = new VCalendar();
+        unset($comp[0]);
+
+    }
+
+    function testAddScalar() {
+
+        $comp = new VCalendar(array(), false);
+
+        $comp->add('myprop','value');
+
+        $this->assertEquals(1, count($comp->children()));
+
+        $bla = $comp->children[0];
+
+        $this->assertTrue($bla instanceof Property);
+        $this->assertEquals('MYPROP',$bla->name);
+        $this->assertEquals('value',(string)$bla);
+
+    }
+
+    function testAddScalarParams() {
+
+        $comp = new VCalendar(array(), false);
+
+        $comp->add('myprop','value',array('param1'=>'value1'));
+
+        $this->assertEquals(1, count($comp->children()));
+
+        $bla = $comp->children[0];
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $bla);
+        $this->assertEquals('MYPROP',$bla->name);
+        $this->assertEquals('value', (string)$bla);
+
+        $this->assertEquals(1, count($bla->parameters()));
+
+        $this->assertEquals('PARAM1',$bla->parameters['PARAM1']->name);
+        $this->assertEquals('value1',$bla->parameters['PARAM1']->getValue());
+
+    }
+
+
+    function testAddComponent() {
+
+        $comp = new VCalendar(array(), false);
+
+        $comp->add($comp->createComponent('VEVENT'));
+
+        $this->assertEquals(1, count($comp->children()));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testAddComponentTwice() {
+
+        $comp = new VCalendar(array(), false);
+
+        $comp->add($comp->createComponent('VEVENT'));
+        $comp->add($comp->createComponent('VEVENT'));
+
+        $this->assertEquals(2, count($comp->children()));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail() {
+
+        $comp = new VCalendar();
+        $comp->add($comp->createComponent('VEVENT'),'hello');
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail2() {
+
+        $comp = new VCalendar();
+        $comp->add(array());
+
+    }
+
+    function testMagicUnset() {
+
+        $comp = new VCalendar(array(), false);
+        $comp->add($comp->createComponent('VEVENT'));
+
+        unset($comp->vevent);
+
+        $this->assertEquals(0, count($comp->children()));
+
+    }
+
+
+    function testCount() {
+
+        $comp = new VCalendar();
+        $this->assertEquals(1,$comp->count());
+
+    }
+
+    function testChildren() {
+
+        $comp = new VCalendar(array(), false);
+
+        // Note that 'myProp' is ignored here.
+        $comp->add($comp->createComponent('VEVENT'));
+        $comp->add($comp->createComponent('VTODO'));
+
+        $r = $comp->children();
+        $this->assertInternalType('array', $r);
+        $this->assertEquals(2,count($r));
+    }
+
+    function testGetComponents() {
+
+        $comp = new VCalendar();
+
+        $comp->add($comp->createProperty('FOO','BAR'));
+        $comp->add($comp->createComponent('VTODO'));
+
+        $r = $comp->getComponents();
+        $this->assertInternalType('array', $r);
+        $this->assertEquals(1, count($r));
+        $this->assertEquals('VTODO', $r[0]->name);
+    }
+
+    function testSerialize() {
+
+        $comp = new VCalendar(array(), false);
+        $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
+
+    }
+
+    function testSerializeChildren() {
+
+        $comp = new VCalendar(array(), false);
+        $event = $comp->add($comp->createComponent('VEVENT'));
+        unset($event->DTSTAMP, $event->UID);
+        $comp->add($comp->createComponent('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 VCalendar(array(), false);
+        $comp->add($event = $comp->createComponent('VEVENT'));
+        $comp->add('PROP1','BLABLA');
+        $comp->add('VERSION','2.0');
+        $comp->add($comp->createComponent('VTIMEZONE'));
+
+        unset($event->DTSTAMP, $event->UID);
+        $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 VCard(array(), false);
+
+        $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);
+
+    }
+
+    function testInstantiateWithChildren() {
+
+        $comp = new VCard(array(
+            'ORG' => array('Acme Inc.', 'Section 9'),
+            'FN' => 'Finn The Human',
+        ));
+
+        $this->assertEquals(array('Acme Inc.', 'Section 9'), $comp->ORG->getParts());
+        $this->assertEquals('Finn The Human', $comp->FN->getValue());
+
+    }
+
+    function testInstantiateSubComponent() {
+
+        $comp = new VCalendar();
+        $event = $comp->createComponent('VEVENT', array(
+            $comp->createProperty('UID', '12345'),
+        ));
+        $comp->add($event);
+
+        $this->assertEquals('12345', $comp->VEVENT->UID->getValue());
+
+    }
+
+    function testRemoveByName() {
+
+        $comp = new VCalendar(array(), false);
+        $comp->add('prop1','val1');
+        $comp->add('prop2','val2');
+        $comp->add('prop2','val2');
+
+        $comp->remove('prop2');
+        $this->assertFalse(isset($comp->prop2));
+        $this->assertTrue(isset($comp->prop1));
+
+    }
+
+    function testRemoveByObj() {
+
+        $comp = new VCalendar(array(), false);
+        $comp->add('prop1','val1');
+        $prop = $comp->add('prop2','val2');
+
+        $comp->remove($prop);
+        $this->assertFalse(isset($comp->prop2));
+        $this->assertTrue(isset($comp->prop1));
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testRemoveNotFound() {
+
+        $comp = new VCalendar(array(), false);
+        $prop = $comp->createProperty('A','B');
+        $comp->remove($prop);
+
+    }
+
+    /**
+     * @dataProvider ruleData
+     */
+    function testValidateRules($componentList, $errorCount) {
+
+        $vcard = new Component\VCard();
+
+        $component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
+        foreach($componentList as $v) {
+            $component->add($v,'Hello.');
+        }
+
+        $this->assertEquals($errorCount, count($component->validate()));
+
+    }
+
+    function testValidateRepair() {
+
+        $vcard = new Component\VCard();
+
+        $component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
+        $component->validate(Component::REPAIR);
+        $this->assertEquals('yow', $component->BAR->getValue());
+
+    }
+
+    function ruleData() {
+
+        return array(
+
+            array(array(), 2),
+            array(array('FOO'), 3),
+            array(array('BAR'), 1),
+            array(array('BAZ'), 1),
+            array(array('BAR','BAZ'), 0),
+            array(array('BAR','BAZ','ZIM',), 0),
+            array(array('BAR','BAZ','ZIM','GIR'), 0),
+            array(array('BAR','BAZ','ZIM','GIR','GIR'), 1),
+
+        );
+
+    }
+
+}
+
+class FakeComponent extends Component {
+
+    public function getValidationRules() {
+
+        return array(
+            'FOO' => '0',
+            'BAR' => '1',
+            'BAZ' => '+',
+            'ZIM' => '*',
+            'GIR' => '?',
+        );
+
+    }
+
+    public function getDefaults() {
+
+        return array(
+            'BAR' => 'yow',
+        );
+
+    }
+
+}
+

+ 389 - 0
vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php

@@ -0,0 +1,389 @@
+<?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('+0 seconds', DateTimeParser::parseDuration('+PT0S',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 14:14:05',new DateTimeZone('Europe/Amsterdam'));
+        $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);
+
+    }
+
+    /**
+     * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events
+     */
+    function testParseICalendarDateGreaterThan4000() {
+
+        $dateTime = DateTimeParser::parseDate('45001231');
+
+        $expected = new DateTime('4500-12-31 00:00:00',new DateTimeZone('UTC'));
+
+        $this->assertEquals($expected, $dateTime);
+
+        $dateTime = DateTimeParser::parse('45001231');
+        $this->assertEquals($expected, $dateTime);
+
+    }
+
+    /**
+     * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events
+     */
+    function testParseICalendarDateTimeGreaterThan4000() {
+
+        $dateTime = DateTimeParser::parseDateTime('45001231T235959');
+
+        $expected = new DateTime('4500-12-31 23:59:59',new DateTimeZone('UTC'));
+
+        $this->assertEquals($expected, $dateTime);
+
+        $dateTime = DateTimeParser::parse('45001231T235959');
+        $this->assertEquals($expected, $dateTime);
+
+    }
+
+    /**
+     * @depends testParseICalendarDate
+     * @expectedException LogicException
+     */
+    function testParseICalendarDateBadFormat() {
+
+        $dateTime = DateTimeParser::parseDate('20100316T141405');
+
+    }
+
+    /**
+     * @dataProvider vcardDates
+     */
+    function testVCardDate($input, $output) {
+
+        $this->assertEquals(
+            $output,
+            DateTimeParser::parseVCardDateTime($input)
+        );
+
+    }
+
+    /**
+     * @dataProvider vcardDates
+     * @expectedException \InvalidArgumentException
+     */
+    function testBadVCardDate() {
+
+        DateTimeParser::parseVCardDateTime('1985---01');
+
+    }
+
+    /**
+     * @dataProvider vcardDates
+     * @expectedException \InvalidArgumentException
+     */
+    function testBadVCardTime() {
+
+        DateTimeParser::parseVCardTime('23:12:166');
+
+    }
+
+    function vcardDates() {
+
+        return array(
+            array(
+                "19961022T140000",
+                array(
+                    "year" => 1996,
+                    "month" => 10,
+                    "date" => 22,
+                    "hour" => 14,
+                    "minute" => 00,
+                    "second" => 00,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "--1022T1400",
+                array(
+                    "year" => null,
+                    "month" => 10,
+                    "date" => 22,
+                    "hour" => 14,
+                    "minute" => 00,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "---22T14",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => 22,
+                    "hour" => 14,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "19850412",
+                array(
+                    "year" => 1985,
+                    "month" => 4,
+                    "date" => 12,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "1985-04",
+                array(
+                    "year" => 1985,
+                    "month" => 04,
+                    "date" => null,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "1985",
+                array(
+                    "year" => 1985,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "--0412",
+                array(
+                    "year" => null,
+                    "month" => 4,
+                    "date" => 12,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "---12",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => 12,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T102200",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => 10,
+                    "minute" => 22,
+                    "second" => 0,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T1022",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => 10,
+                    "minute" => 22,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T10",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => 10,
+                    "minute" => null,
+                    "second" => null,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T-2200",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => null,
+                    "minute" => 22,
+                    "second" => 00,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T--00",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => null,
+                    "minute" => null,
+                    "second" => 00,
+                    "timezone" => null
+                ),
+            ),
+            array(
+                "T102200Z",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => 10,
+                    "minute" => 22,
+                    "second" => 00,
+                    "timezone" => 'Z'
+                ),
+            ),
+            array(
+                "T102200-0800",
+                array(
+                    "year" => null,
+                    "month" => null,
+                    "date" => null,
+                    "hour" => 10,
+                    "minute" => 22,
+                    "second" => 00,
+                    "timezone" => '-0800'
+                ),
+            ),
+
+            // extended format
+            array(
+                "2012-11-29T15:10:53Z",
+                array(
+                    "year" => 2012,
+                    "month" => 11,
+                    "date" => 29,
+                    "hour" => 15,
+                    "minute" => 10,
+                    "second" => 53,
+                    "timezone" => 'Z'
+                ),
+            ),
+        );
+
+    }
+
+
+}

+ 70 - 0
vendor/sabre/vobject/tests/VObject/DocumentTest.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace Sabre\VObject;
+
+class DocumentTest extends \PHPUnit_Framework_TestCase {
+
+    function testGetDocumentType() {
+
+        $doc = new MockDocument();
+        $this->assertEquals(Document::UNKNOWN, $doc->getDocumentType());
+
+    }
+
+    function testConstruct() {
+
+        $doc = new MockDocument('VLIST');
+        $this->assertEquals('VLIST', $doc->name);
+
+    }
+
+    function testCreateComponent() {
+
+        $vcal = new Component\VCalendar(array(), false);
+
+        $event = $vcal->createComponent('VEVENT');
+
+        $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
+        $vcal->add($event);
+
+        $prop = $vcal->createProperty('X-PROP','1234256',array('X-PARAM' => '3'));
+        $this->assertInstanceOf('Sabre\VObject\Property', $prop);
+
+        $event->add($prop);
+
+        unset(
+            $event->DTSTAMP,
+            $event->UID
+        );
+
+        $out = $vcal->serialize();
+        $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out);
+
+    }
+
+    function testCreate() {
+
+        $vcal = new Component\VCalendar(array(), false);
+
+        $event = $vcal->create('VEVENT');
+        $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
+
+        $event = $vcal->create('CALSCALE');
+        $this->assertInstanceOf('Sabre\VObject\Property\Text', $event);
+
+    }
+
+    function testGetClassNameForPropertyValue() {
+
+        $vcal = new Component\VCalendar(array(), false);
+        $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT'));
+        $this->assertNull($vcal->getClassNameForPropertyValue('FOO'));
+
+    }
+
+}
+
+
+class MockDocument extends Document {
+
+}

+ 33 - 0
vendor/sabre/vobject/tests/VObject/ElementListTest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Sabre\VObject;
+
+class ElementListTest extends \PHPUnit_Framework_TestCase {
+
+    function testIterate() {
+
+        $cal = new Component\VCalendar();
+        $sub = $cal->createComponent('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);
+
+    }
+
+
+}

+ 55 - 0
vendor/sabre/vobject/tests/VObject/EmClientTest.php

@@ -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);
+
+    }
+
+}
+

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff