]> git.mxchange.org Git - friendica-addons.git/commitdiff
Merge remote branch 'friendica/master'
authorfabrixxm <fabrix.xm@gmail.com>
Fri, 31 Aug 2012 13:03:27 +0000 (09:03 -0400)
committerfabrixxm <fabrix.xm@gmail.com>
Fri, 31 Aug 2012 13:03:27 +0000 (09:03 -0400)
264 files changed:
blockem.tgz
blockem/blockem.php
dav/Changelog.txt [new file with mode: 0644]
dav/FriendicaACLPlugin.inc.php [deleted file]
dav/README.md
dav/SabreDAV/ChangeLog
dav/SabreDAV/README.md [new file with mode: 0644]
dav/SabreDAV/bin/migrateto17.php
dav/SabreDAV/composer.json
dav/SabreDAV/docs/caldav-notifications.txt [new file with mode: 0644]
dav/SabreDAV/docs/caldav-sharing-02.txt [new file with mode: 0644]
dav/SabreDAV/docs/rfc5785.txt [new file with mode: 0644]
dav/SabreDAV/examples/sql/mysql.locks.sql
dav/SabreDAV/examples/webserver/apache2_vhost.conf
dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf
dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php
dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Backend/PDO.php
dav/SabreDAV/lib/Sabre/CalDAV/Calendar.php
dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php
dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php
dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php
dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php
dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/ICSExportPlugin.php
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INode.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INotificationType.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Node.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php
dav/SabreDAV/lib/Sabre/CalDAV/Schedule/IMip.php
dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php
dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php
dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php
dav/SabreDAV/lib/Sabre/DAV/Client.php
dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php
dav/SabreDAV/lib/Sabre/DAV/Property.php
dav/SabreDAV/lib/Sabre/DAV/Property/Response.php
dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/DAV/Server.php
dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php
dav/SabreDAV/lib/Sabre/DAV/XMLUtil.php
dav/SabreDAV/lib/Sabre/DAVACL/Property/Acl.php
dav/SabreDAV/lib/Sabre/HTTP/BasicAuth.php
dav/SabreDAV/lib/Sabre/HTTP/Version.php
dav/SabreDAV/lib/Sabre/VObject/Component.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Component/VAlarm.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Component/VEvent.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Component/VJournal.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Component/VTodo.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/DateTimeParser.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Element.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/ElementList.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/FreeBusyGenerator.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Node.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Parameter.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/ParseException.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Property.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Property/MultiDateTime.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Reader.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/Version.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/includes.php [deleted file]
dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php
dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php
dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php
dav/SabreDAV/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue166Test.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue172Test.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue203Test.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue205Test.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php [new file with mode: 0644]
dav/SabreDAV/tests/Sabre/CalDAV/Issue228Test.php [new file with mode: 0644]
dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php [new file with mode: 0644]
dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php [new file with mode: 0644]
dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php [new file with mode: 0644]
dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php
dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php
dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php
dav/SabreDAV/tests/Sabre/DAV/Locks/PluginTest.php
dav/SabreDAV/tests/Sabre/DAV/Property/HrefListTest.php
dav/SabreDAV/tests/Sabre/DAV/Property/HrefTest.php
dav/SabreDAV/tests/Sabre/DAV/Property/SupportedReportSetTest.php
dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php
dav/SabreDAV/tests/Sabre/DAV/ServerMKCOLTest.php
dav/SabreDAV/tests/Sabre/DAV/ServerPropsTest.php
dav/SabreDAV/tests/Sabre/DAV/TemporaryFileFilterTest.php
dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php
dav/SabreDAV/tests/Sabre/DAV/XMLUtilTest.php
dav/SabreDAV/tests/Sabre/HTTP/BasicAuthTest.php
dav/SabreDAV/tests/Sabre/VObject/Component/VAlarmTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Component/VCalendarTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Component/VJournalTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Component/VTodoTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/DateTimeParserTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/ElementListTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/EmClientTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/FreeBusyGeneratorTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Issue153Test.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/ParameterTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Property/DateTimeTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/Property/MultiDateTimeTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/PropertyTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/ReaderTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/VersionTest.php [deleted file]
dav/SabreDAV/tests/Sabre/VObject/issue153.vcf [deleted file]
dav/SabreDAV/tests/bootstrap.php
dav/calendar.friendica.fnk.php [deleted file]
dav/colorpicker/demo.html
dav/common/calendar.fnk.php
dav/common/calendar_rendering.fnk.php [new file with mode: 0644]
dav/common/dav_caldav_backend.inc.php [deleted file]
dav/common/dav_caldav_backend_common.inc.php
dav/common/dav_caldav_backend_private.inc.php [new file with mode: 0644]
dav/common/dav_caldav_backend_virtual.inc.php [new file with mode: 0644]
dav/common/dav_caldav_calendar_private.inc.php [new file with mode: 0644]
dav/common/dav_caldav_calendar_virtual.inc.php [new file with mode: 0644]
dav/common/dav_carddav_backend_common.inc.php [new file with mode: 0644]
dav/common/dav_carddav_backend_private.inc.php [new file with mode: 0644]
dav/common/dav_carddav_backend_std.inc.php [deleted file]
dav/common/dav_carddav_backend_virtual.inc.php [new file with mode: 0644]
dav/common/dav_user_calendars.inc.php
dav/common/dbclasses/dbclass.friendica.calendarobjects.class.php [deleted file]
dav/common/dbclasses/dbclass.friendica.calendars.class.php [deleted file]
dav/common/dbclasses/dbclass.friendica.jqcalendar.class.php [deleted file]
dav/common/dbclasses/dbclass.friendica.notifications.class.php [deleted file]
dav/common/dbclasses/dbclass_animexx.class.php [deleted file]
dav/common/virtual_cal_source_backend.inc.php [deleted file]
dav/common/wdcal.js
dav/common/wdcal/js/jquery.calendar.js
dav/common/wdcal/js/wdCalendar_lang_DE.js
dav/common/wdcal/js/wdCalendar_lang_EN.js
dav/common/wdcal_backend.inc.php [new file with mode: 0644]
dav/common/wdcal_cal_source.inc.php [deleted file]
dav/common/wdcal_cal_source_private.inc.php [deleted file]
dav/common/wdcal_configuration.php
dav/common/wdcal_edit.inc.php [new file with mode: 0644]
dav/database-init.inc.php [deleted file]
dav/dav.php
dav/dav_caldav_backend_friendica.inc.php [deleted file]
dav/dav_carddav_backend_friendica_community.inc.php [deleted file]
dav/dav_friendica_auth.inc.php [deleted file]
dav/dav_friendica_principal.inc.php [deleted file]
dav/friendica/FriendicaACLPlugin.inc.php [new file with mode: 0644]
dav/friendica/calendar.friendica.fnk.php [new file with mode: 0644]
dav/friendica/database-init.inc.php [new file with mode: 0644]
dav/friendica/dav_caldav_backend_virtual_friendica.inc.php [new file with mode: 0644]
dav/friendica/dav_carddav_backend_virtual_friendica.inc.php [new file with mode: 0644]
dav/friendica/dav_friendica_auth.inc.php [new file with mode: 0644]
dav/friendica/dav_friendica_principal.inc.php [new file with mode: 0644]
dav/friendica/layout.fnk.php [new file with mode: 0644]
dav/friendica/main.php [new file with mode: 0644]
dav/friendica/wdcal.css [new file with mode: 0644]
dav/iCalcreator/iCalcreator.class.php [deleted file]
dav/iCalcreator/lgpl.txt [deleted file]
dav/iCalcreator/releaseNotes-2.12.txt [deleted file]
dav/iCalcreator/releaseSummary.txt [deleted file]
dav/iCalcreator/summary.html [deleted file]
dav/jqueryui/jquery-ui-1.8.20.custom.css [deleted file]
dav/jqueryui/jquery-ui-1.8.20.custom.min.js [deleted file]
dav/jqueryui/jquery-ui-1.8.21.custom.css [new file with mode: 0644]
dav/jqueryui/jquery-ui-1.8.21.custom.min.js [new file with mode: 0644]
dav/jqueryui/jquery.ui.datepicker-de.js [new file with mode: 0644]
dav/layout.fnk.php [deleted file]
dav/main.php [deleted file]
dav/sabre-vobject/.travis.yml [new file with mode: 0644]
dav/sabre-vobject/ChangeLog [new file with mode: 0644]
dav/sabre-vobject/LICENSE [new file with mode: 0644]
dav/sabre-vobject/README.md [new file with mode: 0644]
dav/sabre-vobject/composer.json [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VAlarm.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VCalendar.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VCard.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VEvent.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VJournal.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Component/VTodo.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/DateTimeParser.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Element.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/ElementList.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/FreeBusyGenerator.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Node.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Parameter.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/ParseException.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Property.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Property/DateTime.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Property/MultiDateTime.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Reader.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/RecurrenceIterator.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/TimeZoneUtil.php [new file with mode: 0644]
dav/sabre-vobject/lib/Sabre/VObject/Version.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VAlarmTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VCalendarTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VCardTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VEventTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VJournalTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Component/VTodoTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/ComponentTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/DateTimeParserTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/ElementListTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/EmClientTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/FreeBusyGeneratorTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Issue153Test.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Issue154Test.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/ParameterTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Property/DateTimeTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/Property/MultiDateTimeTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/PropertyTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/ReaderTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/TimeZoneUtilTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/VersionTest.php [new file with mode: 0644]
dav/sabre-vobject/tests/Sabre/VObject/issue153.vcf [new file with mode: 0644]
dav/sabre-vobject/tests/bootstrap.php [new file with mode: 0644]
dav/sabre-vobject/tests/phpunit.xml [new file with mode: 0644]
dav/timepicker/index.htm
dav/virtual_cal_source_friendica.inc.php [deleted file]
dav/wdcal.css [deleted file]
dav/wdcal/Changelog.txt [deleted file]
dav/wdcal_cal_source_friendicaevents.inc.php [deleted file]
group_text/group_text.css [new file with mode: 0755]
group_text/group_text.php [new file with mode: 0755]
impressum/impressum.php
infiniteimprobabilitydrive.tgz
infiniteimprobabilitydrive/infiniteimprobabilitydrive.php
irc.tgz
irc/irc.php
jappixmini/jappixmini.php
morechoice.tgz
morechoice/morechoice.php
morepokes/morepokes.php [new file with mode: 0644]
nsfw.tgz
openstreetmap.tgz
openstreetmap/openstreetmap.php
page.tgz
piwik.tgz
piwik/piwik.php
privacy_image_cache.tgz
privacy_image_cache/privacy_image_cache.php
showmore.tgz
showmore/showmore.php
statusnet.tgz
statusnet/statusnet.php
twitter.tgz
twitter/twitter.php

index c1842e52411ebbf55ee9f96894d11bbafb6234a7..65026527f6ad843c2cdfa4b1c2654ea03637904a 100755 (executable)
Binary files a/blockem.tgz and b/blockem.tgz differ
index 5ff87c58b2d0ee2ae3f4690af94df9eee682ffc9..85c7fec68a32b0d631c538e1d9b0f9ebde33088d 100755 (executable)
@@ -157,9 +157,9 @@ function blockem_item_photo_menu(&$a,&$b) {
                }
        }
        if($blocked)
-               $b['menu'][ t('Unblock Author')] = 'javascript:blockemUnblock("' . $author . '");';
+               $b['menu'][ t('Unblock Author')] = 'javascript:blockemUnblock(\'' . $author . '\');';
        else
-               $b['menu'][ t('Block Author')] = 'javascript:blockemBlock("' . $author . '");';
+               $b['menu'][ t('Block Author')] = 'javascript:blockemBlock(\'' . $author . '\');';
 }
 
 function blockem_module() {}
diff --git a/dav/Changelog.txt b/dav/Changelog.txt
new file mode 100644 (file)
index 0000000..c336baa
--- /dev/null
@@ -0,0 +1,23 @@
+v0.3
+[REFACTOR] The new version of the VObject Library is used
+[REFACTOR] The addressbook part has beed heavily refactored
+[REFACTOR] Remove some Friendica-specific code out of the "common"-folder
+
+v0.2.0
+======
+[FEATURE] Multiple private Calendars can be created. Each calendar can have its own default color; single events of a calendar can override this setting.
+[FEATURE] Support for recurring events.
+[FEATURE] ICS files can be imported to and exported from a calendar.
+[FEATURE] Notification by e-mail is supported.
+[COMPATIBILITY] When creating or updating an event using CalDAV, the etag is returned.
+
+v0.1.1
+======
+[FEATURE] A "New Event" Button in the navigation bar of the calendar is added.
+[FEATURE] When creating an event by dragging in the calendar, the "Edit Details"-Link leads to a page where the details can be added before actually creating the event.
+[BUGFIX] When editing a event, the start time cannot be set befor the end time anymore.
+[BUGFIX] Fixed some problems with Magic Quotes
+
+v0.1.0
+======
+Initial Release
\ No newline at end of file
diff --git a/dav/FriendicaACLPlugin.inc.php b/dav/FriendicaACLPlugin.inc.php
deleted file mode 100644 (file)
index 6bb8fc9..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-class Sabre_DAVACL_Plugin_Friendica extends Sabre_DAVACL_Plugin {
-
-       /*
-        * A dirty hack to make iOS CalDAV work with subdirectorys.
-        * When using a Root URL like /dav/ (as it is necessary for friendica), iOS does not evaluate the current-user-principal property,
-        * but only principal-URL. Actually principal-URL is not allowed in /dav/, only for Principal collections, but this seems
-        * to be the only way to force iOS to look at the right location.
-        */
-
-       public function beforeGetProperties($uri, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
-
-               parent::beforeGetProperties($uri, $node, $requestedProperties, $returnedProperties);
-
-               if (false !== ($index = array_search('{DAV:}principal-URL', $requestedProperties))) {
-
-                       unset($requestedProperties[$index]);
-                       $returnedProperties[200]['{DAV:}principal-URL'] = new Sabre_DAV_Property_Href('principals/users/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
-
-               }
-               if (false !== ($index = array_search('{urn:ietf:params:xml:ns:caldav}calendar-home-set', $requestedProperties))) {
-
-                       unset($requestedProperties[$index]);
-                       $returnedProperties[200]['{urn:ietf:params:xml:ns:caldav}calendar-home-set'] = new Sabre_DAV_Property_Href('calendars/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
-
-               }
-
-       }
-
-}
\ No newline at end of file
index bab0a1dda29dd38f8966f876452b8ecb0d710e73..a82439722bd8d429c8be7ca72192b11c005de207 100644 (file)
@@ -6,10 +6,15 @@ It's still in a very early stage, so expect major bugs. Please feel free to repo
 At the moment, the calendar system supports the following features:
 - A web-based drag&drop interface for managing events
 - All-Day-Events, Multi-Day-Events, and time-based events
+- Giving the subject, a description, a location and a color for the event (the color is not available through CalDAV, though)
+- Recurrences (not the whole set of options given in the iCalendar spec, but the most important ones)
+- Notification by e-mail. Multiple notifications can be set per event
+- Multiple calendars per user
 - Access to the events using CalDAV (using iPhone, Thunderbird Lightning etc., see below)
-- read-only access to the friendica-native events (also using CalDAV)
+- Read-only access to the friendica-native events (also using CalDAV)
 - The friendica-contacts are made available using CardDAV (confirmed to work with iOS)
-- Giving the subject, a description, a location and a color for the event (the color is not available through CalDAV, though)
+- The events of a calendar can be exported as ICS file. ICS files can be imported into a calendar
+
 
 Internationalization:
 - At the moment, settings for the US and the german systems are selectable (regarding the date format and the first day of the week). More will be added on request.
@@ -17,8 +22,10 @@ Internationalization:
 
 CalDAV device compatibility:
 - iOS (iPhone/iPodTouch) works
-- Thunderbird Lightning should work, not tested yet
-- Android: http://dmfs.org/caldav/ seems to work, not much tested yet, though
+- Thunderbird Lightning works
+- Android:
+  - aCal (http://andrew.mcmillan.net.nz/projects/aCal) works, available in F-Droid and Google Play
+  - CalDAV-Sync (http://dmfs.org/caldav/) works, non-free
 
 Installation
 After activating, serveral tables in the database have to be created. The admin-interface of the plugin will try to do this automatically.
@@ -26,12 +33,10 @@ In case of errors, the SQL-statement to create the tables manually are shown in
 
 
 Functuality missing: (a.k.a. "Roadmap")
-- Recurrence of events (this is only supported using the CalDAV-interface; recurring events saved using CalDAV will appear correctly multiple times in the web-based frontend; hovever those events will be read-only at the web-based frondend)
-- Sharing events; all events are private at the moment, therefore this system is not yet a complete replacement for the friendica-native events
+- Sharing events; all events are private at the moment, therefore this system is not a complete replacement for the friendica-native events
 - Attendees / Collaboration
 
 
-
 Used libraries
 
 SabreDAV
@@ -46,10 +51,6 @@ jQueryUI
 http://jqueryui.com/
 Dual-licenced: MIT and GPL licenses
 
-iCalCreator
-http://kigkonsult.se/iCalcreator/
-GNU Lesser General Public License
-
 TimePicker
 http://www.texotela.co.uk/code/jquery/timepicker/
 Dual-licenced: MIT and GPL licenses
index 7ba68c47dea79093d13fb9d64accf27a2d0815c5..3f424d953198259b894af62f370d3e9316f5528b 100644 (file)
@@ -1,31 +1,67 @@
 1.7.0-alpha (2012-??-??)
-    * BC Break: The calendarobjects database table has a bunch of new fields,
-         and a migration script is required to ensure everything will keep
-         working. Read the wiki for more details. 
+    * BC Break: The calendarobjects database table has a bunch of new
+         fields, and a migration script is required to ensure everything will
+         keep working. Read the wiki for more details.
        * BC Break: The iCalendar interface now has a new method: calendarQuery.
        * BC Break: In this version a number of classes have been deleted, that
          have been previously deprecated. Namely:
                - Sabre_DAV_Directory (now: Sabre_DAV_Collection)
                - Sabre_DAV_SimpleDirectory (now: Sabre_DAV_SimpleCollection)
-           - Sabre_VObject_Element_DateTime (now: Sabre_VObject_Property_DateTime)
-               - Sabre_VObject_Element_MultiDateTime (now .._Property_MultiDateTime)
+           - Sabre_VObject_Element_DateTime (now: .._Property_DateTime)
+               - Sabre_VObject_Element_MultiDateTime (-> .._Property_MultiDateTime)
        * BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra
          argument. If you extended this class, you should fix this method. It's
          only used for informational purposes.
-       * Changed: Responsibility for dealing with the calendar-query is now moved
-         from the CalDAV plugin to the CalDAV backends. This allows for heavy
-         optimizations.
-       * Changed: The CalDAV PDO backend is now a lot faster for common calendar
-         queries. 
-       * Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded.
+       * BC Break: The DAV: namespace is no longer converted to urn:DAV. This was
+         a workaround for a bug in older PHP versions (pre-5.3).
+       * Changed: The Sabre_VObject library now spawned into it's own project!
+       * New feature: Support for caldav notifications!
+       * Changed: Responsibility for dealing with the calendar-query is now
+         moved from the CalDAV plugin to the CalDAV backends. This allows for
+         heavy optimizations.
+       * Changed: The CalDAV PDO backend is now a lot faster for common
+         calendar queries.
+       * Fixed: Marking both the text/calendar and text/x-vcard as UTF-8
+         encoded.
        * Fixed: Workaround for the SOGO connector, as it doesn't understand
          receiving "text/x-vcard; charset=utf-8" for a contenttype.
        * Added: Sabre_DAV_Client now throws more specific exceptions in cases
          where we already has an exception class.
-       * Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH
-         method to update parts of a file.
-
-1.6.3-stable (2012-??-??)
+       * Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the
+         PATCH method to update parts of a file.
+       * Added: Tons of timezone name mappings for Microsoft Exchange.
+       * Added: Support for an 'exception' event.
+       * Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!)
+       * Fixed: Rejecting calendar objects if they are not in the
+         supported-calendar-component list. (thanks Armin!)
+       * Fixed: Issue 219: serialize() now reorders correctly.
+       * Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes
+         if there is whitespace in $dom.
+
+1.6.5-stable (2012-??-??)
+       * Fixed: Workaround for line-ending bug OS X 10.8 addressbook has.
+
+1.6.4-stable (2012-08-02)
+       * Fixed: Issue 220: Calendar-query filters may fail when filtering on
+         alarms, if an overridden event has it's alarm removed.
+       * Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler.
+       * Fixed: Issue 222: beforeWriteContent shouldn't be called for lock
+         requests.
+       * Fixed: Problem with POST requests to the outbox if mailto: was not lower
+         cased.
+       * Fixed: Yearly recurrence rule expansion on leap-days no behaves
+         correctly.
+       * Fixed: Correctly checking if recurring, all-day events with no dtstart
+         fall in a timerange if the start of the time-range exceeds the start of
+         the instance of an event, but not the end.
+       * Fixed: All-day recurring events wouldn't match if an occurence ended
+         exactly on the start of a time-range.
+       * Fixed: HTTP basic auth did not correctly deal with passwords containing
+         colons on some servers.
+       * Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the
+         calendar-query REPORT and free-busy calculations.
+
+1.6.3-stable (2012-06-12)
        * Added: It's now possible to specify in Sabre_DAV_Client which type of
          authentication is to be used.
        * Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed.
@@ -43,6 +79,7 @@
          compatibility.
        * Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See
          https://bugs.kde.org/show_bug.cgi?id=300047
+       * Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken.
 
 1.6.2-stable (2012-04-16)
        * Fixed: Sabre_VObject_Node::$parent should have been public.
diff --git a/dav/SabreDAV/README.md b/dav/SabreDAV/README.md
new file mode 100644 (file)
index 0000000..15e8593
--- /dev/null
@@ -0,0 +1,42 @@
+# What is SabreDAV
+
+SabreDAV allows you to easily add WebDAV support to a PHP application. SabreDAV is meant to cover the entire standard, and attempts to allow integration using an easy to understand API.
+
+### Feature list:
+
+* Fully WebDAV compliant
+* Supports Windows XP, Windows Vista, Mac OS/X, DavFSv2, Cadaver, Netdrive, Open Office, and probably more.
+* Passing all Litmus tests.
+* Supporting class 1, 2 and 3 Webdav servers.
+* Locking support.
+* Custom property support.
+* CalDAV (tested with [Evolution](http://code.google.com/p/sabredav/wiki/Evolution), [iCal](http://code.google.com/p/sabredav/wiki/ICal), [iPhone](http://code.google.com/p/sabredav/wiki/IPhone) and [Lightning](http://code.google.com/p/sabredav/wiki/Lightning)).
+* CardDAV (tested with [OS/X addressbook](http://code.google.com/p/sabredav/wiki/OSXAddressbook), the [iOS addressbook](http://code.google.com/p/sabredav/wiki/iOSCardDAV) and [Evolution](http://code.google.com/p/sabredav/wiki/Evolution)).
+* Over 97% unittest code coverage.
+
+### Supported RFC's:
+
+* [RFC2617](http://www.ietf.org/rfc/rfc2617.txt): Basic/Digest auth.
+* [RFC2518](http://www.ietf.org/rfc/rfc2518.txt): First WebDAV spec.
+* [RFC3744](http://www.ietf.org/rfc/rfc3744.txt): ACL (some features missing).
+* [RFC4709](http://www.ietf.org/rfc/rfc4709.txt): [DavMount](http://code.google.com/p/sabredav/wiki/DavMount).
+* [RFC4791](http://www.ietf.org/rfc/rfc4791.txt): CalDAV.
+* [RFC4918](http://www.ietf.org/rfc/rfc4918.txt): WebDAV revision.
+* [RFC5397](http://www.ietf.org/rfc/rfc5689.txt): current-user-principal.
+* [RFC5689](http://www.ietf.org/rfc/rfc5689.txt): Extended MKCOL.
+* [RFC5789](http://tools.ietf.org/html/rfc5789): PATCH method for HTTP.
+* [RFC6352](http://www.ietf.org/rfc/rfc6352.txt): CardDAV
+* [draft-daboo-carddav-directory-gateway](http://tools.ietf.org/html/draft-daboo-carddav-directory-gateway): CardDAV directory gateway
+* CalDAV ctag, CalDAV-proxy.
+
+## Live Demo
+
+### Head over to:
+
+* Url: [http://demo.sabredav.org/public/](http://demo.sabredav.org/public/)
+* Username: testuser
+* Password: test
+
+**Please note:** Due to the webserver stack (nginx with varnish) some clients will not work correctly. At the very least this includes Finder and Cyberduck. Any client using chunked transfer encoding or expect *100-Continue* will fail.
+
+The demo site is kindly hosted by sourceforge, so take it easy with the diskspace. It's limited!
\ No newline at end of file
index 4340013b54ffb012522601d4a3cc4b8815806317..89207877c68a11212479b29b8d90c4c783cfa770 100644 (file)
@@ -95,7 +95,12 @@ foreach($fields17 as $field) {
 if ($found === 0) {
     echo "The database had the 1.6 schema. Table will now be altered.\n";
     echo "This may take some time for large tables\n";
-    $pdo->exec(<<<SQL
+
+    switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
+
+        case 'mysql' :
+
+            $pdo->exec(<<<SQL
 ALTER TABLE calendarobjects 
 ADD etag VARCHAR(32),
 ADD size INT(11) UNSIGNED,
@@ -103,7 +108,20 @@ ADD componenttype VARCHAR(8),
 ADD firstoccurence INT(11) UNSIGNED,
 ADD lastoccurence INT(11) UNSIGNED
 SQL
-);
+        );
+            break;
+            case 'sqlite' :
+                $pdo->exec('ALTER TABLE calendarobjects ADD etag text');
+                $pdo->exec('ALTER TABLE calendarobjects ADD size integer');
+                $pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT');
+                $pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer');
+                $pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer');
+                break;
+
+        default :
+            die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n");
+
+    } 
     echo "Database schema upgraded.\n";
 
 } elseif ($found === 5) {
@@ -205,7 +223,7 @@ function getDenormalizedData($calendarData) {
             }
         } else {
             $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID);
-            $maxDate = new DateTime(self::MAX_DATE);
+            $maxDate = new DateTime(Sabre_CalDAV_Backend_PDO::MAX_DATE);
             if ($it->isInfinite()) {
                 $lastOccurence = $maxDate->getTimeStamp();
             } else {
index 8875829ece07fcb40cbb05e45224b868cc3ba737..1a427e76234e13c68d7f77380b45f0f66d36ad5f 100644 (file)
@@ -1,21 +1,30 @@
 {
-    "name": "evert/sabredav",
+    "name": "sabre/dav",
     "type": "library",
     "description": "WebDAV Framework for PHP",
     "keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"],
     "homepage": "http://code.google.com/p/sabredav/",
-    "license": "New BSD License",
+    "license" : "BSD-3-Clause",
     "authors": [
         {
             "name": "Evert Pot",
             "email": "evert@rooftopsolutions.nl",
-            "homepage" : "http://www.rooftopsolutions.nl/"
+            "homepage" : "http://www.rooftopsolutions.nl/",
+            "role" : "Developer"
         }
     ],
     "require": {
-        "php": ">=5.3.1"
+        "php": ">=5.3.1",
+        "sabre/vobject" : "master-dev"
+    },
+    "provide" : { 
+        "evert/sabredav" : "2.0.0" 
     },
     "autoload": {
         "psr-0": { "Sabre": "lib/" }
+    },
+    "support" : {
+        "forum" : "https://groups.google.com/group/sabredav-discuss",
+        "source" : "https://github.com/evert/sabredav"
     }
 }
diff --git a/dav/SabreDAV/docs/caldav-notifications.txt b/dav/SabreDAV/docs/caldav-notifications.txt
new file mode 100644 (file)
index 0000000..75c2e5e
--- /dev/null
@@ -0,0 +1,1568 @@
+
+
+
+Calendar Server Extension                                       C. Daboo
+                                                              Apple Inc.
+                                                          March 19, 2012
+
+
+                  CalDAV: Calendar User Notifications
+
+Abstract
+
+   This specification defines an extension to CalDAV that allows the
+   server to provide notifications to calendar users.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                           [Page 1]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+Table of Contents
+
+   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
+   2.  Open Issues  . . . . . . . . . . . . . . . . . . . . . . . . .  3
+   3.  Conventions Used in This Document  . . . . . . . . . . . . . .  3
+   4.  Notifications  . . . . . . . . . . . . . . . . . . . . . . . .  3
+     4.1.  Additional Principal Properties  . . . . . . . . . . . . .  4
+       4.1.1.  CS:notification-URL Property . . . . . . . . . . . . .  5
+     4.2.  Properties on Notification Resources . . . . . . . . . . .  5
+       4.2.1.  CS:notificationtype Property . . . . . . . . . . . . .  5
+     4.3.  XML Element Definitions  . . . . . . . . . . . . . . . . .  6
+       4.3.1.  CS:notifications . . . . . . . . . . . . . . . . . . .  6
+       4.3.2.  CS:notification  . . . . . . . . . . . . . . . . . . .  6
+       4.3.3.  CS:dtstamp . . . . . . . . . . . . . . . . . . . . . .  7
+   5.  Notification Definitions . . . . . . . . . . . . . . . . . . .  7
+     5.1.  System Status Notification . . . . . . . . . . . . . . . .  7
+       5.1.1.  CS:systemstatus Element Definition . . . . . . . . . .  8
+     5.2.  Quota Notification . . . . . . . . . . . . . . . . . . . .  8
+       5.2.1.  CS:quotastatus Element Definition  . . . . . . . . . .  9
+     5.3.  Resource Changes Notification  . . . . . . . . . . . . . . 10
+       5.3.1.  CS:resource-change Element Definition  . . . . . . . . 11
+       5.3.2.  CS:calendar-changes Element Definition . . . . . . . . 15
+         5.3.2.1.  Handling Recurrences in CS:calendar-changes  . . . 17
+       5.3.3.  CS:deleted-details Element Definition  . . . . . . . . 18
+       5.3.4.  CS:notify-changes Property . . . . . . . . . . . . . . 20
+   6.  Security Considerations  . . . . . . . . . . . . . . . . . . . 20
+   7.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 21
+   8.  Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 21
+   9.  References . . . . . . . . . . . . . . . . . . . . . . . . . . 21
+     9.1.  Normative References . . . . . . . . . . . . . . . . . . . 21
+     9.2.  Informative References . . . . . . . . . . . . . . . . . . 21
+   Appendix A.  Examples  . . . . . . . . . . . . . . . . . . . . . . 21
+     A.1.  Resource Created . . . . . . . . . . . . . . . . . . . . . 21
+     A.2.  Resource Updated - Property Change . . . . . . . . . . . . 22
+     A.3.  Resource Updated - Parameter Change  . . . . . . . . . . . 23
+     A.4.  Resource Updated - Multiple Instances Change . . . . . . . 23
+     A.5.  Resource Updated - Multiple User Change  . . . . . . . . . 24
+     A.6.  Resource Deleted . . . . . . . . . . . . . . . . . . . . . 25
+     A.7.  Collection Created . . . . . . . . . . . . . . . . . . . . 26
+     A.8.  Collection Updated . . . . . . . . . . . . . . . . . . . . 26
+     A.9.  Collection Deleted . . . . . . . . . . . . . . . . . . . . 27
+   Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 27
+
+
+
+
+
+
+
+
+
+Daboo                                                           [Page 2]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+1.  Introduction
+
+   CalDAV [RFC4791] provides a way for calendar users to store calendar
+   data and exchange this data via scheduling operations.  Based on the
+   WebDAV [RFC4918] protocol, it also includes the ability to manage
+   access to calendar data via the WebDAV ACL [RFC3744] extension.
+
+   It is often useful for servers to communicate arbitrary information
+   to calendar users, e.g., system status, message of the day, quota
+   warnings, changes to shared resources made by others etc.  This
+   specification defines a generic "notification" mechanism that allows
+   a server to do that.  Whilst primarily aimed at CalDAV [RFC4791],
+   this mechanism has been designed to be adaptable to WebDAV [RFC4918].
+
+
+2.  Open Issues
+
+   1.  Define specific child elements for system status notification,
+       e.g. "server-maintenance-period", "server-read-only-period",
+       "client-upgrade-required".
+
+
+3.  Conventions Used in This Document
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in [RFC2119].
+
+   When XML element types in the namespaces "DAV:" and
+   "urn:ietf:params:xml:ns:caldav" are referenced in this document
+   outside of the context of an XML fragment, the string "DAV:" and
+   "CALDAV:" will be prefixed to the element type names respectively.
+
+   The namespace "http://calendarserver.org/ns/" is used for XML
+   elements defined in this specification.  When XML element types in
+   that namespace are referenced in this document outside of the context
+   of an XML fragment, the string "CS:" will be prefixed to the element
+   type names.
+
+
+4.  Notifications
+
+   When this feature is available, a CS:notification-URL (Section 4.1.1)
+   property appears on principal resources for those principals who are
+   able to receive notifications.  That property specifies a single DAV:
+   href element whose content refers to a WebDAV collection resource.
+   Notification "messages" are deposited into this collection and can be
+   retrieved by clients and acted on accordingly.
+
+
+
+Daboo                                                           [Page 3]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   The notification collection referenced by the CS:notification-URL
+   (Section 4.1.1) property MUST have a DAV:resourcetype property with
+   DAV:collection and CS:notifications (Section 4.3.1) child elements.
+
+   Notification "messages" are XML documents stored as resources in the
+   notification collection.  Each XML document contains a CS:
+   notification (Section 4.3.2) element as its root.  The root element
+   contains a CS:dtstamp element, and one additional element which
+   represents the type of notification being conveyed in the message.
+   That child element will typically contain additional content that
+   describes the notification.
+
+   Each notification resource has a CS:notificationtype (Section 4.2.1)
+   property which contains as its single child element an empty element
+   that matches the child element of the notification resource XML
+   document root.  Any attributes on the child element in the XML
+   document are also present in the property child element.
+
+   Notifications are automatically generated by the server (perhaps in
+   response to a action) with an appropriate resource stored in the
+   notifications collection of the user to whom the notification is
+   targeted.  Clients SHOULD monitor the notification collection looking
+   for new notification resources.  When doing so, clients SHOULD look
+   at the CS:notificationtype (Section 4.2.1) property to ensure that
+   the notification is of a type that the client can handle.  Once a
+   client has handled the notification in whatever way is appropriate it
+   SHOULD delete the notification resource.  Clients SHOULD remove
+   notifications being displayed to a user when the notification
+   resource is removed from the notification collection, to enable the
+   user to dismiss a notification on one device and have it
+   automatically removed from others.  Clients MUST ignore all
+   notifications for types they do not recognize.  Servers MAY delete
+   notification resources on their own if they determine that the
+   notifications are no longer relevant or valid.  Servers MAY coalesce
+   notifications as appropriate.
+
+   Servers MUST prevent clients from adding resources in the
+   notification collection.
+
+4.1.  Additional Principal Properties
+
+   This section defines new properties for WebDAV principal resources as
+   defined in RFC3744 [RFC3744].  These properties are likely to be
+   protected but the server MAY allow them to be written by appropriate
+   users.
+
+
+
+
+
+
+Daboo                                                           [Page 4]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+4.1.1.  CS:notification-URL Property
+
+   Name:  notification-URL
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Identify the URL of the notification collection owned by
+      the associated principal resource.
+
+   Protected:  This property SHOULD be protected.
+
+   PROPFIND behavior:  This property SHOULD NOT be returned by a
+      PROPFIND allprop request (as defined in Section 14.2 of
+      [RFC4918]).
+
+   COPY/MOVE behavior:  This property value SHOULD be preserved in COPY
+      and MOVE operations.
+
+   Description:  This property is needed for a client to determine where
+      the notification collection of the current user is located so that
+      processing of notification messages can occur.  If not present,
+      then the associated calendar user is not enabled for notification
+      messages on the server.
+
+   Definition:
+
+   <!ELEMENT notification-URL (DAV:href)>
+
+4.2.  Properties on Notification Resources
+
+   The following new WebDAV properties are defined for notification
+   resources.
+
+4.2.1.  CS:notificationtype Property
+
+   Name:  notificationtype
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Identify the type of notification of the corresponding
+      resource.
+
+   Protected:  This property MUST be protected.
+
+   PROPFIND behavior:  This property SHOULD NOT be returned by a
+      PROPFIND allprop request (as defined in Section 14.2 of
+      [RFC4918]).
+
+
+
+
+Daboo                                                           [Page 5]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   COPY/MOVE behavior:  This property value MUST be preserved in COPY
+      and MOVE operations.
+
+   Description:  This property allows a client, via a PROPFIND Depth:1
+      request, to quickly find notification messages that the client can
+      handle in a notification collection.  The single child element is
+      the notification resource root element's child defining the
+      notification itself.  This element MUST be empty, though any
+      attributes on the element in the notification resource MUST be
+      present in the property element.
+
+   Definition:
+
+   <!ELEMENT notificationtype ANY>
+   <!-- Child elements are empty but will have appropriate attributes.
+        Any valid notification message child element can appear.-->
+
+4.3.  XML Element Definitions
+
+4.3.1.  CS:notifications
+
+   Name:  notifications
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Indicates a notification collection.
+
+   Description:  This XML element is used in a DAV:resourcetype element
+      to indicate that the corresponding resource is a notification
+      collection.
+
+   Definition:
+
+   <!ELEMENT notifications EMPTY>
+
+4.3.2.  CS:notification
+
+   Name:  notification
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Notification message root element.
+
+   Description:  The root element used in notification resources.
+
+
+
+
+
+
+
+Daboo                                                           [Page 6]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   Definition:
+
+   <!ELEMENT notification (dtstamp, XXX) >
+   <!-- Any notification type element can appear after
+        CS:dtstamp -->
+
+4.3.3.  CS:dtstamp
+
+   Name:  dtstamp
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Date-time stamp.
+
+   Description:  Contains the date-time stamp corresponding to the
+      creation of a notification message, using the format defined in
+      [RFC3339], or the "compact" format without "-" and ":" characters
+      between date and time elements, respectively.
+
+   Definition:
+
+   <!ELEMENT dtstamp (#PCDATA)>
+   <!-- Value is a date-time in UTZ as per [RFC3339] with
+        "compact" format allowed.-->
+
+
+5.  Notification Definitions
+
+   This section defines a set of common notification types.
+
+5.1.  System Status Notification
+
+   The system status notification is used to convey a URI and/or textual
+   description to the user.  The assumption is that the URI points to a
+   webpage where current system status is described in detail, with the
+   provided description being a summary of that.  A "type" attribute on
+   the element is used to indicate the importance of the current status
+   notification, and has the values "low", "medium" and "high",
+   representing the increasing level of importance of the message
+   respectively.
+
+   Servers might have knowledge of specific calendar user language
+   preferences, in which case it MAY localise the CS:description value
+   as appropriate based on the calendar user accessing the notification,
+   but if it does, it SHOULD include an xml:lang attribute on the CS:
+   description element to indicate what language is being used.
+
+
+
+
+
+Daboo                                                           [Page 7]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+5.1.1.  CS:systemstatus Element Definition
+
+   Name:  systemstatus
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Indicates a system status notification.
+
+   Description:  This XML element is used in a CS:notification element
+      to describe a system status notification.
+
+   Definition:
+
+   <!ELEMENT systemstatus (DAV:href?, CS:description?)>
+   <!ATTLIST systemstatus type (low | medium | high) "low">
+
+     <!ELEMENT description CDATA>
+
+   <!-- One of DAV:href of CS:description MUST be present -->
+
+   Example:  This is an example of the body of a notification resource
+      for an emergency system outage:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+     <CS:systemstatus type="high">
+       <D:href>http://example.com/emergency_shutdown.html</D:href>
+       <CS:description xml:lang='en_US'
+       >Emergency shutdown now</CS:description>
+     </CS:systemstatus>
+   </CS:notification>
+
+   Example:  This is an example of the WebDAV property on the example
+      notification resource above:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notificationtype xmlns:D="DAV:"
+                        xmlns:CS="http://calendarserver.org/ns/">
+     <CS:systemstatus type="high" />
+   </CS:notificationtype>
+
+5.2.  Quota Notification
+
+   The quota notification is used to convey information about the status
+   of one or more quotas for the user.  The notification contains
+   elements for different types of quota being reported to the user.  In
+
+
+
+Daboo                                                           [Page 8]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   some cases these may be warnings (e.g., a user getting to 80% of
+   their quota limit), or in other cases errors (e.g., a user exceeding
+   their quota).
+
+5.2.1.  CS:quotastatus Element Definition
+
+   Name:  quotastatus
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Indicates a quota status notification.
+
+   Description:  This XML element is used in a CS:notification element
+      to describe a quota status notification.  The CS:quota-percent-
+      used element contains an integer greater than or equal to zero.
+      If the value is greater than or equal to 100, then the user's
+      quota has been reached or exceeded.  The DAV:href element contains
+      a URI for a webpage where the user can go to get further
+      information about their quota status or take corrective action.
+
+   Definition:
+
+   <!ELEMENT quota-status (quota+)>
+
+     <!ELEMENT quota (quota-type, quota-percent-used?,
+                      quota-count?, DAV:href?)>
+     <!ATTLIST quota type (warning | exceeded) "exceeded">
+
+     <!ELEMENT quota-type ANY>
+     <!-- Child elements are application specific -->
+
+     <!ELEMENT quota-percent-used CDATA>
+     <!-- Integer value greater than or equal to zero -->
+
+     <!ELEMENT quota-count CDATA>
+     <!-- Integer value greater than or equal to zero -->
+
+   Example:  This is an example of the body of a notification resource
+      for a quota warning:
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                           [Page 9]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+     <CS:quota-status>
+       <CS:quota type="warning">
+         <CS:quota-type><CS:attachments /></CS:quota-type>
+         <CS:quota-percent-used>80</CS:quota-percent-used>
+         <D:href>https://example.com/your-account.html</D:href>
+       </CS:quota>
+     </CS:quota-status>
+   </CS:notification>
+
+   Example:  This is an example of the body of a notification resource
+      for a quota that has been exceeded, and a count-based limit that
+      is shown as a warning:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+     <CS:quota-status>
+       <CS:quota type="exceeded">
+         <CS:quota-type><CS:attachments /></CS:quota-type>
+         <CS:quota-percent-used>102</CS:quota-percent-used>
+         <D:href>https://example.com/fix-account.html</D:href>
+       </CS:quota>
+       <CS:quota type="warning">
+         <CS:quota-type><CS:events /></CS:quota-type>
+         <CS:quota-percent-used>82</CS:quota-percent-used>
+         <CS:quota-count>4980</CS:quota-count>
+         <D:href>https://example.com/buy-more-space.html</D:href>
+       </CS:quota>
+     </CS:quota-status>
+   </CS:notification>
+
+5.3.  Resource Changes Notification
+
+   The resource change notification is used to inform the user of new,
+   updated or deleted resources caused by changes made by someone else
+   (note: servers MUST NOT generate notifications to users for changes
+   they themselves make, though the possibility of an automated process
+   acting on behalf of a user needs to be considered).  This
+   notification can be used by clients to show changes that a user can
+   acknowledge in their own time.  When the notification is present, it
+   can be displayed on all devices a user is accessing their data from.
+   When the user acknowledges and dismisses the notification on one
+   device, other devices SHOULD also remove the notification when they
+
+
+
+Daboo                                                          [Page 10]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   next synchronize the notification collection.
+
+   A new WebDAV property CS:notify-changes (Section 5.3.4) is defined
+   for calendar collections.  This allows users to enable or disable the
+   sending of resource change notifications for the calendar and its
+   child resources.  Servers MUST allow users to set this property on a
+   per-user basis on any calendars accessible to them.  Servers MUST
+   honor the chosen setting to enable or disable change notifications.
+
+   Servers can send notifications for calendar object resources, and
+   ones for calendar collections.  Servers SHOULD coalesce notifications
+   that refer to the same resource into a single notification resource,
+   containing multiple CS:created, CS:updated or CS:deleted elements all
+   with the same DAV:href child element value.  Servers MAY coalesce
+   changes to multiple resources into a change notification for the
+   parent collection of those resources and use a CS:collection-changes
+   element to indicate the number of individual resources that changed.
+
+5.3.1.  CS:resource-change Element Definition
+
+   Name:  resource-change
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Indicates that resources have been created, updated or
+      deleted.
+
+   Description:  This XML element is used in a CS:notification element
+      to describe a resource change notification.  It can describe a
+      change directly to a calendar object resource or to a calendar
+      collection.
+
+      When used for a calendar object resource change, it can contain
+      one of the CS:created, or CS:deleted elements, or multiple CS:
+      updated elements, which indicate a created, deleted or updated
+      resource, respectively.  The DAV:href element within those
+      elements, contains the URI of the changed resource, optional
+      information about who changed the resource and when that change
+      was made (the CS:changed-by element), and information specific to
+      the nature of the change.  Servers SHOULD coalesce resource change
+      notifications for the same resource into a single notification
+      resource where possible.  The CS:updated element optionally
+      contains CS:content and/or DAV:prop elements to indicate a change
+      to the body of the resource or resource WebDAV properties,
+      respectively.  The DAV:prop element MAY contain a list of property
+      elements to indicate which properties changed.  The CS:updated
+      element can also contain zero or more CS:calendar-changes elements
+      to list details of the changes.  If no CS:calendar-changes element
+
+
+
+Daboo                                                          [Page 11]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+      is present, the specific details are not provided, and clients
+      will need to assume that some set of changes occurred, but the
+      server is unwilling to disclose the full details.  The CS:deleted
+      element can also contain zero or more CS:deleted-details elements
+      to list details of the deleted resource.
+
+      When used for a calendar collection change, it can contain a CS:
+      collection-changes element.  The DAV:href element within that
+      element, contains the URI of the changed calendar collection.  The
+      DAV:prop element indicates a change to WebDAV properties on the
+      calendar collection resource.  The CS:child-created, CS:child-
+      updated, and CS:child-deleted elements each contain a positive
+      integer value indicating how many child resources were added,
+      updated or deleted in the collection, respectively.
+
+   Definition:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 12]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <!ELEMENT resource-change (created | updated+ | deleted |
+                              collection-changes)>
+     <!ELEMENT created (DAV:href, changed-by?, ANY)>
+     <!ELEMENT updated (DAV:href, changed-by?, content?,
+                        DAV:prop?, calendar-changes*)>
+       <!ELEMENT content EMPTY>
+     <!ELEMENT deleted (DAV:href, changed-by?, deleted-details)>
+
+     <!ELEMENT changed-by (common-name | (first-name, last-name),
+                           dtstamp?, DAV:href)>
+       <!ELEMENT common-name CDATA>
+       <!ELEMENT first-name CDATA>
+       <!ELEMENT last-name CDATA>
+     <!-- CS:changed-by indicates who made the change that caused the
+          notification. CS:first-name and CS:last-name are the first
+          and last names of the corresponding user. or the
+          CS:common-name is the overall display name. CS:dtstamp is the
+          time in UTC when the change was made. The DAV:href element
+          is the principal URI or email address of the user who made
+          the change. -->
+
+     <!ELEMENT collection-changes (DAV:href, changed-by*, DAV:prop?,
+                                   child-created?, child-updated?,
+                                   child-deleted?>
+       <!-- When coalescing changes from multiple users, the changed-by
+            element can appear more than once. -->
+
+       <!ELEMENT child-created CDATA>
+       <!ELEMENT child-updated CDATA>
+       <!ELEMENT child-deleted CDATA>
+       <!-- Each of the three elements above MUST contain a positive,
+            non-zero integer value indicate the total number of changes
+            being reported for the collection. -->
+
+   Example:  This is an example of the body of a notification resource
+      for changes where one resource has been created:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 13]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:created>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:common-name>Cyrus Daboo</CS:common-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+       </CS:created>
+     </CS:resource-change>
+   </CS:notification>
+
+   Example:  This is an example of the body of a notification resource
+      for changes where a resource has been updated twice.  One of the
+      updated resources elements contains additional information
+      indicating which recurrence instances in the iCalendar data were
+      changed:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/event.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Oliver</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>mailto:oliver@example.com</D:href>
+         </CS:changed-by>
+       </CS:updated>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/event.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Eleanor</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>mailto:eleanor@example.com</D:href>
+         </CS:changed-by>
+       </CS:updated>
+     </CS:resource-change>
+   </CS:notification>
+
+
+
+
+
+
+
+Daboo                                                          [Page 14]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   Example:  This is an example of the body of a notification resource
+      for changes where one resource has been deleted:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:deleted>
+         <D:href>http://example.com/cyrus/calendar/old.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+       </CS:deleted>
+     </CS:resource-change>
+   </CS:notification>
+
+   Example:  This example is the same as the previous three, except that
+      all the individual resource changes have been coalesced into a
+      single notification about changes to the parent calendar
+      collection:
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:collection-changes>
+         <D:href>http://example.com/cyrus/calendar/</D:href>
+         <CS:child-created>1</CS:child-created>
+         <CS:child-updated>2</CS:child-updated>
+         <CS:child-deleted>1</CS:child-deleted>
+       </CS:collection-changes>
+     </CS:resource-change>
+   </CS:notification>
+
+5.3.2.  CS:calendar-changes Element Definition
+
+   Name:  calendar-changes
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Indicates which portions of an calendar object resource
+      have changed, or provides details of deleted calendar object
+      resources.
+
+
+
+
+Daboo                                                          [Page 15]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   Description:  This XML element is used in a CS:updated element to
+      describe how a calendar object resource changed, or in a CS:
+      deleted element to provide details of a deleted resource.  It can
+      identify the master instance, or individual recurrence instances,
+      and for each indicate which iCalendar properties and parameters
+      changed during the update for which the notification was
+      generated.  For details of handling recurrences please see
+      Section 5.3.2.1.
+
+   Definition:
+
+   <!ELEMENT calendar-changes (recurrence+) >
+
+     <!ELEMENT recurrence
+         ((master | recurrenceid), added?, removed?, changes?)>
+     <!-- Which instances were affected by the change,
+          and details on the per-instance changes -->
+
+       <!ELEMENT master EMPTY>
+       <!-- The "master" instance was affected -->
+
+       <!ELEMENT recurrenceid CDATA>
+       <!-- RECURRENCE-ID value in iCalendar form (in UTC if a
+            non-floating DATE-TIME value) for the affected instance -->
+
+       <!ELEMENT added EMPTY>
+       <!-- The component was added -->
+
+       <!ELEMENT removed EMPTY>
+       <!-- The component was removed -->
+
+       <!ELEMENT changes changed-property*>
+       <!-- Detailed changes in the iCalendar data -->
+
+         <!ELEMENT changed-property changed-parameter*>
+         <!ATTLIST changed-property name PCDATA>
+         <!-- An iCalendar property changed -->
+
+           <!ELEMENT changed-parameter EMPTY>
+           <!ATTLIST changed-parameter name PCDATA>
+           <!-- An iCalendar property parameter changed -->
+
+   Example:  This example indicates that a non-recurring component, or
+      the master component in a recurring component, was changed and
+      that the change was to the "SUMMARY" iCalendar property.
+
+
+
+
+
+
+Daboo                                                          [Page 16]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <CS:calendar-changes xmlns:CS="http://calendarserver.org/ns/">
+     <CS:recurrence>
+       <CS:master/>
+       <CS:changes>
+         <CS:changed-property name="SUMMARY"/>
+       </CS:changes>
+     </CS:recurrence>
+   </CS:calendar-changes>
+
+   Example:  This example indicates that an instance of a recurring
+      component was changed and that the change was to the "DTSTART"
+      iCalendar property.
+
+   <CS:calendar-changes xmlns:CS="http://calendarserver.org/ns/">
+     <CS:recurrence>
+       <CS:recurrenceid>20111215T160000Z</CS:recurrenceid>
+       <CS:changes>
+         <CS:changed-property name="DTSTART"/>
+       </CS:changes>
+     </CS:recurrence>
+   </CS:calendar-changes>
+
+5.3.2.1.  Handling Recurrences in CS:calendar-changes
+
+   Changes to recurring components can be complex.  This section
+   describes the possible set of changes that could occur, and what the
+   CS:calendar-changes element will contain as a result.
+
+   Master exists, unchanged override added  In this case, a CS:
+      recurrence element will be present, containing a CS:recurrence-id
+      element with a value equal to the RECURRENCE-ID property value (in
+      UTC) of the added component.  A CS:added element will be present.
+      There will not be any CS:removed or CS:changes elements.
+
+   Master exists, changed override added  In this case, a CS:recurrence
+      element will be present, containing a CS:recurrence-id element
+      with a value equal to the RECURRENCE-ID property value (in UTC) of
+      the added component.  Both CS:added and CS:changes elements will
+      be present.  There will not be a CS:removed element.
+
+   Master exists, override changed  In this case, a CS:recurrence
+      element will be present, containing a CS:recurrence-id element
+      with a value equal to the RECURRENCE-ID property value (in UTC) of
+      the added component.  A CS:changes element will be present.  There
+      will not be any CS:added or CS:removed elements.
+
+
+
+
+
+
+Daboo                                                          [Page 17]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   Master exists, override removed  In this case, a CS:recurrence
+      element will be present, containing a CS:recurrence-id element
+      with a value equal to the RECURRENCE-ID property value (in UTC) of
+      the added component.  A CS:removed element will be present.  There
+      will not be a CS:added element.  A CS:changes element will only be
+      present if the removed component differs from the "derived" master
+      instance.
+
+   Master exists, override cancelled  In this case, a CS:recurrence
+      element will be present, containing a CS:recurrence-id element
+      with a value equal to the RECURRENCE-ID property value (in UTC) of
+      the added component.  A CS:removed element will be present.  There
+      will not be any CS:added or CS:changes element.  There will also
+      be a CS:master element present, with an appropriate CS:changes
+      element, likely covering a change to "RRULE" or addition of
+      "EXDATE" properties.
+
+   Master does not exist, override added  In this case, a CS:recurrence
+      element will be present, containing a CS:recurrence-id element
+      with a value equal to the RECURRENCE-ID property value (in UTC) of
+      the added component.  A CS:added element will be present.  There
+      will not be a CS:removed or CS:changes element.
+
+   Master does not exist, override changed  In this case, a CS:
+      recurrence element will be present, containing a CS:recurrence-id
+      element with a value equal to the RECURRENCE-ID property value (in
+      UTC) of the added component.  A CS:changes element will be
+      present.  There will not be any CS:added or CS:removed elements.
+
+   Master does not exist, override removed  In this case, a CS:
+      recurrence element will be present, containing a CS:recurrence-id
+      element with a value equal to the RECURRENCE-ID property value (in
+      UTC) of the added component.  A CS:removed element will be
+      present.  There will not be any CS:added or CS:changes element.
+
+5.3.3.  CS:deleted-details Element Definition
+
+   Name:  deleted-details
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Provides summary information about a deleted resource or
+      collection.
+
+   Description:  This XML element is used in a CS:deleted element to
+      describe useful information about a deleted resource or
+      collection, so clients can provide a meaningful notification
+      message to users.  This element has two variants: one for deletion
+
+
+
+Daboo                                                          [Page 18]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+      of a calendar object resource, the other for deletion of a
+      calendar collection.
+
+   Definition:
+
+   <!ELEMENT deleted-details ((deleted-component,
+                               deleted-summary,
+                               deleted-next-instance?,
+                               deleted-had-more-instances?) |
+                              deleted-displayname)>
+   <!-- deleted-displayname is used for a collection delete, the other
+        elements used for a resource delete. -->
+
+     <!ELEMENT deleted-component CDATA>
+     <!-- The main calendar component type of the deleted
+          resource, e.g., "VEVENT", "VTODO" -->
+
+     <!ELEMENT deleted-summary CDATA>
+     <!-- Indicates the "SUMMARY" of the next future instance at the
+          time of deletion, or the previous instance if no future
+          instances existed at the time of deletion. -->
+
+     <!ELEMENT deleted-next-instance CDATA>
+     <!ATTLIST deleted-next-instance tzid PCDATA>
+     <!-- If present, indicates when the next deleted instance would
+          have occurred. For a VEVENT that would be the DTSTART value,
+          for a VTODO that would be either DTSTART or DUE, if present.
+          In each case the value must match the value in the iCalendar
+          data, and any TZID iCalendar property parameter value must
+          be included in the tzid XML element attribute value. -->
+
+     <!ELEMENT deleted-had-more-instances EMPTY>
+     <!-- If present indicates that there was more than one future
+          instances still to occur at the time of deletion. -->
+
+     <!ELEMENT deleted-displayname CDATA>
+     <!-- The DAV:getdisplayname property for the collection that
+          was deleted.  -->
+
+   Example:  This example indicates deletion of a non-recurring event
+      that was yet to occur at the time of deletion.
+
+   <CS:deleted-details xmlns:CS="http://calendarserver.org/ns/">
+     <CS:deleted-component>VEVENT</CS:deleted-component>
+     <CS:deleted-summary>Birthday Party</CS:deleted-summary>
+     <CS:deleted-next-instance tzid="America/New_York
+     >20120505T120000</CS:deleted-next-instance>
+   </CS:deleted-details>
+
+
+
+Daboo                                                          [Page 19]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   Example:  This example indicates deletion of a calendar.
+
+   <CS:deleted-details xmlns:CS="http://calendarserver.org/ns/">
+     <CS:deleted-displayname>Holidays</CS:deleted-displayname>
+   </CS:deleted-details>
+
+5.3.4.  CS:notify-changes Property
+
+   Name:  notify-changes
+
+   Namespace:  http://calendarserver.org/ns/
+
+   Purpose:  Allows a user to specify whether resource change
+      notifications are generated by the server.
+
+   Protected:  This property MUST NOT be protected.
+
+   PROPFIND behavior:  This property SHOULD NOT be returned by a
+      PROPFIND allprop request (as defined in Section 14.2 of
+      [RFC4918]).
+
+   COPY/MOVE behavior:  This property value MUST be preserved in COPY
+      and MOVE operations.
+
+   Description:  This property allows a user to enable or disable the
+      server generation of resource change notifications for the
+      calendar collection, and all its child resources, on which the
+      property resides.  If the property is not present on a calendar
+      collection, the client and server MUST assume that resource change
+      notifications are enabled.
+
+   Definition:
+
+   <!ELEMENT notify-changes (true|false)>
+     <!ELEMENT true EMPTY>
+     <!ELEMENT false EMPTY>
+
+   <!-- true - notifications enabled,
+        false - notifications disabled -->
+
+
+6.  Security Considerations
+
+   Some notification mechanisms might allow a user to trigger a
+   notification to be delivered to other users (e.g., an invitation to
+   share a calendar).  In such cases servers MUST ensure that suitable
+   limits are placed on the number and frequency of such user generated
+   notifications.
+
+
+
+Daboo                                                          [Page 20]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   TBD: More?
+
+
+7.  IANA Considerations
+
+   This document does not require any actions on the part of IANA.
+
+
+8.  Acknowledgments
+
+   This specification is the result of discussions between the various
+   Apple calendar server and client teams.
+
+
+9.  References
+
+9.1.  Normative References
+
+   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
+              Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [RFC3339]  Klyne, G., Ed. and C. Newman, "Date and Time on the
+              Internet: Timestamps", RFC 3339, July 2002.
+
+   [RFC4918]  Dusseault, L., "HTTP Extensions for Web Distributed
+              Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
+
+9.2.  Informative References
+
+   [RFC3744]  Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
+              Distributed Authoring and Versioning (WebDAV)
+              Access Control Protocol", RFC 3744, May 2004.
+
+   [RFC4791]  Daboo, C., Desruisseaux, B., and L. Dusseault,
+              "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
+              March 2007.
+
+
+Appendix A.  Examples
+
+   This section provides more detailed examples of resource change
+   notifications for illustrative purposes only.
+
+A.1.  Resource Created
+
+   This is an example of the body of a notification resource where one
+   resource has been created.
+
+
+
+
+Daboo                                                          [Page 21]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:created>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+       </CS:created>
+     </CS:resource-change>
+   </CS:notification>
+
+A.2.  Resource Updated - Property Change
+
+   This is an example of the body of a notification resource where one
+   non-recurring event has had its "DTSTART" and "SUMMARY" iCalendar
+   property values changed.
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:calendar-changes>
+           <CS:recurrence>
+             <CS:master/>
+             <CS:changes>
+               <CS:changed-property name="DTSTART"/>
+               <CS:changed-property name="SUMMARY"/>
+             </CS:changes>
+           </CS:recurrence>
+         </CS:calendar-changes>
+       </CS:updated>
+     </CS:resource-change>
+   </CS:notification>
+
+
+
+
+
+Daboo                                                          [Page 22]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+A.3.  Resource Updated - Parameter Change
+
+   This is an example of the body of a notification resource where one
+   non-recurring event has had the "PARTSTAT" iCalendar property
+   parameter on an "ATTENDEE" property changed, and a "TRANSP" property
+   added.
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:calendar-changes>
+           <CS:recurrence>
+             <CS:master/>
+             <CS:added>
+               <CS:changed-property name="TRANSP"/>
+             </CS:added>
+             <CS:changes>
+               <CS:changed-property name="ATTENDEE">
+                 <CS:changed-parameter name="PARTSTAT"/>
+               </CS:changed-property>
+             </CS:changes>
+           </CS:recurrence>
+         </CS:calendar-changes>
+       </CS:updated>
+     </CS:resource-change>
+   </CS:notification>
+
+A.4.  Resource Updated - Multiple Instances Change
+
+   This is an example of the body of a notification resource where two
+   instances of a recurring event have their "DTSTART" and "SUMMARY"
+   iCalendar property values changed.
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 23]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:calendar-changes>
+           <CS:recurrence>
+             <CS:recurrenceid>20120209T170000Z</CS:recurrenceid>
+             <CS:changes>
+               <CS:changed-property name="DTSTART"/>
+               <CS:changed-property name="SUMMARY"/>
+             </CS:changes>
+           </CS:recurrence>
+           <CS:recurrence>
+             <CS:recurrenceid>20120210T170000Z</CS:recurrenceid>
+             <CS:changes>
+               <CS:changed-property name="DTSTART"/>
+               <CS:changed-property name="SUMMARY"/>
+             </CS:changes>
+           </CS:recurrence>
+         </CS:calendar-changes>
+       </CS:updated>
+     </CS:resource-change>
+   </CS:notification>
+
+A.5.  Resource Updated - Multiple User Change
+
+   This is an example of the body of a notification resource where two
+   instances of a recurring event have their "DTSTART" and "SUMMARY"
+   iCalendar property values changed.  Each instance was changed by a
+   different user.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 24]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:calendar-changes>
+           <CS:recurrence>
+             <CS:recurrenceid>20120209T170000Z</CS:recurrenceid>
+             <CS:changes>
+               <CS:changed-property name="DTSTART"/>
+               <CS:changed-property name="SUMMARY"/>
+             </CS:changes>
+           </CS:recurrence>
+         </CS:calendar-changes>
+       </CS:updated>
+       <CS:updated>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Eric</CS:first-name>
+           <CS:last-name>York</CS:last-name>
+           <D:href>/principals/ericyork</D:href>
+         </CS:changed-by>
+         <CS:calendar-changes>
+           <CS:recurrence>
+             <CS:recurrenceid>20120210T170000Z</CS:recurrenceid>
+             <CS:changes>
+               <CS:changed-property name="DTSTART"/>
+               <CS:changed-property name="SUMMARY"/>
+             </CS:changes>
+           </CS:recurrence>
+         </CS:calendar-changes>
+       </CS:updated>
+     </CS:resource-change>
+   </CS:notification>
+
+A.6.  Resource Deleted
+
+   This is an example of the body of a notification resource where one
+   resource has been deleted.  The resource was a VEVENT whose next
+   occurrence was in the future on 20120210T170000Z.
+
+
+
+
+Daboo                                                          [Page 25]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:deleted>
+         <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:deleted-details>
+           <CS:deleted-component>VEVENT</CS:deleted-component>
+           <CS:deleted-summary>CalDAV Meeting</CS:deleted-summary>
+           <CS:deleted-next-instance
+           >20120210T170000Z</CS:deleted-next-instance>
+         </CS:deleted-details>
+       </CS:deleted>
+     </CS:resource-change>
+   </CS:notification>
+
+A.7.  Collection Created
+
+   This is an example of the body of a notification resource where a
+   calendar collection has been created.
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:created>
+         <D:href>http://example.com/cyrus/new-calendar/</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+       </CS:created>
+     </CS:resource-change>
+   </CS:notification>
+
+A.8.  Collection Updated
+
+   This is an example of the body of a notification resource where
+   coalesced changes in a calendar collection are shown.  In this case 1
+   child resource was created, 2 updated, and 1 deleted.
+
+
+
+Daboo                                                          [Page 26]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:collection-changes>
+         <D:href>http://example.com/cyrus/calendar/</D:href>
+         <CS:child-created>1</CS:child-created>
+         <CS:child-updated>2</CS:child-updated>
+         <CS:child-deleted>1</CS:child-deleted>
+       </CS:collection-changes>
+     </CS:resource-change>
+   </CS:notification>
+
+A.9.  Collection Deleted
+
+   This is an example of the body of a notification resource where a
+   calendar collection has been deleted.
+
+   <?xml version="1.0" encoding="UTF-8"?>
+   <CS:notification xmlns:D="DAV:"
+                    xmlns:CS="http://calendarserver.org/ns/">
+     <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+     <CS:resource-change>
+       <CS:deleted>
+         <D:href>http://example.com/cyrus/old-calendar/</D:href>
+         <CS:changed-by>
+           <CS:first-name>Cyrus</CS:first-name>
+           <CS:last-name>Daboo</CS:last-name>
+           <D:href>/principals/cyrusdaboo</D:href>
+         </CS:changed-by>
+         <CS:deleted-details>
+           <CS:deleted-displayname>Holidays</CS:deleted-displayname>
+         </CS:deleted-details>
+       </CS:deleted>
+     </CS:resource-change>
+   </CS:notification>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 27]
+\f
+                        CalDAV User Notifications             March 2012
+
+
+Author's Address
+
+   Cyrus Daboo
+   Apple Inc.
+   1 Infinite Loop
+   Cupertino, CA  95014
+   USA
+
+   Email: cyrus@daboo.name
+   URI:   http://www.apple.com/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo                                                          [Page 28]
+\f
diff --git a/dav/SabreDAV/docs/caldav-sharing-02.txt b/dav/SabreDAV/docs/caldav-sharing-02.txt
new file mode 100644 (file)
index 0000000..7850e0a
--- /dev/null
@@ -0,0 +1,224 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  
+  
+
+  
+
+
+  
+
+  <head>
+    <title>
+      /CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt â€“ Calendar and Contacts Server
+    </title>
+        <link rel="search" href="/search" />
+        <link rel="help" href="/wiki/TracGuide" />
+        <link rel="alternate" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?format=txt" type="text/plain" title="Plain Text" /><link rel="alternate" href="/export/9403/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" type="text/plain; charset=iso-8859-15" title="Original Format" />
+        <link rel="up" href="/browser/CalendarServer/trunk/doc/Extensions" title="Parent directory" />
+        <link rel="start" href="/wiki" />
+        <link rel="stylesheet" href="/chrome/common/css/trac.css" type="text/css" /><link rel="stylesheet" href="/chrome/common/css/code.css" type="text/css" /><link rel="stylesheet" href="/pygments/colorful.css" type="text/css" /><link rel="stylesheet" href="/chrome/common/css/browser.css" type="text/css" />
+        <link rel="shortcut icon" href="/chrome/common/trac.ico" type="image/x-icon" />
+        <link rel="icon" href="/chrome/common/trac.ico" type="image/x-icon" />
+      <link type="application/opensearchdescription+xml" rel="search" href="/search/opensearch" title="Search Calendar and Contacts Server" />
+    <script type="text/javascript" src="/chrome/common/js/jquery.js"></script><script type="text/javascript" src="/chrome/common/js/trac.js"></script><script type="text/javascript" src="/chrome/common/js/search.js"></script>
+    <!--[if lt IE 7]>
+    <script type="text/javascript" src="/chrome/common/js/ie_pre7_hacks.js"></script>
+    <![endif]-->
+    <script type="text/javascript">
+      jQuery(document).ready(function($) {
+        $(".trac-toggledeleted").show().click(function() {
+                  $(this).siblings().find(".trac-deleted").toggle();
+                  return false;
+        }).click();
+        $("#jumploc input").hide();
+        $("#jumploc select").change(function () {
+          this.parentNode.parentNode.submit();
+        });
+      });
+    </script>
+  <link rel="stylesheet" type="text/css" href="/static/css/style_v4.css" />
+ </head>
+  <body>
+ <div id="forge-body">
+  <div id="forge-header">
+   <div id="forge-logo">
+     <a href="http://www.macosforge.org/"><img alt="macosforge logo" src="https://static2.macosforge.org/static/images/logo_v2.png" /></a>
+   </div>
+   <div id="forge-project">
+        <a id="forge-project-logo" href="http://calendarserver.org/">
+         <img alt="project logo" src="http://static2.macosforge.org/files/logos/CalendarServer.png" />
+       </a>
+        <a id="forge-project-name" href="http://calendarserver.org/">
+         Calendar and Contacts Server
+        </a>
+   </div>
+   <div id="auth-nav">
+      <a href="/auth/register/">Register</a>
+      <a href="/auth/password/lost/">Lost Password</a>
+      <a href="/auth/login/">Login</a>
+   </div>
+  </div>
+  <div id="forge-outter">
+   <div id="left-nav">
+ <div class="heading" id="projects-list-heading">Projects</div>
+<div id="project-list" class="project-list">
+ <ul>
+   <li>
+    <a class="navlink-item" href="http://alac.macosforge.org/">Apple Lossless Audio Codec</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://calendarserver.org/">Calendar and Contacts Server</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://www.dcerpc.org/">DCERPC</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://dss.macosforge.org/">Darwin Streaming Server</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://darwinbuild.macosforge.org/">DarwinBuild</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://fstools.macosforge.org/">FS Tools</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://www.macports.org/">MacPorts</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://smartcardservices.macosforge.org/">SmartCard Services</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://www.webkit.org/">WebKit</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://xquartz.macosforge.org/">XQuartz</a>
+   </li>
+   <li>
+    <a class="navlink-item" href="http://libdispatch.macosforge.org/">libdispatch</a>
+   </li>
+ </ul>
+</div>
+     <div id="forge-footer">
+      <div id="footerlinks">
+       <a href="http://www.macosforge.org/contact/">Contact</a><br />
+       <a href="http://www.macosforge.org/terms/">Terms of Use</a><br />
+       <a href="http://www.apple.com/legal/privacy/">Privacy Policy</a><br />
+      </div>
+      <div id="footertext">
+       <br />
+       All user-submitted text and content on this website is licensed under a
+       <a href="http://creativecommons.org/licenses/by/2.5/">
+        Creative Commons Attribution 2.5 License
+       </a>
+       unless otherwise noted.
+       Copyright Â© 2010 Apple Inc.  All rights reserved.
+      </div>
+     </div>
+   </div>
+   <div id="forge-inner">
+    <div id="top-nav">
+   <a href="/wiki">Wiki</a>
+    Â  Â 
+   <a href="/timeline">Timeline</a>
+    Â  Â 
+   <a href="/roadmap">Roadmap</a>
+    Â  Â 
+   <a href="/browser">Browse Source</a>
+    Â  Â 
+   <a href="/report">View Tickets</a>
+    Â  Â 
+   <a href="/newticket">New Ticket</a>
+    Â  Â 
+   <a href="/search">Search</a>
+    </div>
+    <div id="forge-content">
+    <div id="banner">
+      <div id="header">
+        <a id="logo" href="/"><img src="/chrome/site/icon.png" alt="Calendar Server" height="100" width="100" /></a>
+      </div>
+      <form id="search" action="/search" method="get">
+        <div>
+          <label for="proj-search">Search:</label>
+          <input type="text" id="proj-search" name="q" size="18" value="" />
+          <input type="submit" value="Search" />
+        </div>
+      </form>
+      <div id="metanav" class="nav">
+  </div>
+    </div>
+    <div id="main">
+      <div id="ctxtnav" class="nav">
+        <h2>Context Navigation</h2>
+          <ul>
+              <li class="first"><a href="/changeset/6401/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Last Change</a></li><li><a href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?annotate=blame&amp;rev=6401" title="Annotate each line with the last changed revision (this can be time consuming...)">Annotate</a></li><li class="last"><a href="/log/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Revision Log</a></li>
+          </ul>
+        <hr />
+      </div>
+    <div id="content" class="browser">
+      <h1>
+    <a class="pathentry first" title="Go to root directory" href="/browser">root</a><span class="pathentry sep">/</span><a class="pathentry" title="View CalendarServer" href="/browser/CalendarServer">CalendarServer</a><span class="pathentry sep">/</span><a class="pathentry" title="View trunk" href="/browser/CalendarServer/trunk">trunk</a><span class="pathentry sep">/</span><a class="pathentry" title="View doc" href="/browser/CalendarServer/trunk/doc">doc</a><span class="pathentry sep">/</span><a class="pathentry" title="View Extensions" href="/browser/CalendarServer/trunk/doc/Extensions">Extensions</a><span class="pathentry sep">/</span><a class="pathentry" title="View caldav-sharing-02.txt" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">caldav-sharing-02.txt</a>
+    <br style="clear: both" />
+  </h1>
+      <div id="jumprev">
+        <form action="" method="get">
+          <div>
+            <label for="rev">
+              View revision:</label>
+            <input type="text" id="rev" name="rev" size="6" />
+          </div>
+        </form>
+      </div>
+      <table id="info" summary="Revision info">
+        <tr>
+          <th scope="col">
+            Revision <a href="/changeset/6401">6401</a>, <span title="54302 bytes">53.0 KB</span>
+            (checked in by cdaboo@…, <a class="timeline" href="/timeline?from=2010-10-06T07%3A54%3A17-0700&amp;precision=second" title="2010-10-06T07:54:17-0700 in Timeline">21 months</a> ago)
+          </th>
+        </tr>
+        <tr>
+          <td class="message searchable">
+              <p>
+Sharing extension specification.<br />
+</p>
+          </td>
+        </tr>
+      </table>
+      <div id="preview" class="searchable">
+    <table class="code"><thead><tr><th class="lineno" title="Line numbers">Line</th><th class="content"> </th></tr></thead><tbody><tr><th id="L1"><a href="#L1">1</a></th><td></td></tr><tr><th id="L2"><a href="#L2">2</a></th><td></td></tr><tr><th id="L3"><a href="#L3">3</a></th><td></td></tr><tr><th id="L4"><a href="#L4">4</a></th><td>Calendar Server Extension  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â C. Daboo</td></tr><tr><th id="L5"><a href="#L5">5</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â E. York</td></tr><tr><th id="L6"><a href="#L6">6</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Apple Inc.</td></tr><tr><th id="L7"><a href="#L7">7</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â October 6, 2010</td></tr><tr><th id="L8"><a href="#L8">8</a></th><td></td></tr><tr><th id="L9"><a href="#L9">9</a></th><td></td></tr><tr><th id="L10"><a href="#L10">10</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Shared and Published Calendars in CalDAV</td></tr><tr><th id="L11"><a href="#L11">11</a></th><td></td></tr><tr><th id="L12"><a href="#L12">12</a></th><td>Abstract</td></tr><tr><th id="L13"><a href="#L13">13</a></th><td></td></tr><tr><th id="L14"><a href="#L14">14</a></th><td>  Â This specification defines an extension to CalDAV that enables the</td></tr><tr><th id="L15"><a href="#L15">15</a></th><td>  Â sharing of calendars between users on a CalDAV server.</td></tr><tr><th id="L16"><a href="#L16">16</a></th><td></td></tr><tr><th id="L17"><a href="#L17">17</a></th><td></td></tr><tr><th id="L18"><a href="#L18">18</a></th><td>Table of Contents</td></tr><tr><th id="L19"><a href="#L19">19</a></th><td></td></tr><tr><th id="L20"><a href="#L20">20</a></th><td>  Â 1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3</td></tr><tr><th id="L21"><a href="#L21">21</a></th><td>  Â 2.  Conventions Used in This Document  . . . . . . . . . . . . . .  3</td></tr><tr><th id="L22"><a href="#L22">22</a></th><td>  Â 3.  Overview . . . . . . . . . . . . . . . . . . . . . . . . . . .  4</td></tr><tr><th id="L23"><a href="#L23">23</a></th><td>  Â 4.  Notifications  . . . . . . . . . . . . . . . . . . . . . . . .  5</td></tr><tr><th id="L24"><a href="#L24">24</a></th><td>  Â  Â 4.1.  Additional Principal Properties  . . . . . . . . . . . . .  5</td></tr><tr><th id="L25"><a href="#L25">25</a></th><td>  Â  Â  Â 4.1.1.  CS:notification-URL Property . . . . . . . . . . . . .  6</td></tr><tr><th id="L26"><a href="#L26">26</a></th><td>  Â  Â 4.2.  Properties on Notification Resources . . . . . . . . . . .  6</td></tr><tr><th id="L27"><a href="#L27">27</a></th><td>  Â  Â  Â 4.2.1.  CS:notificationtype Property . . . . . . . . . . . . .  6</td></tr><tr><th id="L28"><a href="#L28">28</a></th><td>  Â 5.  Shared Calendaring . . . . . . . . . . . . . . . . . . . . . .  7</td></tr><tr><th id="L29"><a href="#L29">29</a></th><td>  Â  Â 5.1.  Feature Discovery  . . . . . . . . . . . . . . . . . . . .  7</td></tr><tr><th id="L30"><a href="#L30">30</a></th><td>  Â  Â 5.2.  Additional Properties for Calendars  . . . . . . . . . . .  7</td></tr><tr><th id="L31"><a href="#L31">31</a></th><td>  Â  Â  Â 5.2.1.  DAV:resourcetype Property  . . . . . . . . . . . . . .  7</td></tr><tr><th id="L32"><a href="#L32">32</a></th><td>  Â  Â  Â 5.2.2.  CS:invite Property . . . . . . . . . . . . . . . . . .  8</td></tr><tr><th id="L33"><a href="#L33">33</a></th><td>  Â  Â  Â 5.2.3.  CS:allowed-sharing-modes Property  . . . . . . . . . .  8</td></tr><tr><th id="L34"><a href="#L34">34</a></th><td>  Â  Â  Â 5.2.4.  CS:shared-url Property . . . . . . . . . . . . . . . .  9</td></tr><tr><th id="L35"><a href="#L35">35</a></th><td>  Â  Â 5.3.  Sharer Actions on Shared Calendars . . . . . . . . . . . .  9</td></tr><tr><th id="L36"><a href="#L36">36</a></th><td>  Â  Â  Â 5.3.1.  Creating a Shared Calendar . . . . . . . . . . . . . .  9</td></tr><tr><th id="L37"><a href="#L37">37</a></th><td>  Â  Â  Â  Â 5.3.1.1.  Example: Successful MKCALENDAR Request . . . . . . 10</td></tr><tr><th id="L38"><a href="#L38">38</a></th><td>  Â  Â  Â  Â 5.3.1.2.  Example: Successful Extended MKCOL Request . . . . 10</td></tr><tr><th id="L39"><a href="#L39">39</a></th><td>  Â  Â  Â 5.3.2.  Sharing an Existing Calendar . . . . . . . . . . . . . 11</td></tr><tr><th id="L40"><a href="#L40">40</a></th><td>  Â  Â  Â  Â 5.3.2.1.  Example: Successful PROPPATCH Request  . . . . . . 11</td></tr><tr><th id="L41"><a href="#L41">41</a></th><td>  Â  Â  Â 5.3.3.  Manipulating Sharees of a Shared Calendar  . . . . . . 12</td></tr><tr><th id="L42"><a href="#L42">42</a></th><td>  Â  Â  Â  Â 5.3.3.1.  Example: Successful Sharee Add Request . . . . . . 13</td></tr><tr><th id="L43"><a href="#L43">43</a></th><td>  Â  Â  Â  Â 5.3.3.2.  Example: Successful Multiple Sharee Change</td></tr><tr><th id="L44"><a href="#L44">44</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â Request  . . . . . . . . . . . . . . . . . . . . . 14</td></tr><tr><th id="L45"><a href="#L45">45</a></th><td>  Â  Â 5.4.  Sharee Actions on Shared Calendars . . . . . . . . . . . . 15</td></tr><tr><th id="L46"><a href="#L46">46</a></th><td>  Â  Â  Â 5.4.1.  Replying to a Sharing Invite . . . . . . . . . . . . . 15</td></tr><tr><th id="L47"><a href="#L47">47</a></th><td>  Â  Â  Â 5.4.2.  Removing a Shared Calendar . . . . . . . . . . . . . . 16</td></tr><tr><th id="L48"><a href="#L48">48</a></th><td>  Â  Â 5.5.  General Considerations . . . . . . . . . . . . . . . . . . 16</td></tr><tr><th id="L49"><a href="#L49">49</a></th><td>  Â  Â  Â 5.5.1.  Access Levels  . . . . . . . . . . . . . . . . . . . . 16</td></tr><tr><th id="L50"><a href="#L50">50</a></th><td>  Â  Â  Â 5.5.2.  Allowing or Disallowing Sharing  . . . . . . . . . . . 16</td></tr><tr><th id="L51"><a href="#L51">51</a></th><td>  Â  Â  Â 5.5.3.  Per-user WebDAV Properties . . . . . . . . . . . . . . 17</td></tr><tr><th id="L52"><a href="#L52">52</a></th><td></td></tr><tr><th id="L53"><a href="#L53">53</a></th><td></td></tr><tr><th id="L54"><a href="#L54">54</a></th><td></td></tr><tr><th id="L55"><a href="#L55">55</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 1]</td></tr><tr><th id="L56"><a href="#L56">56</a></th><td></td></tr><tr><th id="L57"><a href="#L57">57</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L58"><a href="#L58">58</a></th><td></td></tr><tr><th id="L59"><a href="#L59">59</a></th><td></td></tr><tr><th id="L60"><a href="#L60">60</a></th><td>  Â  Â  Â 5.5.4.  Per-user Calendar Data . . . . . . . . . . . . . . . . 17</td></tr><tr><th id="L61"><a href="#L61">61</a></th><td>  Â  Â  Â 5.5.5.  Scheduling . . . . . . . . . . . . . . . . . . . . . . 18</td></tr><tr><th id="L62"><a href="#L62">62</a></th><td>  Â 6.  XML Element Definitions  . . . . . . . . . . . . . . . . . . . 19</td></tr><tr><th id="L63"><a href="#L63">63</a></th><td>  Â  Â 6.1.  CS:shared-owner  . . . . . . . . . . . . . . . . . . . . . 19</td></tr><tr><th id="L64"><a href="#L64">64</a></th><td>  Â  Â 6.2.  CS:shared  . . . . . . . . . . . . . . . . . . . . . . . . 20</td></tr><tr><th id="L65"><a href="#L65">65</a></th><td>  Â  Â 6.3.  CS:can-be-shared . . . . . . . . . . . . . . . . . . . . . 20</td></tr><tr><th id="L66"><a href="#L66">66</a></th><td>  Â  Â 6.4.  CS:can-be-published  . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L67"><a href="#L67">67</a></th><td>  Â  Â 6.5.  CS:user  . . . . . . . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L68"><a href="#L68">68</a></th><td>  Â  Â 6.6.  CS:invite-noresponse . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L69"><a href="#L69">69</a></th><td>  Â  Â 6.7.  CS:invite-deleted  . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L70"><a href="#L70">70</a></th><td>  Â  Â 6.8.  CS:invite-accepted . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L71"><a href="#L71">71</a></th><td>  Â  Â 6.9.  CS:invite-declined . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L72"><a href="#L72">72</a></th><td>  Â  Â 6.10. CS:invite-invalid  . . . . . . . . . . . . . . . . . . . . 23</td></tr><tr><th id="L73"><a href="#L73">73</a></th><td>  Â  Â 6.11. CS:access  . . . . . . . . . . . . . . . . . . . . . . . . 23</td></tr><tr><th id="L74"><a href="#L74">74</a></th><td>  Â  Â 6.12. CS:read  . . . . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L75"><a href="#L75">75</a></th><td>  Â  Â 6.13. CS:read-write  . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L76"><a href="#L76">76</a></th><td>  Â  Â 6.14. CS:summary . . . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L77"><a href="#L77">77</a></th><td>  Â  Â 6.15. CS:invite-notification . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L78"><a href="#L78">78</a></th><td>  Â  Â 6.16. CS:uid . . . . . . . . . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L79"><a href="#L79">79</a></th><td>  Â  Â 6.17. CS:hosturl . . . . . . . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L80"><a href="#L80">80</a></th><td>  Â  Â 6.18. CS:organizer . . . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L81"><a href="#L81">81</a></th><td>  Â  Â 6.19. CS:common-name . . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L82"><a href="#L82">82</a></th><td>  Â  Â 6.20. CS:invite-reply  . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L83"><a href="#L83">83</a></th><td>  Â  Â 6.21. CS:in-reply-to . . . . . . . . . . . . . . . . . . . . . . 27</td></tr><tr><th id="L84"><a href="#L84">84</a></th><td>  Â  Â 6.22. CS:notification  . . . . . . . . . . . . . . . . . . . . . 27</td></tr><tr><th id="L85"><a href="#L85">85</a></th><td>  Â  Â 6.23. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L86"><a href="#L86">86</a></th><td>  Â  Â 6.24. CS:share . . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L87"><a href="#L87">87</a></th><td>  Â  Â 6.25. CS:set . . . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L88"><a href="#L88">88</a></th><td>  Â  Â 6.26. CS:remove  . . . . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L89"><a href="#L89">89</a></th><td>  Â  Â 6.27. CS:shared-as . . . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L90"><a href="#L90">90</a></th><td>  Â 7.  Security Considerations  . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L91"><a href="#L91">91</a></th><td>  Â 8.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L92"><a href="#L92">92</a></th><td>  Â 9.  Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L93"><a href="#L93">93</a></th><td>  Â 10. Normative References . . . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L94"><a href="#L94">94</a></th><td>  Â Appendix A.  Change History  . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L95"><a href="#L95">95</a></th><td>  Â Appendix B.  Change History  . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L96"><a href="#L96">96</a></th><td>  Â Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 31</td></tr><tr><th id="L97"><a href="#L97">97</a></th><td></td></tr><tr><th id="L98"><a href="#L98">98</a></th><td></td></tr><tr><th id="L99"><a href="#L99">99</a></th><td></td></tr><tr><th id="L100"><a href="#L100">100</a></th><td></td></tr><tr><th id="L101"><a href="#L101">101</a></th><td></td></tr><tr><th id="L102"><a href="#L102">102</a></th><td></td></tr><tr><th id="L103"><a href="#L103">103</a></th><td></td></tr><tr><th id="L104"><a href="#L104">104</a></th><td></td></tr><tr><th id="L105"><a href="#L105">105</a></th><td></td></tr><tr><th id="L106"><a href="#L106">106</a></th><td></td></tr><tr><th id="L107"><a href="#L107">107</a></th><td></td></tr><tr><th id="L108"><a href="#L108">108</a></th><td></td></tr><tr><th id="L109"><a href="#L109">109</a></th><td></td></tr><tr><th id="L110"><a href="#L110">110</a></th><td></td></tr><tr><th id="L111"><a href="#L111">111</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 2]</td></tr><tr><th id="L112"><a href="#L112">112</a></th><td></td></tr><tr><th id="L113"><a href="#L113">113</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L114"><a href="#L114">114</a></th><td></td></tr><tr><th id="L115"><a href="#L115">115</a></th><td></td></tr><tr><th id="L116"><a href="#L116">116</a></th><td>1.  Introduction</td></tr><tr><th id="L117"><a href="#L117">117</a></th><td></td></tr><tr><th id="L118"><a href="#L118">118</a></th><td>  Â CalDAV [RFC4791] provides a way for calendar users to store calendar</td></tr><tr><th id="L119"><a href="#L119">119</a></th><td>  Â data and exchange this data via scheduling operations.  Based on the</td></tr><tr><th id="L120"><a href="#L120">120</a></th><td>  Â WebDAV [RFC4918] protocol, it also includes the ability to manage</td></tr><tr><th id="L121"><a href="#L121">121</a></th><td>  Â access to calendar data via the WebDAV ACL [RFC3744] extension.</td></tr><tr><th id="L122"><a href="#L122">122</a></th><td></td></tr><tr><th id="L123"><a href="#L123">123</a></th><td>  Â WebDAV ACL [RFC3744] provides a way to manage fine-grained access</td></tr><tr><th id="L124"><a href="#L124">124</a></th><td>  Â controls on WebDAV resources.  Whilst this could be used directly to</td></tr><tr><th id="L125"><a href="#L125">125</a></th><td>  Â manage sharing of calendars, experience has shown that client</td></tr><tr><th id="L126"><a href="#L126">126</a></th><td>  Â developers are averse to using it due to its complexity.  Instead a</td></tr><tr><th id="L127"><a href="#L127">127</a></th><td>  Â simpler process for sharing calendars is preferred.</td></tr><tr><th id="L128"><a href="#L128">128</a></th><td></td></tr><tr><th id="L129"><a href="#L129">129</a></th><td>  Â This extension defines a way for individual calendar users to share</td></tr><tr><th id="L130"><a href="#L130">130</a></th><td>  Â calendars with other users.  This is done via an "opt-in" process in</td></tr><tr><th id="L131"><a href="#L131">131</a></th><td>  Â which a sharing invite is sent from the sharer to a sharee, allowing</td></tr><tr><th id="L132"><a href="#L132">132</a></th><td>  Â the sharee to accept or decline.  If the sharee accepts the sharing</td></tr><tr><th id="L133"><a href="#L133">133</a></th><td>  Â invite, the shared calendar is made available to them in their own</td></tr><tr><th id="L134"><a href="#L134">134</a></th><td>  Â calendar home collection (i.e., alongside their own personal</td></tr><tr><th id="L135"><a href="#L135">135</a></th><td>  Â calendars).  HTTP POST operations are used to manage the sharing</td></tr><tr><th id="L136"><a href="#L136">136</a></th><td>  Â invitations and replies, and WebDAV properties are used to expose the</td></tr><tr><th id="L137"><a href="#L137">137</a></th><td>  Â state of shared calendars.</td></tr><tr><th id="L138"><a href="#L138">138</a></th><td></td></tr><tr><th id="L139"><a href="#L139">139</a></th><td></td></tr><tr><th id="L140"><a href="#L140">140</a></th><td>2.  Conventions Used in This Document</td></tr><tr><th id="L141"><a href="#L141">141</a></th><td></td></tr><tr><th id="L142"><a href="#L142">142</a></th><td>  Â The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",</td></tr><tr><th id="L143"><a href="#L143">143</a></th><td>  Â "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this</td></tr><tr><th id="L144"><a href="#L144">144</a></th><td>  Â document are to be interpreted as described in [RFC2119].</td></tr><tr><th id="L145"><a href="#L145">145</a></th><td></td></tr><tr><th id="L146"><a href="#L146">146</a></th><td>  Â When XML element types in the namespaces "DAV:" and</td></tr><tr><th id="L147"><a href="#L147">147</a></th><td>  Â "urn:ietf:params:xml:ns:caldav" are referenced in this document</td></tr><tr><th id="L148"><a href="#L148">148</a></th><td>  Â outside of the context of an XML fragment, the string "DAV:" and</td></tr><tr><th id="L149"><a href="#L149">149</a></th><td>  Â "CALDAV:" will be prefixed to the element type names respectively.</td></tr><tr><th id="L150"><a href="#L150">150</a></th><td></td></tr><tr><th id="L151"><a href="#L151">151</a></th><td>  Â The namespace "http://calendarserver.org/ns/" is used for XML</td></tr><tr><th id="L152"><a href="#L152">152</a></th><td>  Â elements defined in this specification.  When XML element types in</td></tr><tr><th id="L153"><a href="#L153">153</a></th><td>  Â that namespace are referenced in this document outside of the context</td></tr><tr><th id="L154"><a href="#L154">154</a></th><td>  Â of an XML fragment, the string "CS:" will be prefixed to the element</td></tr><tr><th id="L155"><a href="#L155">155</a></th><td>  Â type names.</td></tr><tr><th id="L156"><a href="#L156">156</a></th><td></td></tr><tr><th id="L157"><a href="#L157">157</a></th><td>  Â Terms Used:</td></tr><tr><th id="L158"><a href="#L158">158</a></th><td></td></tr><tr><th id="L159"><a href="#L159">159</a></th><td>  Â Sharer  A calendar user who is sharing a calendar with other calendar</td></tr><tr><th id="L160"><a href="#L160">160</a></th><td>  Â  Â  users.</td></tr><tr><th id="L161"><a href="#L161">161</a></th><td></td></tr><tr><th id="L162"><a href="#L162">162</a></th><td></td></tr><tr><th id="L163"><a href="#L163">163</a></th><td></td></tr><tr><th id="L164"><a href="#L164">164</a></th><td></td></tr><tr><th id="L165"><a href="#L165">165</a></th><td></td></tr><tr><th id="L166"><a href="#L166">166</a></th><td></td></tr><tr><th id="L167"><a href="#L167">167</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 3]</td></tr><tr><th id="L168"><a href="#L168">168</a></th><td></td></tr><tr><th id="L169"><a href="#L169">169</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L170"><a href="#L170">170</a></th><td></td></tr><tr><th id="L171"><a href="#L171">171</a></th><td></td></tr><tr><th id="L172"><a href="#L172">172</a></th><td>  Â Sharee  A calendar user to whom a calendar has been shared.</td></tr><tr><th id="L173"><a href="#L173">173</a></th><td></td></tr><tr><th id="L174"><a href="#L174">174</a></th><td>  Â Sharing Invite  A message sent by a sharer to a sharee to indicate</td></tr><tr><th id="L175"><a href="#L175">175</a></th><td>  Â  Â  the status of a shared calendar.</td></tr><tr><th id="L176"><a href="#L176">176</a></th><td></td></tr><tr><th id="L177"><a href="#L177">177</a></th><td>  Â Sharing Reply  A message sent by a sharee to a sharer to indicate the</td></tr><tr><th id="L178"><a href="#L178">178</a></th><td>  Â  Â  status of a shared calendar.</td></tr><tr><th id="L179"><a href="#L179">179</a></th><td></td></tr><tr><th id="L180"><a href="#L180">180</a></th><td></td></tr><tr><th id="L181"><a href="#L181">181</a></th><td>3.  Overview</td></tr><tr><th id="L182"><a href="#L182">182</a></th><td></td></tr><tr><th id="L183"><a href="#L183">183</a></th><td>  Â This section provides a basic overview of this protocol by way of a</td></tr><tr><th id="L184"><a href="#L184">184</a></th><td>  Â simple use case of a sharer sharing a calendar with a single sharee.</td></tr><tr><th id="L185"><a href="#L185">185</a></th><td></td></tr><tr><th id="L186"><a href="#L186">186</a></th><td>  Â To share a calendar with another user, the sharer's client executes</td></tr><tr><th id="L187"><a href="#L187">187</a></th><td>  Â an HTTP POST request against the calendar collection resource for the</td></tr><tr><th id="L188"><a href="#L188">188</a></th><td>  Â calendar to be shared.  The POST request body will contain details of</td></tr><tr><th id="L189"><a href="#L189">189</a></th><td>  Â the calendar user to whom the calendar is to be shared as well as the</td></tr><tr><th id="L190"><a href="#L190">190</a></th><td>  Â access right to be granted to them.  If the request succeeds, a</td></tr><tr><th id="L191"><a href="#L191">191</a></th><td>  Â notification is sent to the sharee with details of the calendar being</td></tr><tr><th id="L192"><a href="#L192">192</a></th><td>  Â shared to them.</td></tr><tr><th id="L193"><a href="#L193">193</a></th><td></td></tr><tr><th id="L194"><a href="#L194">194</a></th><td>  Â The sharer's client will show the notification to the sharee and</td></tr><tr><th id="L195"><a href="#L195">195</a></th><td>  Â present them with the choice to accept or decline the invitation to</td></tr><tr><th id="L196"><a href="#L196">196</a></th><td>  Â the shared calendar.  If the sharee chooses to decline, then nothing</td></tr><tr><th id="L197"><a href="#L197">197</a></th><td>  Â changes for that sharee.  If the sharee chooses to accept, then the</td></tr><tr><th id="L198"><a href="#L198">198</a></th><td>  Â server automatically creates a new calendar collection resource in</td></tr><tr><th id="L199"><a href="#L199">199</a></th><td>  Â the sharee's calendar home collection, and ensures that calendar</td></tr><tr><th id="L200"><a href="#L200">200</a></th><td>  Â provides a mapping to the actual shared calendar of the sharer.  Thus</td></tr><tr><th id="L201"><a href="#L201">201</a></th><td>  Â the shared calendar is available to the sharee as just another</td></tr><tr><th id="L202"><a href="#L202">202</a></th><td>  Â calendar in their calendar home.  The server enforces the appropriare</td></tr><tr><th id="L203"><a href="#L203">203</a></th><td>  Â access privileges for the sharee.</td></tr><tr><th id="L204"><a href="#L204">204</a></th><td></td></tr><tr><th id="L205"><a href="#L205">205</a></th><td>  Â At any time, the sharer can inspect properties on the calendar</td></tr><tr><th id="L206"><a href="#L206">206</a></th><td>  Â collection being shared, and determine the accept/decline status of</td></tr><tr><th id="L207"><a href="#L207">207</a></th><td>  Â each sharee.  Additional sharees can be added and existing ones</td></tr><tr><th id="L208"><a href="#L208">208</a></th><td>  Â removed.  The access privileges for existing sharees can also be</td></tr><tr><th id="L209"><a href="#L209">209</a></th><td>  Â changed.</td></tr><tr><th id="L210"><a href="#L210">210</a></th><td></td></tr><tr><th id="L211"><a href="#L211">211</a></th><td>  Â Once a sharee has a shared calendar set to appear in their calendar</td></tr><tr><th id="L212"><a href="#L212">212</a></th><td>  Â home collection, they can remove it and decline the sharing invite by</td></tr><tr><th id="L213"><a href="#L213">213</a></th><td>  Â simply having their client issue an HTTP DELETE request on the shared</td></tr><tr><th id="L214"><a href="#L214">214</a></th><td>  Â calendar collection.  That does not delete any calendar data, but</td></tr><tr><th id="L215"><a href="#L215">215</a></th><td>  Â rather simply removes the "link" to the sharer's calendar collection</td></tr><tr><th id="L216"><a href="#L216">216</a></th><td>  Â and sets the sharee's inviate status to declined.</td></tr><tr><th id="L217"><a href="#L217">217</a></th><td></td></tr><tr><th id="L218"><a href="#L218">218</a></th><td></td></tr><tr><th id="L219"><a href="#L219">219</a></th><td></td></tr><tr><th id="L220"><a href="#L220">220</a></th><td></td></tr><tr><th id="L221"><a href="#L221">221</a></th><td></td></tr><tr><th id="L222"><a href="#L222">222</a></th><td></td></tr><tr><th id="L223"><a href="#L223">223</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 4]</td></tr><tr><th id="L224"><a href="#L224">224</a></th><td></td></tr><tr><th id="L225"><a href="#L225">225</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L226"><a href="#L226">226</a></th><td></td></tr><tr><th id="L227"><a href="#L227">227</a></th><td></td></tr><tr><th id="L228"><a href="#L228">228</a></th><td>4.  Notifications</td></tr><tr><th id="L229"><a href="#L229">229</a></th><td></td></tr><tr><th id="L230"><a href="#L230">230</a></th><td>  Â In order to facilitate the process of sharing invitations, this</td></tr><tr><th id="L231"><a href="#L231">231</a></th><td>  Â specification defines a new generic notification mechanism for CalDAV</td></tr><tr><th id="L232"><a href="#L232">232</a></th><td>  Â servers.  When this feature is available, a CS:notification-URL</td></tr><tr><th id="L233"><a href="#L233">233</a></th><td>  Â (Section 4.1.1) property appears on principal resources for those</td></tr><tr><th id="L234"><a href="#L234">234</a></th><td>  Â principals who are able to receive notifications.  That property</td></tr><tr><th id="L235"><a href="#L235">235</a></th><td>  Â specifies a single DAV:href element whose content refers to a WebDAV</td></tr><tr><th id="L236"><a href="#L236">236</a></th><td>  Â collection resource.  Notification "messages" are deposited into this</td></tr><tr><th id="L237"><a href="#L237">237</a></th><td>  Â collection and can be retrieved by clients and acted on accordingly.</td></tr><tr><th id="L238"><a href="#L238">238</a></th><td></td></tr><tr><th id="L239"><a href="#L239">239</a></th><td>  Â The notification collection referenced by the CS:notification-URL</td></tr><tr><th id="L240"><a href="#L240">240</a></th><td>  Â (Section 4.1.1) property MUST have a DAV:resourcetype property with</td></tr><tr><th id="L241"><a href="#L241">241</a></th><td>  Â DAV:collection and CS:notification (Section 6.22) child elements.</td></tr><tr><th id="L242"><a href="#L242">242</a></th><td></td></tr><tr><th id="L243"><a href="#L243">243</a></th><td>  Â Notification "messages" are XML documents stored as resources in the</td></tr><tr><th id="L244"><a href="#L244">244</a></th><td>  Â notification collection.  Each XML document contains a CS:</td></tr><tr><th id="L245"><a href="#L245">245</a></th><td>  Â notification (Section 6.22) element as its root.  The root element</td></tr><tr><th id="L246"><a href="#L246">246</a></th><td>  Â contains a CS:dtstamp (Section 6.23) element, and one additional</td></tr><tr><th id="L247"><a href="#L247">247</a></th><td>  Â element which represents the type of notification being conveyed in</td></tr><tr><th id="L248"><a href="#L248">248</a></th><td>  Â the message.  That child element will typically contain additional</td></tr><tr><th id="L249"><a href="#L249">249</a></th><td>  Â content that describes the notification.</td></tr><tr><th id="L250"><a href="#L250">250</a></th><td></td></tr><tr><th id="L251"><a href="#L251">251</a></th><td>  Â Each notification resource has a CS:notificationtype (Section 4.2.1)</td></tr><tr><th id="L252"><a href="#L252">252</a></th><td>  Â property which contains as its single child element an empty element</td></tr><tr><th id="L253"><a href="#L253">253</a></th><td>  Â that matches the child element of the notification resource XML</td></tr><tr><th id="L254"><a href="#L254">254</a></th><td>  Â document root.  Any attributes on the child element in the XML</td></tr><tr><th id="L255"><a href="#L255">255</a></th><td>  Â document are also present in the property child element.</td></tr><tr><th id="L256"><a href="#L256">256</a></th><td></td></tr><tr><th id="L257"><a href="#L257">257</a></th><td>  Â Notifications are automatically generated by the server (perhaps in</td></tr><tr><th id="L258"><a href="#L258">258</a></th><td>  Â response to a client action) with an appropriate resource stored in</td></tr><tr><th id="L259"><a href="#L259">259</a></th><td>  Â the notifications collection of the user to whom the notification is</td></tr><tr><th id="L260"><a href="#L260">260</a></th><td>  Â targeted.  Clients SHOULD monitor the notification collection looking</td></tr><tr><th id="L261"><a href="#L261">261</a></th><td>  Â for new notification resources.  When doing so, clients SHOULD look</td></tr><tr><th id="L262"><a href="#L262">262</a></th><td>  Â at the CS:notificationtype (Section 4.2.1) property to ensure that</td></tr><tr><th id="L263"><a href="#L263">263</a></th><td>  Â the notification is of a type that the client can handle.  Once a</td></tr><tr><th id="L264"><a href="#L264">264</a></th><td>  Â client has handled the notification in whatever way is appropriate it</td></tr><tr><th id="L265"><a href="#L265">265</a></th><td>  Â SHOULD delete the notification resource.  Servers MAY delete</td></tr><tr><th id="L266"><a href="#L266">266</a></th><td>  Â notification resources on their own if they determine that the</td></tr><tr><th id="L267"><a href="#L267">267</a></th><td>  Â notifications are no longer relevant or valid.  Servers MAY coalesce</td></tr><tr><th id="L268"><a href="#L268">268</a></th><td>  Â notifications as appropriate.</td></tr><tr><th id="L269"><a href="#L269">269</a></th><td></td></tr><tr><th id="L270"><a href="#L270">270</a></th><td>4.1.  Additional Principal Properties</td></tr><tr><th id="L271"><a href="#L271">271</a></th><td></td></tr><tr><th id="L272"><a href="#L272">272</a></th><td>  Â This section defines new properties for WebDAV principal resources as</td></tr><tr><th id="L273"><a href="#L273">273</a></th><td>  Â defined in RFC3744 [RFC3744].  These properties are likely to be</td></tr><tr><th id="L274"><a href="#L274">274</a></th><td>  Â protected but the server MAY allow them to be written by appropriate</td></tr><tr><th id="L275"><a href="#L275">275</a></th><td>  Â users.</td></tr><tr><th id="L276"><a href="#L276">276</a></th><td></td></tr><tr><th id="L277"><a href="#L277">277</a></th><td></td></tr><tr><th id="L278"><a href="#L278">278</a></th><td></td></tr><tr><th id="L279"><a href="#L279">279</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 5]</td></tr><tr><th id="L280"><a href="#L280">280</a></th><td></td></tr><tr><th id="L281"><a href="#L281">281</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L282"><a href="#L282">282</a></th><td></td></tr><tr><th id="L283"><a href="#L283">283</a></th><td></td></tr><tr><th id="L284"><a href="#L284">284</a></th><td>4.1.1.  CS:notification-URL Property</td></tr><tr><th id="L285"><a href="#L285">285</a></th><td></td></tr><tr><th id="L286"><a href="#L286">286</a></th><td>  Â Name:  notification-URL</td></tr><tr><th id="L287"><a href="#L287">287</a></th><td></td></tr><tr><th id="L288"><a href="#L288">288</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L289"><a href="#L289">289</a></th><td></td></tr><tr><th id="L290"><a href="#L290">290</a></th><td>  Â Purpose:  Identify the URL of the notification collection owned by</td></tr><tr><th id="L291"><a href="#L291">291</a></th><td>  Â  Â  the associated principal resource.</td></tr><tr><th id="L292"><a href="#L292">292</a></th><td></td></tr><tr><th id="L293"><a href="#L293">293</a></th><td>  Â Protected:  This property SHOULD be protected.</td></tr><tr><th id="L294"><a href="#L294">294</a></th><td></td></tr><tr><th id="L295"><a href="#L295">295</a></th><td>  Â PROPFIND behavior:  This property SHOULD NOT be returned by a</td></tr><tr><th id="L296"><a href="#L296">296</a></th><td>  Â  Â  PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L297"><a href="#L297">297</a></th><td>  Â  Â  [RFC4918]).</td></tr><tr><th id="L298"><a href="#L298">298</a></th><td></td></tr><tr><th id="L299"><a href="#L299">299</a></th><td>  Â COPY/MOVE behavior:  This property value SHOULD be preserved in COPY</td></tr><tr><th id="L300"><a href="#L300">300</a></th><td>  Â  Â  and MOVE operations.</td></tr><tr><th id="L301"><a href="#L301">301</a></th><td></td></tr><tr><th id="L302"><a href="#L302">302</a></th><td>  Â Description:  This property is needed for a client to determine where</td></tr><tr><th id="L303"><a href="#L303">303</a></th><td>  Â  Â  the notification collection of the current user is located so that</td></tr><tr><th id="L304"><a href="#L304">304</a></th><td>  Â  Â  processing of notification messages can occur.  If not present,</td></tr><tr><th id="L305"><a href="#L305">305</a></th><td>  Â  Â  then the associated calendar user is not enabled for notification</td></tr><tr><th id="L306"><a href="#L306">306</a></th><td>  Â  Â  messages on the server.</td></tr><tr><th id="L307"><a href="#L307">307</a></th><td></td></tr><tr><th id="L308"><a href="#L308">308</a></th><td>  Â Definition:</td></tr><tr><th id="L309"><a href="#L309">309</a></th><td></td></tr><tr><th id="L310"><a href="#L310">310</a></th><td>  Â &lt;!ELEMENT notification-URL (DAV:href)&gt;</td></tr><tr><th id="L311"><a href="#L311">311</a></th><td></td></tr><tr><th id="L312"><a href="#L312">312</a></th><td>4.2.  Properties on Notification Resources</td></tr><tr><th id="L313"><a href="#L313">313</a></th><td></td></tr><tr><th id="L314"><a href="#L314">314</a></th><td>  Â The following new WebDAV properties are defined for notification</td></tr><tr><th id="L315"><a href="#L315">315</a></th><td>  Â resources.</td></tr><tr><th id="L316"><a href="#L316">316</a></th><td></td></tr><tr><th id="L317"><a href="#L317">317</a></th><td>4.2.1.  CS:notificationtype Property</td></tr><tr><th id="L318"><a href="#L318">318</a></th><td></td></tr><tr><th id="L319"><a href="#L319">319</a></th><td>  Â Name:  notificationtype</td></tr><tr><th id="L320"><a href="#L320">320</a></th><td></td></tr><tr><th id="L321"><a href="#L321">321</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L322"><a href="#L322">322</a></th><td></td></tr><tr><th id="L323"><a href="#L323">323</a></th><td>  Â Purpose:  Identify the type of notification of the corresponding</td></tr><tr><th id="L324"><a href="#L324">324</a></th><td>  Â  Â  resource.</td></tr><tr><th id="L325"><a href="#L325">325</a></th><td></td></tr><tr><th id="L326"><a href="#L326">326</a></th><td>  Â Protected:  This property MUST be protected.</td></tr><tr><th id="L327"><a href="#L327">327</a></th><td></td></tr><tr><th id="L328"><a href="#L328">328</a></th><td>  Â PROPFIND behavior:  This property SHOULD NOT be returned by a</td></tr><tr><th id="L329"><a href="#L329">329</a></th><td>  Â  Â  PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L330"><a href="#L330">330</a></th><td>  Â  Â  [RFC4918]).</td></tr><tr><th id="L331"><a href="#L331">331</a></th><td></td></tr><tr><th id="L332"><a href="#L332">332</a></th><td></td></tr><tr><th id="L333"><a href="#L333">333</a></th><td></td></tr><tr><th id="L334"><a href="#L334">334</a></th><td></td></tr><tr><th id="L335"><a href="#L335">335</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 6]</td></tr><tr><th id="L336"><a href="#L336">336</a></th><td></td></tr><tr><th id="L337"><a href="#L337">337</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L338"><a href="#L338">338</a></th><td></td></tr><tr><th id="L339"><a href="#L339">339</a></th><td></td></tr><tr><th id="L340"><a href="#L340">340</a></th><td>  Â COPY/MOVE behavior:  This property value MUST be preserved in COPY</td></tr><tr><th id="L341"><a href="#L341">341</a></th><td>  Â  Â  and MOVE operations.</td></tr><tr><th id="L342"><a href="#L342">342</a></th><td></td></tr><tr><th id="L343"><a href="#L343">343</a></th><td>  Â Description:  This property allows a client, via a PROPFIND Depth:1</td></tr><tr><th id="L344"><a href="#L344">344</a></th><td>  Â  Â  request, to quickly find notification messages that the client can</td></tr><tr><th id="L345"><a href="#L345">345</a></th><td>  Â  Â  handle in a notification collection.  The single child element is</td></tr><tr><th id="L346"><a href="#L346">346</a></th><td>  Â  Â  the notification resource root element's child defining the</td></tr><tr><th id="L347"><a href="#L347">347</a></th><td>  Â  Â  notification itself.  This element MUST be empty, though any</td></tr><tr><th id="L348"><a href="#L348">348</a></th><td>  Â  Â  attributes on the element in the notification resource MUST be</td></tr><tr><th id="L349"><a href="#L349">349</a></th><td>  Â  Â  present in the property element.</td></tr><tr><th id="L350"><a href="#L350">350</a></th><td></td></tr><tr><th id="L351"><a href="#L351">351</a></th><td>  Â Definition:</td></tr><tr><th id="L352"><a href="#L352">352</a></th><td></td></tr><tr><th id="L353"><a href="#L353">353</a></th><td>  Â &lt;!ELEMENT notificationtype (invite-notification | invite-reply)&gt;</td></tr><tr><th id="L354"><a href="#L354">354</a></th><td>  Â &lt;!-- Child elements are empty but will have appropriate attributes.</td></tr><tr><th id="L355"><a href="#L355">355</a></th><td>  Â  Â  Â  Any valid notification message child element can appear.--&gt;</td></tr><tr><th id="L356"><a href="#L356">356</a></th><td></td></tr><tr><th id="L357"><a href="#L357">357</a></th><td></td></tr><tr><th id="L358"><a href="#L358">358</a></th><td>5.  Shared Calendaring</td></tr><tr><th id="L359"><a href="#L359">359</a></th><td></td></tr><tr><th id="L360"><a href="#L360">360</a></th><td>5.1.  Feature Discovery</td></tr><tr><th id="L361"><a href="#L361">361</a></th><td></td></tr><tr><th id="L362"><a href="#L362">362</a></th><td>  Â A server that supports the features described in this document MUST</td></tr><tr><th id="L363"><a href="#L363">363</a></th><td>  Â include "calendarserver-sharing" as a field in the DAV response</td></tr><tr><th id="L364"><a href="#L364">364</a></th><td>  Â header from an OPTIONS request on any resource that supports these</td></tr><tr><th id="L365"><a href="#L365">365</a></th><td>  Â features.</td></tr><tr><th id="L366"><a href="#L366">366</a></th><td></td></tr><tr><th id="L367"><a href="#L367">367</a></th><td>5.2.  Additional Properties for Calendars</td></tr><tr><th id="L368"><a href="#L368">368</a></th><td></td></tr><tr><th id="L369"><a href="#L369">369</a></th><td>  Â The following new or modified WebDAV properties are defined for</td></tr><tr><th id="L370"><a href="#L370">370</a></th><td>  Â calendar collections and used to view or manipulate shared calendar</td></tr><tr><th id="L371"><a href="#L371">371</a></th><td>  Â features.</td></tr><tr><th id="L372"><a href="#L372">372</a></th><td></td></tr><tr><th id="L373"><a href="#L373">373</a></th><td>5.2.1.  DAV:resourcetype Property</td></tr><tr><th id="L374"><a href="#L374">374</a></th><td></td></tr><tr><th id="L375"><a href="#L375">375</a></th><td>  Â Calendar collections that are shared have addition elements listed in</td></tr><tr><th id="L376"><a href="#L376">376</a></th><td>  Â their DAV:resourcetype property in addition to DAV:collection and</td></tr><tr><th id="L377"><a href="#L377">377</a></th><td>  Â CALDAV:calendar.</td></tr><tr><th id="L378"><a href="#L378">378</a></th><td></td></tr><tr><th id="L379"><a href="#L379">379</a></th><td>  Â o  CS:shared-owner (Section 6.1): used to indicate that the calendar</td></tr><tr><th id="L380"><a href="#L380">380</a></th><td>  Â  Â  is owned by the current user and is being shared by them.</td></tr><tr><th id="L381"><a href="#L381">381</a></th><td></td></tr><tr><th id="L382"><a href="#L382">382</a></th><td>  Â o  CS:shared (Section 6.2): used to indicate that the calendar is</td></tr><tr><th id="L383"><a href="#L383">383</a></th><td>  Â  Â  owned by another user and is being shared to the current user.</td></tr><tr><th id="L384"><a href="#L384">384</a></th><td></td></tr><tr><th id="L385"><a href="#L385">385</a></th><td></td></tr><tr><th id="L386"><a href="#L386">386</a></th><td></td></tr><tr><th id="L387"><a href="#L387">387</a></th><td></td></tr><tr><th id="L388"><a href="#L388">388</a></th><td></td></tr><tr><th id="L389"><a href="#L389">389</a></th><td></td></tr><tr><th id="L390"><a href="#L390">390</a></th><td></td></tr><tr><th id="L391"><a href="#L391">391</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 7]</td></tr><tr><th id="L392"><a href="#L392">392</a></th><td></td></tr><tr><th id="L393"><a href="#L393">393</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L394"><a href="#L394">394</a></th><td></td></tr><tr><th id="L395"><a href="#L395">395</a></th><td></td></tr><tr><th id="L396"><a href="#L396">396</a></th><td>5.2.2.  CS:invite Property</td></tr><tr><th id="L397"><a href="#L397">397</a></th><td></td></tr><tr><th id="L398"><a href="#L398">398</a></th><td>  Â Name:  invite</td></tr><tr><th id="L399"><a href="#L399">399</a></th><td></td></tr><tr><th id="L400"><a href="#L400">400</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L401"><a href="#L401">401</a></th><td></td></tr><tr><th id="L402"><a href="#L402">402</a></th><td>  Â Purpose:  Used to show to whom a calendar has been shared.</td></tr><tr><th id="L403"><a href="#L403">403</a></th><td></td></tr><tr><th id="L404"><a href="#L404">404</a></th><td>  Â Protected:  This property MUST be protected.</td></tr><tr><th id="L405"><a href="#L405">405</a></th><td></td></tr><tr><th id="L406"><a href="#L406">406</a></th><td>  Â PROPFIND behavior:  This property SHOULD NOT be returned by a</td></tr><tr><th id="L407"><a href="#L407">407</a></th><td>  Â  Â  PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L408"><a href="#L408">408</a></th><td>  Â  Â  [RFC4918]).</td></tr><tr><th id="L409"><a href="#L409">409</a></th><td></td></tr><tr><th id="L410"><a href="#L410">410</a></th><td>  Â COPY/MOVE behavior:  This property value MUST be preserved in COPY</td></tr><tr><th id="L411"><a href="#L411">411</a></th><td>  Â  Â  and MOVE operations.</td></tr><tr><th id="L412"><a href="#L412">412</a></th><td></td></tr><tr><th id="L413"><a href="#L413">413</a></th><td>  Â Description:  This WebDAV property is present on a calendar</td></tr><tr><th id="L414"><a href="#L414">414</a></th><td>  Â  Â  collection resource that has been shared by the owner.  It MUST</td></tr><tr><th id="L415"><a href="#L415">415</a></th><td>  Â  Â  NOT appear on the calendar collection resources of the sharees of</td></tr><tr><th id="L416"><a href="#L416">416</a></th><td>  Â  Â  the calendar.  It provides a list of users to whom the calendar</td></tr><tr><th id="L417"><a href="#L417">417</a></th><td>  Â  Â  has been shared, along with the "status" of the sharing invites</td></tr><tr><th id="L418"><a href="#L418">418</a></th><td>  Â  Â  sent to each user.</td></tr><tr><th id="L419"><a href="#L419">419</a></th><td></td></tr><tr><th id="L420"><a href="#L420">420</a></th><td>  Â Definition:</td></tr><tr><th id="L421"><a href="#L421">421</a></th><td></td></tr><tr><th id="L422"><a href="#L422">422</a></th><td>  Â &lt;!ELEMENT invite (user*)&gt;</td></tr><tr><th id="L423"><a href="#L423">423</a></th><td></td></tr><tr><th id="L424"><a href="#L424">424</a></th><td>5.2.3.  CS:allowed-sharing-modes Property</td></tr><tr><th id="L425"><a href="#L425">425</a></th><td></td></tr><tr><th id="L426"><a href="#L426">426</a></th><td>  Â Name:  allowed-sharing-modes</td></tr><tr><th id="L427"><a href="#L427">427</a></th><td></td></tr><tr><th id="L428"><a href="#L428">428</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L429"><a href="#L429">429</a></th><td></td></tr><tr><th id="L430"><a href="#L430">430</a></th><td>  Â Purpose:  Used to show which modes of sharing are supported on a</td></tr><tr><th id="L431"><a href="#L431">431</a></th><td>  Â  Â  calendar collection.</td></tr><tr><th id="L432"><a href="#L432">432</a></th><td></td></tr><tr><th id="L433"><a href="#L433">433</a></th><td>  Â Protected:  This property MUST be protected.</td></tr><tr><th id="L434"><a href="#L434">434</a></th><td></td></tr><tr><th id="L435"><a href="#L435">435</a></th><td>  Â PROPFIND behavior:  This property SHOULD NOT be returned by a</td></tr><tr><th id="L436"><a href="#L436">436</a></th><td>  Â  Â  PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L437"><a href="#L437">437</a></th><td>  Â  Â  [RFC4918]).</td></tr><tr><th id="L438"><a href="#L438">438</a></th><td></td></tr><tr><th id="L439"><a href="#L439">439</a></th><td>  Â COPY/MOVE behavior:  This property value MUST be preserved in COPY</td></tr><tr><th id="L440"><a href="#L440">440</a></th><td>  Â  Â  and MOVE operations.</td></tr><tr><th id="L441"><a href="#L441">441</a></th><td></td></tr><tr><th id="L442"><a href="#L442">442</a></th><td></td></tr><tr><th id="L443"><a href="#L443">443</a></th><td></td></tr><tr><th id="L444"><a href="#L444">444</a></th><td></td></tr><tr><th id="L445"><a href="#L445">445</a></th><td></td></tr><tr><th id="L446"><a href="#L446">446</a></th><td></td></tr><tr><th id="L447"><a href="#L447">447</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 8]</td></tr><tr><th id="L448"><a href="#L448">448</a></th><td></td></tr><tr><th id="L449"><a href="#L449">449</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L450"><a href="#L450">450</a></th><td></td></tr><tr><th id="L451"><a href="#L451">451</a></th><td></td></tr><tr><th id="L452"><a href="#L452">452</a></th><td>  Â Description:  This WebDAV property is present on a calendar</td></tr><tr><th id="L453"><a href="#L453">453</a></th><td>  Â  Â  collection resource that can been shared or published.  It</td></tr><tr><th id="L454"><a href="#L454">454</a></th><td>  Â  Â  provides a list of options indicating what sharing modes are</td></tr><tr><th id="L455"><a href="#L455">455</a></th><td>  Â  Â  allowed as per Section 5.5.2.</td></tr><tr><th id="L456"><a href="#L456">456</a></th><td></td></tr><tr><th id="L457"><a href="#L457">457</a></th><td>  Â Definition:</td></tr><tr><th id="L458"><a href="#L458">458</a></th><td></td></tr><tr><th id="L459"><a href="#L459">459</a></th><td>  Â &lt;!ELEMENT allowed-sharing-modes</td></tr><tr><th id="L460"><a href="#L460">460</a></th><td>  Â  Â  Â  Â  Â  Â (can-be-shared?, can-be-published?)&gt;</td></tr><tr><th id="L461"><a href="#L461">461</a></th><td></td></tr><tr><th id="L462"><a href="#L462">462</a></th><td>5.2.4.  CS:shared-url Property</td></tr><tr><th id="L463"><a href="#L463">463</a></th><td></td></tr><tr><th id="L464"><a href="#L464">464</a></th><td>  Â Name:  shared-url</td></tr><tr><th id="L465"><a href="#L465">465</a></th><td></td></tr><tr><th id="L466"><a href="#L466">466</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L467"><a href="#L467">467</a></th><td></td></tr><tr><th id="L468"><a href="#L468">468</a></th><td>  Â Purpose:  Indicates the URL of the owner's copy of a shared calendar.</td></tr><tr><th id="L469"><a href="#L469">469</a></th><td></td></tr><tr><th id="L470"><a href="#L470">470</a></th><td>  Â Protected:  This property MUST be protected.</td></tr><tr><th id="L471"><a href="#L471">471</a></th><td></td></tr><tr><th id="L472"><a href="#L472">472</a></th><td>  Â PROPFIND behavior:  This property SHOULD NOT be returned by a</td></tr><tr><th id="L473"><a href="#L473">473</a></th><td>  Â  Â  PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L474"><a href="#L474">474</a></th><td>  Â  Â  [RFC4918]).</td></tr><tr><th id="L475"><a href="#L475">475</a></th><td></td></tr><tr><th id="L476"><a href="#L476">476</a></th><td>  Â COPY/MOVE behavior:  This property value MUST be preserved in COPY</td></tr><tr><th id="L477"><a href="#L477">477</a></th><td>  Â  Â  and MOVE operations.</td></tr><tr><th id="L478"><a href="#L478">478</a></th><td></td></tr><tr><th id="L479"><a href="#L479">479</a></th><td>  Â Description:  This WebDAV property is present on a shared calendar</td></tr><tr><th id="L480"><a href="#L480">480</a></th><td>  Â  Â  collection resource that appears in a sharee's calendar home</td></tr><tr><th id="L481"><a href="#L481">481</a></th><td>  Â  Â  collection.  Its content is a single DAV:href element whose value</td></tr><tr><th id="L482"><a href="#L482">482</a></th><td>  Â  Â  is the URL of the sharer's calendar being shared.</td></tr><tr><th id="L483"><a href="#L483">483</a></th><td></td></tr><tr><th id="L484"><a href="#L484">484</a></th><td>  Â Definition:</td></tr><tr><th id="L485"><a href="#L485">485</a></th><td></td></tr><tr><th id="L486"><a href="#L486">486</a></th><td>  Â &lt;!ELEMENT shared-url (DAV:href)&gt;</td></tr><tr><th id="L487"><a href="#L487">487</a></th><td></td></tr><tr><th id="L488"><a href="#L488">488</a></th><td>5.3.  Sharer Actions on Shared Calendars</td></tr><tr><th id="L489"><a href="#L489">489</a></th><td></td></tr><tr><th id="L490"><a href="#L490">490</a></th><td>5.3.1.  Creating a Shared Calendar</td></tr><tr><th id="L491"><a href="#L491">491</a></th><td></td></tr><tr><th id="L492"><a href="#L492">492</a></th><td>  Â To create a shared calendar, clients use the MKCALENDAR [RFC4791] or</td></tr><tr><th id="L493"><a href="#L493">493</a></th><td>  Â extended MKCOL [RFC5689] requests, and include a DAV:resourcetype</td></tr><tr><th id="L494"><a href="#L494">494</a></th><td>  Â property to be set upon creation.  That property MUST contain DAV:</td></tr><tr><th id="L495"><a href="#L495">495</a></th><td>  Â collection, CALDAV:calendar and CS:shared-owner child elements to</td></tr><tr><th id="L496"><a href="#L496">496</a></th><td>  Â enable sharing.</td></tr><tr><th id="L497"><a href="#L497">497</a></th><td></td></tr><tr><th id="L498"><a href="#L498">498</a></th><td></td></tr><tr><th id="L499"><a href="#L499">499</a></th><td></td></tr><tr><th id="L500"><a href="#L500">500</a></th><td></td></tr><tr><th id="L501"><a href="#L501">501</a></th><td></td></tr><tr><th id="L502"><a href="#L502">502</a></th><td></td></tr><tr><th id="L503"><a href="#L503">503</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  [Page 9]</td></tr><tr><th id="L504"><a href="#L504">504</a></th><td></td></tr><tr><th id="L505"><a href="#L505">505</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L506"><a href="#L506">506</a></th><td></td></tr><tr><th id="L507"><a href="#L507">507</a></th><td></td></tr><tr><th id="L508"><a href="#L508">508</a></th><td>5.3.1.1.  Example: Successful MKCALENDAR Request</td></tr><tr><th id="L509"><a href="#L509">509</a></th><td></td></tr><tr><th id="L510"><a href="#L510">510</a></th><td>  Â This example shows how the MKCALENDAR request is used to create a</td></tr><tr><th id="L511"><a href="#L511">511</a></th><td>  Â shared calendar collection.  The response body is empty as the</td></tr><tr><th id="L512"><a href="#L512">512</a></th><td>  Â request completed successfully.</td></tr><tr><th id="L513"><a href="#L513">513</a></th><td></td></tr><tr><th id="L514"><a href="#L514">514</a></th><td>  Â &gt;&gt; Request &lt;&lt;</td></tr><tr><th id="L515"><a href="#L515">515</a></th><td></td></tr><tr><th id="L516"><a href="#L516">516</a></th><td>  Â MKCALENDAR /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L517"><a href="#L517">517</a></th><td>  Â Host: calendar.example.com</td></tr><tr><th id="L518"><a href="#L518">518</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L519"><a href="#L519">519</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L520"><a href="#L520">520</a></th><td></td></tr><tr><th id="L521"><a href="#L521">521</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L522"><a href="#L522">522</a></th><td>  Â &lt;C:mkcalendar xmlns:D="DAV:"</td></tr><tr><th id="L523"><a href="#L523">523</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L524"><a href="#L524">524</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:CS="http://calendarserver.org/ns/"&gt;</td></tr><tr><th id="L525"><a href="#L525">525</a></th><td>  Â  Â &lt;D:set&gt;</td></tr><tr><th id="L526"><a href="#L526">526</a></th><td>  Â  Â  Â &lt;D:prop&gt;</td></tr><tr><th id="L527"><a href="#L527">527</a></th><td>  Â  Â  Â  Â &lt;D:resourcetype&gt;</td></tr><tr><th id="L528"><a href="#L528">528</a></th><td>  Â  Â  Â  Â  Â &lt;D:collection/&gt;</td></tr><tr><th id="L529"><a href="#L529">529</a></th><td>  Â  Â  Â  Â  Â &lt;C:calendar/&gt;</td></tr><tr><th id="L530"><a href="#L530">530</a></th><td>  Â  Â  Â  Â  Â &lt;CS:shared-owner/&gt;</td></tr><tr><th id="L531"><a href="#L531">531</a></th><td>  Â  Â  Â  Â &lt;/D:resourcetype&gt;</td></tr><tr><th id="L532"><a href="#L532">532</a></th><td>  Â  Â  Â &lt;/D:prop&gt;</td></tr><tr><th id="L533"><a href="#L533">533</a></th><td>  Â  Â &lt;/D:set&gt;</td></tr><tr><th id="L534"><a href="#L534">534</a></th><td>  Â &lt;/C:mkcalendar&gt;</td></tr><tr><th id="L535"><a href="#L535">535</a></th><td></td></tr><tr><th id="L536"><a href="#L536">536</a></th><td>  Â &gt;&gt; Response &lt;&lt;</td></tr><tr><th id="L537"><a href="#L537">537</a></th><td></td></tr><tr><th id="L538"><a href="#L538">538</a></th><td>  Â HTTP/1.1 201 Created</td></tr><tr><th id="L539"><a href="#L539">539</a></th><td>  Â Cache-Control: no-cache</td></tr><tr><th id="L540"><a href="#L540">540</a></th><td>  Â Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L541"><a href="#L541">541</a></th><td></td></tr><tr><th id="L542"><a href="#L542">542</a></th><td>5.3.1.2.  Example: Successful Extended MKCOL Request</td></tr><tr><th id="L543"><a href="#L543">543</a></th><td></td></tr><tr><th id="L544"><a href="#L544">544</a></th><td>  Â This example shows how the extended MKCOL request is used to create a</td></tr><tr><th id="L545"><a href="#L545">545</a></th><td>  Â shared calendar collection.  The response body is empty as the</td></tr><tr><th id="L546"><a href="#L546">546</a></th><td>  Â request completed successfully.</td></tr><tr><th id="L547"><a href="#L547">547</a></th><td></td></tr><tr><th id="L548"><a href="#L548">548</a></th><td></td></tr><tr><th id="L549"><a href="#L549">549</a></th><td></td></tr><tr><th id="L550"><a href="#L550">550</a></th><td></td></tr><tr><th id="L551"><a href="#L551">551</a></th><td></td></tr><tr><th id="L552"><a href="#L552">552</a></th><td></td></tr><tr><th id="L553"><a href="#L553">553</a></th><td></td></tr><tr><th id="L554"><a href="#L554">554</a></th><td></td></tr><tr><th id="L555"><a href="#L555">555</a></th><td></td></tr><tr><th id="L556"><a href="#L556">556</a></th><td></td></tr><tr><th id="L557"><a href="#L557">557</a></th><td></td></tr><tr><th id="L558"><a href="#L558">558</a></th><td></td></tr><tr><th id="L559"><a href="#L559">559</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 10]</td></tr><tr><th id="L560"><a href="#L560">560</a></th><td></td></tr><tr><th id="L561"><a href="#L561">561</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L562"><a href="#L562">562</a></th><td></td></tr><tr><th id="L563"><a href="#L563">563</a></th><td></td></tr><tr><th id="L564"><a href="#L564">564</a></th><td>  Â &gt;&gt; Request &lt;&lt;</td></tr><tr><th id="L565"><a href="#L565">565</a></th><td></td></tr><tr><th id="L566"><a href="#L566">566</a></th><td>  Â MKCOL /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L567"><a href="#L567">567</a></th><td>  Â Host: calendar.example.com</td></tr><tr><th id="L568"><a href="#L568">568</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L569"><a href="#L569">569</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L570"><a href="#L570">570</a></th><td></td></tr><tr><th id="L571"><a href="#L571">571</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L572"><a href="#L572">572</a></th><td>  Â &lt;D:mkcol xmlns:D="DAV:"</td></tr><tr><th id="L573"><a href="#L573">573</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L574"><a href="#L574">574</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:CS="http://calendarserver.org/ns/"&gt;</td></tr><tr><th id="L575"><a href="#L575">575</a></th><td>  Â  Â &lt;D:set&gt;</td></tr><tr><th id="L576"><a href="#L576">576</a></th><td>  Â  Â  Â &lt;D:prop&gt;</td></tr><tr><th id="L577"><a href="#L577">577</a></th><td>  Â  Â  Â  Â &lt;D:resourcetype&gt;</td></tr><tr><th id="L578"><a href="#L578">578</a></th><td>  Â  Â  Â  Â  Â &lt;D:collection/&gt;</td></tr><tr><th id="L579"><a href="#L579">579</a></th><td>  Â  Â  Â  Â  Â &lt;C:calendar/&gt;</td></tr><tr><th id="L580"><a href="#L580">580</a></th><td>  Â  Â  Â  Â  Â &lt;CS:shared-owner/&gt;</td></tr><tr><th id="L581"><a href="#L581">581</a></th><td>  Â  Â  Â  Â &lt;/D:resourcetype&gt;</td></tr><tr><th id="L582"><a href="#L582">582</a></th><td>  Â  Â  Â &lt;/D:prop&gt;</td></tr><tr><th id="L583"><a href="#L583">583</a></th><td>  Â  Â &lt;/D:set&gt;</td></tr><tr><th id="L584"><a href="#L584">584</a></th><td>  Â &lt;/D:mkcol&gt;</td></tr><tr><th id="L585"><a href="#L585">585</a></th><td></td></tr><tr><th id="L586"><a href="#L586">586</a></th><td>  Â &gt;&gt; Response &lt;&lt;</td></tr><tr><th id="L587"><a href="#L587">587</a></th><td></td></tr><tr><th id="L588"><a href="#L588">588</a></th><td>  Â HTTP/1.1 201 Created</td></tr><tr><th id="L589"><a href="#L589">589</a></th><td>  Â Cache-Control: no-cache</td></tr><tr><th id="L590"><a href="#L590">590</a></th><td>  Â Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L591"><a href="#L591">591</a></th><td></td></tr><tr><th id="L592"><a href="#L592">592</a></th><td>5.3.2.  Sharing an Existing Calendar</td></tr><tr><th id="L593"><a href="#L593">593</a></th><td></td></tr><tr><th id="L594"><a href="#L594">594</a></th><td>  Â Sharing an existing calendar can be accomplished in two ways.  One</td></tr><tr><th id="L595"><a href="#L595">595</a></th><td>  Â option is to use a PROPPATCH request to set the DAV:resourcetype</td></tr><tr><th id="L596"><a href="#L596">596</a></th><td>  Â property to include CS:shared-owner as a child element.  Another</td></tr><tr><th id="L597"><a href="#L597">597</a></th><td>  Â option is to add sharee's directly to the calendar collection (as</td></tr><tr><th id="L598"><a href="#L598">598</a></th><td>  Â described in Section 5.3.3) - that action MUST upgrade a non-shared</td></tr><tr><th id="L599"><a href="#L599">599</a></th><td>  Â calendar to a shared calendar when it completes successfully,</td></tr><tr><th id="L600"><a href="#L600">600</a></th><td>  Â assuming that such an upgrade is allowed as per Section 5.5.2.</td></tr><tr><th id="L601"><a href="#L601">601</a></th><td></td></tr><tr><th id="L602"><a href="#L602">602</a></th><td>5.3.2.1.  Example: Successful PROPPATCH Request</td></tr><tr><th id="L603"><a href="#L603">603</a></th><td></td></tr><tr><th id="L604"><a href="#L604">604</a></th><td>  Â This example shows how the PROPPATCH request is used to upgrade to a</td></tr><tr><th id="L605"><a href="#L605">605</a></th><td>  Â shared calendar collection.</td></tr><tr><th id="L606"><a href="#L606">606</a></th><td></td></tr><tr><th id="L607"><a href="#L607">607</a></th><td></td></tr><tr><th id="L608"><a href="#L608">608</a></th><td></td></tr><tr><th id="L609"><a href="#L609">609</a></th><td></td></tr><tr><th id="L610"><a href="#L610">610</a></th><td></td></tr><tr><th id="L611"><a href="#L611">611</a></th><td></td></tr><tr><th id="L612"><a href="#L612">612</a></th><td></td></tr><tr><th id="L613"><a href="#L613">613</a></th><td></td></tr><tr><th id="L614"><a href="#L614">614</a></th><td></td></tr><tr><th id="L615"><a href="#L615">615</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 11]</td></tr><tr><th id="L616"><a href="#L616">616</a></th><td></td></tr><tr><th id="L617"><a href="#L617">617</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L618"><a href="#L618">618</a></th><td></td></tr><tr><th id="L619"><a href="#L619">619</a></th><td></td></tr><tr><th id="L620"><a href="#L620">620</a></th><td>  Â &gt;&gt; Request &lt;&lt;</td></tr><tr><th id="L621"><a href="#L621">621</a></th><td></td></tr><tr><th id="L622"><a href="#L622">622</a></th><td>  Â PROPPATCH /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L623"><a href="#L623">623</a></th><td>  Â Host: calendar.example.com</td></tr><tr><th id="L624"><a href="#L624">624</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L625"><a href="#L625">625</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L626"><a href="#L626">626</a></th><td></td></tr><tr><th id="L627"><a href="#L627">627</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L628"><a href="#L628">628</a></th><td>  Â &lt;D:propertyupdate xmlns:D="DAV:"</td></tr><tr><th id="L629"><a href="#L629">629</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L630"><a href="#L630">630</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:CS="http://calendarserver.org/ns/"&gt;</td></tr><tr><th id="L631"><a href="#L631">631</a></th><td>  Â  Â &lt;D:set&gt;</td></tr><tr><th id="L632"><a href="#L632">632</a></th><td>  Â  Â  Â &lt;D:prop&gt;</td></tr><tr><th id="L633"><a href="#L633">633</a></th><td>  Â  Â  Â  Â &lt;D:resourcetype&gt;</td></tr><tr><th id="L634"><a href="#L634">634</a></th><td>  Â  Â  Â  Â  Â &lt;D:collection/&gt;</td></tr><tr><th id="L635"><a href="#L635">635</a></th><td>  Â  Â  Â  Â  Â &lt;C:calendar/&gt;</td></tr><tr><th id="L636"><a href="#L636">636</a></th><td>  Â  Â  Â  Â  Â &lt;CS:shared-owner/&gt;</td></tr><tr><th id="L637"><a href="#L637">637</a></th><td>  Â  Â  Â  Â &lt;/D:resourcetype&gt;</td></tr><tr><th id="L638"><a href="#L638">638</a></th><td>  Â  Â  Â &lt;/D:prop&gt;</td></tr><tr><th id="L639"><a href="#L639">639</a></th><td>  Â  Â &lt;/D:set&gt;</td></tr><tr><th id="L640"><a href="#L640">640</a></th><td>  Â &lt;/D:propertyupdate&gt;</td></tr><tr><th id="L641"><a href="#L641">641</a></th><td></td></tr><tr><th id="L642"><a href="#L642">642</a></th><td>  Â &gt;&gt; Response &lt;&lt;</td></tr><tr><th id="L643"><a href="#L643">643</a></th><td></td></tr><tr><th id="L644"><a href="#L644">644</a></th><td>  Â HTTP/1.1 207 Multi-Status</td></tr><tr><th id="L645"><a href="#L645">645</a></th><td>  Â Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L646"><a href="#L646">646</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L647"><a href="#L647">647</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L648"><a href="#L648">648</a></th><td></td></tr><tr><th id="L649"><a href="#L649">649</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L650"><a href="#L650">650</a></th><td>  Â &lt;D:multistatus xmlns:D="DAV:"&gt;</td></tr><tr><th id="L651"><a href="#L651">651</a></th><td>  Â  Â &lt;D:response&gt;</td></tr><tr><th id="L652"><a href="#L652">652</a></th><td>  Â  Â  Â &lt;D:href&gt;/calendars/users/cyrus/shared/&lt;/D:href&gt;</td></tr><tr><th id="L653"><a href="#L653">653</a></th><td>  Â  Â  Â &lt;D:propstat&gt;</td></tr><tr><th id="L654"><a href="#L654">654</a></th><td>  Â  Â  Â  Â &lt;D:prop&gt;</td></tr><tr><th id="L655"><a href="#L655">655</a></th><td>  Â  Â  Â  Â  Â &lt;D:resourcetype/&gt;</td></tr><tr><th id="L656"><a href="#L656">656</a></th><td>  Â  Â  Â  Â &lt;/D:prop&gt;</td></tr><tr><th id="L657"><a href="#L657">657</a></th><td>  Â  Â  Â  Â &lt;D:status&gt;HTTP/1.1 200 OK&lt;/D:status&gt;</td></tr><tr><th id="L658"><a href="#L658">658</a></th><td>  Â  Â  Â &lt;/D:propstat&gt;</td></tr><tr><th id="L659"><a href="#L659">659</a></th><td>  Â  Â &lt;/D:response&gt;</td></tr><tr><th id="L660"><a href="#L660">660</a></th><td>  Â &lt;/D:multistatus&gt;</td></tr><tr><th id="L661"><a href="#L661">661</a></th><td></td></tr><tr><th id="L662"><a href="#L662">662</a></th><td>5.3.3.  Manipulating Sharees of a Shared Calendar</td></tr><tr><th id="L663"><a href="#L663">663</a></th><td></td></tr><tr><th id="L664"><a href="#L664">664</a></th><td>  Â The sharer of a shared calendar is able to manipulate the sharee list</td></tr><tr><th id="L665"><a href="#L665">665</a></th><td>  Â by issuing a POST request targeted at the shared calendar collection</td></tr><tr><th id="L666"><a href="#L666">666</a></th><td>  Â resource.  The POST request MUST contain an XML document as its body</td></tr><tr><th id="L667"><a href="#L667">667</a></th><td>  Â with the root element being CS:share (Section 6.24).</td></tr><tr><th id="L668"><a href="#L668">668</a></th><td></td></tr><tr><th id="L669"><a href="#L669">669</a></th><td></td></tr><tr><th id="L670"><a href="#L670">670</a></th><td></td></tr><tr><th id="L671"><a href="#L671">671</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 12]</td></tr><tr><th id="L672"><a href="#L672">672</a></th><td></td></tr><tr><th id="L673"><a href="#L673">673</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L674"><a href="#L674">674</a></th><td></td></tr><tr><th id="L675"><a href="#L675">675</a></th><td></td></tr><tr><th id="L676"><a href="#L676">676</a></th><td>  Â The CS:share (Section 6.24) element in the POST requests MUST contain</td></tr><tr><th id="L677"><a href="#L677">677</a></th><td>  Â one or more CS:set (Section 6.25) or CS:remove (Section 6.26)</td></tr><tr><th id="L678"><a href="#L678">678</a></th><td>  Â elements.  For each CS:set (Section 6.25) element, the server MUST</td></tr><tr><th id="L679"><a href="#L679">679</a></th><td>  Â add the specified sharee access to the shared calendar.  For each CS:</td></tr><tr><th id="L680"><a href="#L680">680</a></th><td>  Â remove (Section 6.26) element the server MUST remove the specified</td></tr><tr><th id="L681"><a href="#L681">681</a></th><td>  Â sharee access from the shared calendar.  In each case the server MUST</td></tr><tr><th id="L682"><a href="#L682">682</a></th><td>  Â send a notification message to any sharees whose status is changed</td></tr><tr><th id="L683"><a href="#L683">683</a></th><td>  Â (added, modified or removed), indicating to them a change in status</td></tr><tr><th id="L684"><a href="#L684">684</a></th><td>  Â for the shared calendar.  The server SHOULD NOT send notification</td></tr><tr><th id="L685"><a href="#L685">685</a></th><td>  Â messages to sharees whose status is unchanged.</td></tr><tr><th id="L686"><a href="#L686">686</a></th><td></td></tr><tr><th id="L687"><a href="#L687">687</a></th><td>  Â Sharee's are identified via a DAV:href element whose value is either</td></tr><tr><th id="L688"><a href="#L688">688</a></th><td>  Â a principal-URL for a sharee hosted on the same server, a calendar</td></tr><tr><th id="L689"><a href="#L689">689</a></th><td>  Â user address or email address.  In the case of the later two, the</td></tr><tr><th id="L690"><a href="#L690">690</a></th><td>  Â sharee might not be a user on the same server - though in that case</td></tr><tr><th id="L691"><a href="#L691">691</a></th><td>  Â how invitations are sent or access enabled is out of scope for this</td></tr><tr><th id="L692"><a href="#L692">692</a></th><td>  Â specification.  A server MAY change the sharee's "address" to any</td></tr><tr><th id="L693"><a href="#L693">693</a></th><td>  Â suitable alternative that it might prefer when returning the list of</td></tr><tr><th id="L694"><a href="#L694">694</a></th><td>  Â sharees via the CS:invite property (Section 5.2.2).</td></tr><tr><th id="L695"><a href="#L695">695</a></th><td></td></tr><tr><th id="L696"><a href="#L696">696</a></th><td>  Â The client MAY include a CS:common-name (Section 6.19) element in the</td></tr><tr><th id="L697"><a href="#L697">697</a></th><td>  Â CS:set (Section 6.25) element.  When provided, the value represents</td></tr><tr><th id="L698"><a href="#L698">698</a></th><td>  Â the common name for the sharee, and is returned in the list of</td></tr><tr><th id="L699"><a href="#L699">699</a></th><td>  Â sharees via the CS:invite property (Section 5.2.2).  The server MAY</td></tr><tr><th id="L700"><a href="#L700">700</a></th><td>  Â change this to a suitable alternative when it is able to match the</td></tr><tr><th id="L701"><a href="#L701">701</a></th><td>  Â sharee to a known user.  If absent from the client request, the</td></tr><tr><th id="L702"><a href="#L702">702</a></th><td>  Â server SHOULD add a CS:common-name when it is able to match the</td></tr><tr><th id="L703"><a href="#L703">703</a></th><td>  Â sharee with a known user, and a common name for that user can be</td></tr><tr><th id="L704"><a href="#L704">704</a></th><td>  Â determined.</td></tr><tr><th id="L705"><a href="#L705">705</a></th><td></td></tr><tr><th id="L706"><a href="#L706">706</a></th><td>  Â When the sharee list on a shared calendar is changed, the server MUST</td></tr><tr><th id="L707"><a href="#L707">707</a></th><td>  Â send notifications to each sharee to update them on their current</td></tr><tr><th id="L708"><a href="#L708">708</a></th><td>  Â sharing status.  This is accomplished by sending a CS:invite-</td></tr><tr><th id="L709"><a href="#L709">709</a></th><td>  Â notification (Section 6.15) notification to each sharee.</td></tr><tr><th id="L710"><a href="#L710">710</a></th><td></td></tr><tr><th id="L711"><a href="#L711">711</a></th><td>5.3.3.1.  Example: Successful Sharee Add Request</td></tr><tr><th id="L712"><a href="#L712">712</a></th><td></td></tr><tr><th id="L713"><a href="#L713">713</a></th><td>  Â This example shows how to add a single sharee (with calendar user</td></tr><tr><th id="L714"><a href="#L714">714</a></th><td>  Â address "mailto:eric@example.com") to a shared calendar with CS:read-</td></tr><tr><th id="L715"><a href="#L715">715</a></th><td>  Â write access.</td></tr><tr><th id="L716"><a href="#L716">716</a></th><td></td></tr><tr><th id="L717"><a href="#L717">717</a></th><td></td></tr><tr><th id="L718"><a href="#L718">718</a></th><td></td></tr><tr><th id="L719"><a href="#L719">719</a></th><td></td></tr><tr><th id="L720"><a href="#L720">720</a></th><td></td></tr><tr><th id="L721"><a href="#L721">721</a></th><td></td></tr><tr><th id="L722"><a href="#L722">722</a></th><td></td></tr><tr><th id="L723"><a href="#L723">723</a></th><td></td></tr><tr><th id="L724"><a href="#L724">724</a></th><td></td></tr><tr><th id="L725"><a href="#L725">725</a></th><td></td></tr><tr><th id="L726"><a href="#L726">726</a></th><td></td></tr><tr><th id="L727"><a href="#L727">727</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 13]</td></tr><tr><th id="L728"><a href="#L728">728</a></th><td></td></tr><tr><th id="L729"><a href="#L729">729</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L730"><a href="#L730">730</a></th><td></td></tr><tr><th id="L731"><a href="#L731">731</a></th><td></td></tr><tr><th id="L732"><a href="#L732">732</a></th><td>  Â &gt;&gt; Request &lt;&lt;</td></tr><tr><th id="L733"><a href="#L733">733</a></th><td></td></tr><tr><th id="L734"><a href="#L734">734</a></th><td>  Â POST /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L735"><a href="#L735">735</a></th><td>  Â Host: calendar.example.com</td></tr><tr><th id="L736"><a href="#L736">736</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L737"><a href="#L737">737</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L738"><a href="#L738">738</a></th><td></td></tr><tr><th id="L739"><a href="#L739">739</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L740"><a href="#L740">740</a></th><td>  Â &lt;CS:share xmlns:D="DAV:"</td></tr><tr><th id="L741"><a href="#L741">741</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:CS="http://calendarserver.org/ns/"&gt;</td></tr><tr><th id="L742"><a href="#L742">742</a></th><td>  Â  Â &lt;CS:set&gt;</td></tr><tr><th id="L743"><a href="#L743">743</a></th><td>  Â  Â  Â &lt;D:href&gt;mailto:eric@example.com&lt;/D:href&gt;</td></tr><tr><th id="L744"><a href="#L744">744</a></th><td>  Â  Â  Â &lt;CS:common-name&gt;Eric York&lt;/CS:common-name&gt;</td></tr><tr><th id="L745"><a href="#L745">745</a></th><td>  Â  Â  Â &lt;CS:summary&gt;Shared workspace&lt;/CS:summary&gt;</td></tr><tr><th id="L746"><a href="#L746">746</a></th><td>  Â  Â  Â &lt;CS:read-write /&gt;</td></tr><tr><th id="L747"><a href="#L747">747</a></th><td>  Â  Â &lt;/CS:set&gt;</td></tr><tr><th id="L748"><a href="#L748">748</a></th><td>  Â &lt;/CS:share&gt;</td></tr><tr><th id="L749"><a href="#L749">749</a></th><td></td></tr><tr><th id="L750"><a href="#L750">750</a></th><td>  Â &gt;&gt; Response &lt;&lt;</td></tr><tr><th id="L751"><a href="#L751">751</a></th><td></td></tr><tr><th id="L752"><a href="#L752">752</a></th><td>  Â HTTP/1.1 200 OK</td></tr><tr><th id="L753"><a href="#L753">753</a></th><td>  Â Cache-Control: no-cache</td></tr><tr><th id="L754"><a href="#L754">754</a></th><td>  Â Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L755"><a href="#L755">755</a></th><td></td></tr><tr><th id="L756"><a href="#L756">756</a></th><td>5.3.3.2.  Example: Successful Multiple Sharee Change Request</td></tr><tr><th id="L757"><a href="#L757">757</a></th><td></td></tr><tr><th id="L758"><a href="#L758">758</a></th><td>  Â This example shows how multiple sharee's can be manipulated in a</td></tr><tr><th id="L759"><a href="#L759">759</a></th><td>  Â single request.  The sharee with calendar user address</td></tr><tr><th id="L760"><a href="#L760">760</a></th><td>  Â "mailto:eric@example.com" has their access downgraded to CS:read,</td></tr><tr><th id="L761"><a href="#L761">761</a></th><td>  Â whilst another sharee is removed from the access list entirely.</td></tr><tr><th id="L762"><a href="#L762">762</a></th><td></td></tr><tr><th id="L763"><a href="#L763">763</a></th><td></td></tr><tr><th id="L764"><a href="#L764">764</a></th><td></td></tr><tr><th id="L765"><a href="#L765">765</a></th><td></td></tr><tr><th id="L766"><a href="#L766">766</a></th><td></td></tr><tr><th id="L767"><a href="#L767">767</a></th><td></td></tr><tr><th id="L768"><a href="#L768">768</a></th><td></td></tr><tr><th id="L769"><a href="#L769">769</a></th><td></td></tr><tr><th id="L770"><a href="#L770">770</a></th><td></td></tr><tr><th id="L771"><a href="#L771">771</a></th><td></td></tr><tr><th id="L772"><a href="#L772">772</a></th><td></td></tr><tr><th id="L773"><a href="#L773">773</a></th><td></td></tr><tr><th id="L774"><a href="#L774">774</a></th><td></td></tr><tr><th id="L775"><a href="#L775">775</a></th><td></td></tr><tr><th id="L776"><a href="#L776">776</a></th><td></td></tr><tr><th id="L777"><a href="#L777">777</a></th><td></td></tr><tr><th id="L778"><a href="#L778">778</a></th><td></td></tr><tr><th id="L779"><a href="#L779">779</a></th><td></td></tr><tr><th id="L780"><a href="#L780">780</a></th><td></td></tr><tr><th id="L781"><a href="#L781">781</a></th><td></td></tr><tr><th id="L782"><a href="#L782">782</a></th><td></td></tr><tr><th id="L783"><a href="#L783">783</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 14]</td></tr><tr><th id="L784"><a href="#L784">784</a></th><td></td></tr><tr><th id="L785"><a href="#L785">785</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L786"><a href="#L786">786</a></th><td></td></tr><tr><th id="L787"><a href="#L787">787</a></th><td></td></tr><tr><th id="L788"><a href="#L788">788</a></th><td>  Â &gt;&gt; Request &lt;&lt;</td></tr><tr><th id="L789"><a href="#L789">789</a></th><td></td></tr><tr><th id="L790"><a href="#L790">790</a></th><td>  Â POST /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L791"><a href="#L791">791</a></th><td>  Â Host: calendar.example.com</td></tr><tr><th id="L792"><a href="#L792">792</a></th><td>  Â Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L793"><a href="#L793">793</a></th><td>  Â Content-Length: xxxx</td></tr><tr><th id="L794"><a href="#L794">794</a></th><td></td></tr><tr><th id="L795"><a href="#L795">795</a></th><td>  Â &lt;?xml version="1.0" encoding="utf-8" ?&gt;</td></tr><tr><th id="L796"><a href="#L796">796</a></th><td>  Â &lt;CS:share xmlns:D="DAV:"</td></tr><tr><th id="L797"><a href="#L797">797</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â xmlns:CS="http://calendarserver.org/ns/"&gt;</td></tr><tr><th id="L798"><a href="#L798">798</a></th><td>  Â  Â &lt;CS:set&gt;</td></tr><tr><th id="L799"><a href="#L799">799</a></th><td>  Â  Â  Â &lt;D:href&gt;mailto:eric@example.com&lt;/D:href&gt;</td></tr><tr><th id="L800"><a href="#L800">800</a></th><td>  Â  Â  Â &lt;CS:summary&gt;Shared workspace&lt;/CS:summary&gt;</td></tr><tr><th id="L801"><a href="#L801">801</a></th><td>  Â  Â  Â &lt;CS:read-write /&gt;</td></tr><tr><th id="L802"><a href="#L802">802</a></th><td>  Â  Â &lt;/CS:set&gt;</td></tr><tr><th id="L803"><a href="#L803">803</a></th><td>  Â  Â &lt;CS:remove&gt;</td></tr><tr><th id="L804"><a href="#L804">804</a></th><td>  Â  Â  Â &lt;D:href&gt;mailto:wilfredo@example.com&lt;/D:href&gt;</td></tr><tr><th id="L805"><a href="#L805">805</a></th><td>  Â  Â &lt;/CS:remove&gt;</td></tr><tr><th id="L806"><a href="#L806">806</a></th><td>  Â &lt;/CS:share&gt;</td></tr><tr><th id="L807"><a href="#L807">807</a></th><td></td></tr><tr><th id="L808"><a href="#L808">808</a></th><td>  Â &gt;&gt; Response &lt;&lt;</td></tr><tr><th id="L809"><a href="#L809">809</a></th><td></td></tr><tr><th id="L810"><a href="#L810">810</a></th><td>  Â HTTP/1.1 200 OK</td></tr><tr><th id="L811"><a href="#L811">811</a></th><td>  Â Cache-Control: no-cache</td></tr><tr><th id="L812"><a href="#L812">812</a></th><td>  Â Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L813"><a href="#L813">813</a></th><td></td></tr><tr><th id="L814"><a href="#L814">814</a></th><td>5.4.  Sharee Actions on Shared Calendars</td></tr><tr><th id="L815"><a href="#L815">815</a></th><td></td></tr><tr><th id="L816"><a href="#L816">816</a></th><td>5.4.1.  Replying to a Sharing Invite</td></tr><tr><th id="L817"><a href="#L817">817</a></th><td></td></tr><tr><th id="L818"><a href="#L818">818</a></th><td>  Â When a sharee is invited to a shared calendar they can accept or</td></tr><tr><th id="L819"><a href="#L819">819</a></th><td>  Â decline the invite by issuing a POST request to the sharee's calendar</td></tr><tr><th id="L820"><a href="#L820">820</a></th><td>  Â home collection resource.  The POST request MUST contain an XML</td></tr><tr><th id="L821"><a href="#L821">821</a></th><td>  Â document as its body with the root element being CS:invite-reply</td></tr><tr><th id="L822"><a href="#L822">822</a></th><td>  Â (Section 6.20).</td></tr><tr><th id="L823"><a href="#L823">823</a></th><td></td></tr><tr><th id="L824"><a href="#L824">824</a></th><td>  Â The CS:invite-reply (Section 6.20) element in the POST request</td></tr><tr><th id="L825"><a href="#L825">825</a></th><td>  Â specifies the sharee who is replying in the DAV:href element, the</td></tr><tr><th id="L826"><a href="#L826">826</a></th><td>  Â accept or decline action via the CS:invite-accepted or CS:invite-</td></tr><tr><th id="L827"><a href="#L827">827</a></th><td>  Â declined elements, the URL of the shared calendar in the CS:hosturl</td></tr><tr><th id="L828"><a href="#L828">828</a></th><td>  Â element, the unique identifier of the invite to which it is a reply</td></tr><tr><th id="L829"><a href="#L829">829</a></th><td>  Â in the CS:in-reply-to element, and an optional CS:summary element.</td></tr><tr><th id="L830"><a href="#L830">830</a></th><td></td></tr><tr><th id="L831"><a href="#L831">831</a></th><td>  Â The response to a POST request that accepts a shared calendar invite</td></tr><tr><th id="L832"><a href="#L832">832</a></th><td>  Â MUST be an XML document containing CS:shared-as (Section 6.27) as its</td></tr><tr><th id="L833"><a href="#L833">833</a></th><td>  Â root element.  That root element contains a single DAV:href element</td></tr><tr><th id="L834"><a href="#L834">834</a></th><td>  Â whose content is the URI of the shared calendar in the sharee's</td></tr><tr><th id="L835"><a href="#L835">835</a></th><td>  Â calendar home created by the invite acceptance.</td></tr><tr><th id="L836"><a href="#L836">836</a></th><td></td></tr><tr><th id="L837"><a href="#L837">837</a></th><td></td></tr><tr><th id="L838"><a href="#L838">838</a></th><td></td></tr><tr><th id="L839"><a href="#L839">839</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 15]</td></tr><tr><th id="L840"><a href="#L840">840</a></th><td></td></tr><tr><th id="L841"><a href="#L841">841</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L842"><a href="#L842">842</a></th><td></td></tr><tr><th id="L843"><a href="#L843">843</a></th><td></td></tr><tr><th id="L844"><a href="#L844">844</a></th><td>  Â When the sharee replies to an invite, the server SHOULD send a</td></tr><tr><th id="L845"><a href="#L845">845</a></th><td>  Â notification to the sharer to update them on the change in the sharee</td></tr><tr><th id="L846"><a href="#L846">846</a></th><td>  Â state.  This is accomplished by sending a CS:invite-reply</td></tr><tr><th id="L847"><a href="#L847">847</a></th><td>  Â (Section 6.20) notification to the sharer.</td></tr><tr><th id="L848"><a href="#L848">848</a></th><td></td></tr><tr><th id="L849"><a href="#L849">849</a></th><td>5.4.2.  Removing a Shared Calendar</td></tr><tr><th id="L850"><a href="#L850">850</a></th><td></td></tr><tr><th id="L851"><a href="#L851">851</a></th><td>  Â To remove a shared calendar from a sharee's calendar home collection</td></tr><tr><th id="L852"><a href="#L852">852</a></th><td>  Â a DELETE request is targeted at the shared calendar URI.  When such a</td></tr><tr><th id="L853"><a href="#L853">853</a></th><td>  Â request is received the server MUST remove the shared calendar from</td></tr><tr><th id="L854"><a href="#L854">854</a></th><td>  Â the sharee's calendar home and automatically update the sharee's</td></tr><tr><th id="L855"><a href="#L855">855</a></th><td>  Â status in the sharer's calendar's CS:invite property.</td></tr><tr><th id="L856"><a href="#L856">856</a></th><td></td></tr><tr><th id="L857"><a href="#L857">857</a></th><td>5.5.  General Considerations</td></tr><tr><th id="L858"><a href="#L858">858</a></th><td></td></tr><tr><th id="L859"><a href="#L859">859</a></th><td>5.5.1.  Access Levels</td></tr><tr><th id="L860"><a href="#L860">860</a></th><td></td></tr><tr><th id="L861"><a href="#L861">861</a></th><td>  Â Two levels of access ca be granted by a sharer to any sharee.  These</td></tr><tr><th id="L862"><a href="#L862">862</a></th><td>  Â are governed by the CS:access element used in the CS:invite/CS:user</td></tr><tr><th id="L863"><a href="#L863">863</a></th><td>  Â element that specifies a shared user invite.  CS:access contains a</td></tr><tr><th id="L864"><a href="#L864">864</a></th><td>  Â single empty element that defines the type of access granted:</td></tr><tr><th id="L865"><a href="#L865">865</a></th><td></td></tr><tr><th id="L866"><a href="#L866">866</a></th><td>  Â CS:read  When present this indicates that sharees can read calendar</td></tr><tr><th id="L867"><a href="#L867">867</a></th><td>  Â  Â  data but cannot change it.</td></tr><tr><th id="L868"><a href="#L868">868</a></th><td></td></tr><tr><th id="L869"><a href="#L869">869</a></th><td>  Â CS:read-write  When present this indicates that sharees can read and</td></tr><tr><th id="L870"><a href="#L870">870</a></th><td>  Â  Â  write calendar data.</td></tr><tr><th id="L871"><a href="#L871">871</a></th><td></td></tr><tr><th id="L872"><a href="#L872">872</a></th><td>5.5.2.  Allowing or Disallowing Sharing</td></tr><tr><th id="L873"><a href="#L873">873</a></th><td></td></tr><tr><th id="L874"><a href="#L874">874</a></th><td>  Â Servers MAY support calendar sharing on a per-calendar basis - e.g.,</td></tr><tr><th id="L875"><a href="#L875">875</a></th><td>  Â they could treat some calendars as always private (cannot be shared)</td></tr><tr><th id="L876"><a href="#L876">876</a></th><td>  Â or always public (always shared).  As a result clients need a way to</td></tr><tr><th id="L877"><a href="#L877">877</a></th><td>  Â determine which calendar could be shared so they can enable or</td></tr><tr><th id="L878"><a href="#L878">878</a></th><td>  Â disable sharing options on a per-calendar basis.</td></tr><tr><th id="L879"><a href="#L879">879</a></th><td></td></tr><tr><th id="L880"><a href="#L880">880</a></th><td>  Â This specification adds a CS:allowed-sharing-modes (Section 5.2.3)</td></tr><tr><th id="L881"><a href="#L881">881</a></th><td>  Â WebDAV property which servers can return on calendar collection</td></tr><tr><th id="L882"><a href="#L882">882</a></th><td>  Â resources.  This property contains XML elements that describe which</td></tr><tr><th id="L883"><a href="#L883">883</a></th><td>  Â sharing or publishing capabilities can be supported by the</td></tr><tr><th id="L884"><a href="#L884">884</a></th><td>  Â corresponding calendar collection:</td></tr><tr><th id="L885"><a href="#L885">885</a></th><td></td></tr><tr><th id="L886"><a href="#L886">886</a></th><td>  Â  Â  CS:can-be-shared (Section 6.3): when present indicates that the</td></tr><tr><th id="L887"><a href="#L887">887</a></th><td>  Â  Â  calendar collection can be shared.  When not present, the calendar</td></tr><tr><th id="L888"><a href="#L888">888</a></th><td>  Â  Â  collection cannot be shared.</td></tr><tr><th id="L889"><a href="#L889">889</a></th><td></td></tr><tr><th id="L890"><a href="#L890">890</a></th><td>  Â  Â  CS:can-be-published (Section 6.4): when present indicates that the</td></tr><tr><th id="L891"><a href="#L891">891</a></th><td>  Â  Â  calendar collection can be published.  When not present, the</td></tr><tr><th id="L892"><a href="#L892">892</a></th><td></td></tr><tr><th id="L893"><a href="#L893">893</a></th><td></td></tr><tr><th id="L894"><a href="#L894">894</a></th><td></td></tr><tr><th id="L895"><a href="#L895">895</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 16]</td></tr><tr><th id="L896"><a href="#L896">896</a></th><td></td></tr><tr><th id="L897"><a href="#L897">897</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L898"><a href="#L898">898</a></th><td></td></tr><tr><th id="L899"><a href="#L899">899</a></th><td></td></tr><tr><th id="L900"><a href="#L900">900</a></th><td>  Â  Â  calendar collection cannot be published.</td></tr><tr><th id="L901"><a href="#L901">901</a></th><td></td></tr><tr><th id="L902"><a href="#L902">902</a></th><td>  Â When not present on a calendar collection, sharing or publishing of</td></tr><tr><th id="L903"><a href="#L903">903</a></th><td>  Â that calendar is not allowed.  Clients SHOULD NOT attempt to use</td></tr><tr><th id="L904"><a href="#L904">904</a></th><td>  Â requests to enable sharing or publishing targeted at those calendar</td></tr><tr><th id="L905"><a href="#L905">905</a></th><td>  Â collections.</td></tr><tr><th id="L906"><a href="#L906">906</a></th><td></td></tr><tr><th id="L907"><a href="#L907">907</a></th><td>5.5.3.  Per-user WebDAV Properties</td></tr><tr><th id="L908"><a href="#L908">908</a></th><td></td></tr><tr><th id="L909"><a href="#L909">909</a></th><td>  Â Servers MUST support "per-user" WebDAV properties on shared calendar</td></tr><tr><th id="L910"><a href="#L910">910</a></th><td>  Â collections and MAY support them on calendar object resources within</td></tr><tr><th id="L911"><a href="#L911">911</a></th><td>  Â shared calendar collections.  A "per-user" WebDAV property is one</td></tr><tr><th id="L912"><a href="#L912">912</a></th><td>  Â whose value can be set and retrieved independently by each user with</td></tr><tr><th id="L913"><a href="#L913">913</a></th><td>  Â appropriate access rights. e.g., user "A" changes the DAV:displayname</td></tr><tr><th id="L914"><a href="#L914">914</a></th><td>  Â property on a shared calendar in their calendar home to "My</td></tr><tr><th id="L915"><a href="#L915">915</a></th><td>  Â calendar", and user "B" changes the same property to "Shared" on the</td></tr><tr><th id="L916"><a href="#L916">916</a></th><td>  Â same shared calendar in their calendar home.  When each user</td></tr><tr><th id="L917"><a href="#L917">917</a></th><td>  Â retrieves the property value they will see their own last stored</td></tr><tr><th id="L918"><a href="#L918">918</a></th><td>  Â value and not the value of the other user.</td></tr><tr><th id="L919"><a href="#L919">919</a></th><td></td></tr><tr><th id="L920"><a href="#L920">920</a></th><td>  Â For shared calendars, the server MUST allow all users to write "per-</td></tr><tr><th id="L921"><a href="#L921">921</a></th><td>  Â user" WebDAV properties on the shared calendar collection and MAY</td></tr><tr><th id="L922"><a href="#L922">922</a></th><td>  Â allow property writes on calendar object resources within the shared</td></tr><tr><th id="L923"><a href="#L923">923</a></th><td>  Â calendar collection.  This is required even in the case where the</td></tr><tr><th id="L924"><a href="#L924">924</a></th><td>  Â sharee has been granted read access only (i.e., the ability to change</td></tr><tr><th id="L925"><a href="#L925">925</a></th><td>  Â calendar data is disallowed).  This requirement ensures that sharees</td></tr><tr><th id="L926"><a href="#L926">926</a></th><td>  Â can always change "personal" properties such as calendar colors and</td></tr><tr><th id="L927"><a href="#L927">927</a></th><td>  Â display names.</td></tr><tr><th id="L928"><a href="#L928">928</a></th><td></td></tr><tr><th id="L929"><a href="#L929">929</a></th><td>  Â Servers MUST treat the following properties as "per-user":</td></tr><tr><th id="L930"><a href="#L930">930</a></th><td></td></tr><tr><th id="L931"><a href="#L931">931</a></th><td>  Â  Â  DAV:displayname</td></tr><tr><th id="L932"><a href="#L932">932</a></th><td></td></tr><tr><th id="L933"><a href="#L933">933</a></th><td>  Â  Â  CALDAV:calendar-description</td></tr><tr><th id="L934"><a href="#L934">934</a></th><td></td></tr><tr><th id="L935"><a href="#L935">935</a></th><td>  Â  Â  CALDAV:schedule-calendar-transp</td></tr><tr><th id="L936"><a href="#L936">936</a></th><td></td></tr><tr><th id="L937"><a href="#L937">937</a></th><td>  Â  Â  ICAL:calendar-color</td></tr><tr><th id="L938"><a href="#L938">938</a></th><td></td></tr><tr><th id="L939"><a href="#L939">939</a></th><td>  Â Servers MAY treat any dead property as per-user.</td></tr><tr><th id="L940"><a href="#L940">940</a></th><td></td></tr><tr><th id="L941"><a href="#L941">941</a></th><td>  Â Servers MUST NOT treat live properties as per-user.</td></tr><tr><th id="L942"><a href="#L942">942</a></th><td></td></tr><tr><th id="L943"><a href="#L943">943</a></th><td>5.5.4.  Per-user Calendar Data</td></tr><tr><th id="L944"><a href="#L944">944</a></th><td></td></tr><tr><th id="L945"><a href="#L945">945</a></th><td>  Â Servers MUST support "per-user" calendar data in calendar object</td></tr><tr><th id="L946"><a href="#L946">946</a></th><td>  Â resources stored in shared calendars.  This allows each sharee and</td></tr><tr><th id="L947"><a href="#L947">947</a></th><td>  Â the sharer to store their own alarms and free busy transparency</td></tr><tr><th id="L948"><a href="#L948">948</a></th><td></td></tr><tr><th id="L949"><a href="#L949">949</a></th><td></td></tr><tr><th id="L950"><a href="#L950">950</a></th><td></td></tr><tr><th id="L951"><a href="#L951">951</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 17]</td></tr><tr><th id="L952"><a href="#L952">952</a></th><td></td></tr><tr><th id="L953"><a href="#L953">953</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L954"><a href="#L954">954</a></th><td></td></tr><tr><th id="L955"><a href="#L955">955</a></th><td></td></tr><tr><th id="L956"><a href="#L956">956</a></th><td>  Â status without "interfering" with other users who also have access to</td></tr><tr><th id="L957"><a href="#L957">957</a></th><td>  Â the same calendar object resources.</td></tr><tr><th id="L958"><a href="#L958">958</a></th><td></td></tr><tr><th id="L959"><a href="#L959">959</a></th><td>  Â For calendaring object resources in shared calendar collections, the</td></tr><tr><th id="L960"><a href="#L960">960</a></th><td>  Â server MUST treat the following iCalendar data objects as per-user:</td></tr><tr><th id="L961"><a href="#L961">961</a></th><td></td></tr><tr><th id="L962"><a href="#L962">962</a></th><td>  Â  Â  TRANSP property</td></tr><tr><th id="L963"><a href="#L963">963</a></th><td></td></tr><tr><th id="L964"><a href="#L964">964</a></th><td>  Â  Â  VALARM component</td></tr><tr><th id="L965"><a href="#L965">965</a></th><td></td></tr><tr><th id="L966"><a href="#L966">966</a></th><td>  Â Servers MAY treat any non-standard X- iCalendar properties as per-</td></tr><tr><th id="L967"><a href="#L967">967</a></th><td>  Â user.</td></tr><tr><th id="L968"><a href="#L968">968</a></th><td></td></tr><tr><th id="L969"><a href="#L969">969</a></th><td>  Â When handling per-user data in recurring components, servers SHOULD</td></tr><tr><th id="L970"><a href="#L970">970</a></th><td>  Â eliminate overridden instances when returning iCalendar data to</td></tr><tr><th id="L971"><a href="#L971">971</a></th><td>  Â clients in the case where there are no differences between the</td></tr><tr><th id="L972"><a href="#L972">972</a></th><td>  Â overridden component and the instance that could be derived from the</td></tr><tr><th id="L973"><a href="#L973">973</a></th><td>  Â "master" recurrence component.  For example, consider a daily</td></tr><tr><th id="L974"><a href="#L974">974</a></th><td>  Â recurring event, Monday through Friday, initially defined without any</td></tr><tr><th id="L975"><a href="#L975">975</a></th><td>  Â overridden instances, that is in a shared calendar.  If user "A"</td></tr><tr><th id="L976"><a href="#L976">976</a></th><td>  Â overrides the Tuesday instance and adds their own "VALARM" component</td></tr><tr><th id="L977"><a href="#L977">977</a></th><td>  Â only, then when user "A" later retrieves the data again they would</td></tr><tr><th id="L978"><a href="#L978">978</a></th><td>  Â see that overridden instance, but when user "B" does so, they would</td></tr><tr><th id="L979"><a href="#L979">979</a></th><td>  Â not.  This ensures that each user sees the most "compact"</td></tr><tr><th id="L980"><a href="#L980">980</a></th><td>  Â representation of the calendar data.</td></tr><tr><th id="L981"><a href="#L981">981</a></th><td></td></tr><tr><th id="L982"><a href="#L982">982</a></th><td>5.5.5.  Scheduling</td></tr><tr><th id="L983"><a href="#L983">983</a></th><td></td></tr><tr><th id="L984"><a href="#L984">984</a></th><td>  Â CalDAV Scheduling [I-D.desruisseaux-caldav-sched] defines how a</td></tr><tr><th id="L985"><a href="#L985">985</a></th><td>  Â CalDAV server carries out scheduling operations when calendar object</td></tr><tr><th id="L986"><a href="#L986">986</a></th><td>  Â resources are created, modified or deleted and include "ORGANIZER"</td></tr><tr><th id="L987"><a href="#L987">987</a></th><td>  Â and "ATTENDEE" iCalendar properties.</td></tr><tr><th id="L988"><a href="#L988">988</a></th><td></td></tr><tr><th id="L989"><a href="#L989">989</a></th><td>  Â When calendar object resources are created, modified or deleted in</td></tr><tr><th id="L990"><a href="#L990">990</a></th><td>  Â shared calendars by sharees, the following restrictions apply:</td></tr><tr><th id="L991"><a href="#L991">991</a></th><td></td></tr><tr><th id="L992"><a href="#L992">992</a></th><td>  Â 1.  The "ORGANIZER" iCalendar property value in the iCalendar data</td></tr><tr><th id="L993"><a href="#L993">993</a></th><td>  Â  Â  Â MUST match a calendar user address of the sharer (owner) of the</td></tr><tr><th id="L994"><a href="#L994">994</a></th><td>  Â  Â  Â shared calendar.  The DAV:owner WebDAV property MUST be present</td></tr><tr><th id="L995"><a href="#L995">995</a></th><td>  Â  Â  Â on a shared calendar and MUST provide a reference to a principal-</td></tr><tr><th id="L996"><a href="#L996">996</a></th><td>  Â  Â  Â URL of the sharer (owner) of the shared calendar.  Clients can</td></tr><tr><th id="L997"><a href="#L997">997</a></th><td>  Â  Â  Â use this value to determine what the allowed "ORGANIZER"</td></tr><tr><th id="L998"><a href="#L998">998</a></th><td>  Â  Â  Â iCalendar property values are.  The server MUST reject any</td></tr><tr><th id="L999"><a href="#L999">999</a></th><td>  Â  Â  Â attempt by a sharee to create an iCalendar component with an</td></tr><tr><th id="L1000"><a href="#L1000">1000</a></th><td>  Â  Â  Â "ORGANIZER" property value other than the sharer (owner) of the</td></tr><tr><th id="L1001"><a href="#L1001">1001</a></th><td>  Â  Â  Â shared calendar.</td></tr><tr><th id="L1002"><a href="#L1002">1002</a></th><td></td></tr><tr><th id="L1003"><a href="#L1003">1003</a></th><td></td></tr><tr><th id="L1004"><a href="#L1004">1004</a></th><td></td></tr><tr><th id="L1005"><a href="#L1005">1005</a></th><td></td></tr><tr><th id="L1006"><a href="#L1006">1006</a></th><td></td></tr><tr><th id="L1007"><a href="#L1007">1007</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 18]</td></tr><tr><th id="L1008"><a href="#L1008">1008</a></th><td></td></tr><tr><th id="L1009"><a href="#L1009">1009</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1010"><a href="#L1010">1010</a></th><td></td></tr><tr><th id="L1011"><a href="#L1011">1011</a></th><td></td></tr><tr><th id="L1012"><a href="#L1012">1012</a></th><td>  Â 2.  The server MUST reject any attempt by a sharee to MOVE a calendar</td></tr><tr><th id="L1013"><a href="#L1013">1013</a></th><td>  Â  Â  Â object resource in a shared calendar to some other collection.</td></tr><tr><th id="L1014"><a href="#L1014">1014</a></th><td></td></tr><tr><th id="L1015"><a href="#L1015">1015</a></th><td>  Â 3.  When a sharee is listed as an Attendee in a calendar object</td></tr><tr><th id="L1016"><a href="#L1016">1016</a></th><td>  Â  Â  Â resource in a shared calendar, and write access is granted, the</td></tr><tr><th id="L1017"><a href="#L1017">1017</a></th><td>  Â  Â  Â sharee is allowed to change not only iCalendar data related to</td></tr><tr><th id="L1018"><a href="#L1018">1018</a></th><td>  Â  Â  Â the Organizer, but also data related to the Attendee. i.e., a</td></tr><tr><th id="L1019"><a href="#L1019">1019</a></th><td>  Â  Â  Â sharee can change their own participation status on the</td></tr><tr><th id="L1020"><a href="#L1020">1020</a></th><td>  Â  Â  Â "ATTENDEE" iCalendar property referring to them.  Additionally,</td></tr><tr><th id="L1021"><a href="#L1021">1021</a></th><td>  Â  Â  Â if the sharee is not listed as an Attendee, and write access is</td></tr><tr><th id="L1022"><a href="#L1022">1022</a></th><td>  Â  Â  Â granted, the sharee can add themselves as an Attendee.</td></tr><tr><th id="L1023"><a href="#L1023">1023</a></th><td></td></tr><tr><th id="L1024"><a href="#L1024">1024</a></th><td>  Â 4.  The default calendar collection defined in Section 6.3 of</td></tr><tr><th id="L1025"><a href="#L1025">1025</a></th><td>  Â  Â  Â [I-D.desruisseaux-caldav-sched] MUST NOT be a calendar shared to</td></tr><tr><th id="L1026"><a href="#L1026">1026</a></th><td>  Â  Â  Â the corresponding calendar user.</td></tr><tr><th id="L1027"><a href="#L1027">1027</a></th><td></td></tr><tr><th id="L1028"><a href="#L1028">1028</a></th><td>  Â Following are additional considerations for scheduling with shared</td></tr><tr><th id="L1029"><a href="#L1029">1029</a></th><td>  Â calendars:</td></tr><tr><th id="L1030"><a href="#L1030">1030</a></th><td></td></tr><tr><th id="L1031"><a href="#L1031">1031</a></th><td>  Â 1.  A scheduled iCalendar component could appear in more than one</td></tr><tr><th id="L1032"><a href="#L1032">1032</a></th><td>  Â  Â  Â calendar collection within a sharee's calendar home if the sharee</td></tr><tr><th id="L1033"><a href="#L1033">1033</a></th><td>  Â  Â  Â is an Attendee and the Organizer or other Attendees have shared a</td></tr><tr><th id="L1034"><a href="#L1034">1034</a></th><td>  Â  Â  Â calendar with the sharee that includes their copies of the</td></tr><tr><th id="L1035"><a href="#L1035">1035</a></th><td>  Â  Â  Â iCalendar component.  It is important to note that the scheduled</td></tr><tr><th id="L1036"><a href="#L1036">1036</a></th><td>  Â  Â  Â component in the shared calendar could have different access</td></tr><tr><th id="L1037"><a href="#L1037">1037</a></th><td>  Â  Â  Â rights than the one in the sharee's owned calendar.</td></tr><tr><th id="L1038"><a href="#L1038">1038</a></th><td></td></tr><tr><th id="L1039"><a href="#L1039">1039</a></th><td>  Â 2.  A scheduled iCalendar component appearing in a sharee's shared</td></tr><tr><th id="L1040"><a href="#L1040">1040</a></th><td>  Â  Â  Â calendar could include the sharee as an Attendee.  For recurring</td></tr><tr><th id="L1041"><a href="#L1041">1041</a></th><td>  Â  Â  Â events, it is possible for the sharee to only be listed as an</td></tr><tr><th id="L1042"><a href="#L1042">1042</a></th><td>  Â  Â  Â Attendee in some instances, as opposed to all.  Clients will need</td></tr><tr><th id="L1043"><a href="#L1043">1043</a></th><td>  Â  Â  Â to be aware of this when allowing sharee's to set their own</td></tr><tr><th id="L1044"><a href="#L1044">1044</a></th><td>  Â  Â  Â participation status.</td></tr><tr><th id="L1045"><a href="#L1045">1045</a></th><td></td></tr><tr><th id="L1046"><a href="#L1046">1046</a></th><td>  Â In addition, when a shared calendar is first accepted by a sharee,</td></tr><tr><th id="L1047"><a href="#L1047">1047</a></th><td>  Â the server SHOULD set the CALDAV:schedule-calendar-transp property to</td></tr><tr><th id="L1048"><a href="#L1048">1048</a></th><td>  Â the value CALDAV:transparent to ensure newly accepted shared</td></tr><tr><th id="L1049"><a href="#L1049">1049</a></th><td>  Â calendars do not contribute to the sharee's freebusy time until the</td></tr><tr><th id="L1050"><a href="#L1050">1050</a></th><td>  Â sharee explicitly requests it.</td></tr><tr><th id="L1051"><a href="#L1051">1051</a></th><td></td></tr><tr><th id="L1052"><a href="#L1052">1052</a></th><td></td></tr><tr><th id="L1053"><a href="#L1053">1053</a></th><td>6.  XML Element Definitions</td></tr><tr><th id="L1054"><a href="#L1054">1054</a></th><td></td></tr><tr><th id="L1055"><a href="#L1055">1055</a></th><td>6.1.  CS:shared-owner</td></tr><tr><th id="L1056"><a href="#L1056">1056</a></th><td></td></tr><tr><th id="L1057"><a href="#L1057">1057</a></th><td></td></tr><tr><th id="L1058"><a href="#L1058">1058</a></th><td></td></tr><tr><th id="L1059"><a href="#L1059">1059</a></th><td></td></tr><tr><th id="L1060"><a href="#L1060">1060</a></th><td></td></tr><tr><th id="L1061"><a href="#L1061">1061</a></th><td></td></tr><tr><th id="L1062"><a href="#L1062">1062</a></th><td></td></tr><tr><th id="L1063"><a href="#L1063">1063</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 19]</td></tr><tr><th id="L1064"><a href="#L1064">1064</a></th><td></td></tr><tr><th id="L1065"><a href="#L1065">1065</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1066"><a href="#L1066">1066</a></th><td></td></tr><tr><th id="L1067"><a href="#L1067">1067</a></th><td></td></tr><tr><th id="L1068"><a href="#L1068">1068</a></th><td>  Â Name:  shared-owner</td></tr><tr><th id="L1069"><a href="#L1069">1069</a></th><td></td></tr><tr><th id="L1070"><a href="#L1070">1070</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1071"><a href="#L1071">1071</a></th><td></td></tr><tr><th id="L1072"><a href="#L1072">1072</a></th><td>  Â Purpose:  Used to indicate that a calendar is being shared by the</td></tr><tr><th id="L1073"><a href="#L1073">1073</a></th><td>  Â  Â  owner.</td></tr><tr><th id="L1074"><a href="#L1074">1074</a></th><td></td></tr><tr><th id="L1075"><a href="#L1075">1075</a></th><td>  Â Description:  This property appears in the DAV:resourcetype property</td></tr><tr><th id="L1076"><a href="#L1076">1076</a></th><td>  Â  Â  on the calendar collection resource shared by a sharer.  See</td></tr><tr><th id="L1077"><a href="#L1077">1077</a></th><td>  Â  Â  Section 5.2.</td></tr><tr><th id="L1078"><a href="#L1078">1078</a></th><td></td></tr><tr><th id="L1079"><a href="#L1079">1079</a></th><td>  Â Definition:</td></tr><tr><th id="L1080"><a href="#L1080">1080</a></th><td></td></tr><tr><th id="L1081"><a href="#L1081">1081</a></th><td>  Â &lt;!ELEMENT shared-owner EMPTY&gt;</td></tr><tr><th id="L1082"><a href="#L1082">1082</a></th><td></td></tr><tr><th id="L1083"><a href="#L1083">1083</a></th><td>6.2.  CS:shared</td></tr><tr><th id="L1084"><a href="#L1084">1084</a></th><td></td></tr><tr><th id="L1085"><a href="#L1085">1085</a></th><td>  Â Name:  shared</td></tr><tr><th id="L1086"><a href="#L1086">1086</a></th><td></td></tr><tr><th id="L1087"><a href="#L1087">1087</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1088"><a href="#L1088">1088</a></th><td></td></tr><tr><th id="L1089"><a href="#L1089">1089</a></th><td>  Â Purpose:  Used to indicate that a calendar is being shared to a</td></tr><tr><th id="L1090"><a href="#L1090">1090</a></th><td>  Â  Â  sharee.</td></tr><tr><th id="L1091"><a href="#L1091">1091</a></th><td></td></tr><tr><th id="L1092"><a href="#L1092">1092</a></th><td>  Â Description:  This property appears in the DAV:resourcetype property</td></tr><tr><th id="L1093"><a href="#L1093">1093</a></th><td>  Â  Â  on a calendar collection resource that is shared to a sharee and</td></tr><tr><th id="L1094"><a href="#L1094">1094</a></th><td>  Â  Â  appears in the sharee's calendar home collection.  See</td></tr><tr><th id="L1095"><a href="#L1095">1095</a></th><td>  Â  Â  Section 5.2.</td></tr><tr><th id="L1096"><a href="#L1096">1096</a></th><td></td></tr><tr><th id="L1097"><a href="#L1097">1097</a></th><td>  Â Definition:</td></tr><tr><th id="L1098"><a href="#L1098">1098</a></th><td></td></tr><tr><th id="L1099"><a href="#L1099">1099</a></th><td>  Â &lt;!ELEMENT shared EMPTY&gt;</td></tr><tr><th id="L1100"><a href="#L1100">1100</a></th><td></td></tr><tr><th id="L1101"><a href="#L1101">1101</a></th><td>6.3.  CS:can-be-shared</td></tr><tr><th id="L1102"><a href="#L1102">1102</a></th><td></td></tr><tr><th id="L1103"><a href="#L1103">1103</a></th><td>  Â Name:  can-be-shared</td></tr><tr><th id="L1104"><a href="#L1104">1104</a></th><td></td></tr><tr><th id="L1105"><a href="#L1105">1105</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1106"><a href="#L1106">1106</a></th><td></td></tr><tr><th id="L1107"><a href="#L1107">1107</a></th><td>  Â Purpose:  Used to indicate that a calendar can be shared.</td></tr><tr><th id="L1108"><a href="#L1108">1108</a></th><td></td></tr><tr><th id="L1109"><a href="#L1109">1109</a></th><td>  Â Description:  This element indicates that a calendar can be shared</td></tr><tr><th id="L1110"><a href="#L1110">1110</a></th><td>  Â  Â  with other users.  See Section 5.2.3</td></tr><tr><th id="L1111"><a href="#L1111">1111</a></th><td></td></tr><tr><th id="L1112"><a href="#L1112">1112</a></th><td>  Â Definition:</td></tr><tr><th id="L1113"><a href="#L1113">1113</a></th><td></td></tr><tr><th id="L1114"><a href="#L1114">1114</a></th><td>  Â &lt;!ELEMENT can-be-shared EMPTY&gt;</td></tr><tr><th id="L1115"><a href="#L1115">1115</a></th><td></td></tr><tr><th id="L1116"><a href="#L1116">1116</a></th><td></td></tr><tr><th id="L1117"><a href="#L1117">1117</a></th><td></td></tr><tr><th id="L1118"><a href="#L1118">1118</a></th><td></td></tr><tr><th id="L1119"><a href="#L1119">1119</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 20]</td></tr><tr><th id="L1120"><a href="#L1120">1120</a></th><td></td></tr><tr><th id="L1121"><a href="#L1121">1121</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1122"><a href="#L1122">1122</a></th><td></td></tr><tr><th id="L1123"><a href="#L1123">1123</a></th><td></td></tr><tr><th id="L1124"><a href="#L1124">1124</a></th><td>6.4.  CS:can-be-published</td></tr><tr><th id="L1125"><a href="#L1125">1125</a></th><td></td></tr><tr><th id="L1126"><a href="#L1126">1126</a></th><td>  Â Name:  can-be-published</td></tr><tr><th id="L1127"><a href="#L1127">1127</a></th><td></td></tr><tr><th id="L1128"><a href="#L1128">1128</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1129"><a href="#L1129">1129</a></th><td></td></tr><tr><th id="L1130"><a href="#L1130">1130</a></th><td>  Â Purpose:  Used to indicate that a calendar can be published.</td></tr><tr><th id="L1131"><a href="#L1131">1131</a></th><td></td></tr><tr><th id="L1132"><a href="#L1132">1132</a></th><td>  Â Description:  This element indicates that a calendar can be published</td></tr><tr><th id="L1133"><a href="#L1133">1133</a></th><td>  Â  Â  to anyone.  See Section 5.2.3</td></tr><tr><th id="L1134"><a href="#L1134">1134</a></th><td></td></tr><tr><th id="L1135"><a href="#L1135">1135</a></th><td>  Â Definition:</td></tr><tr><th id="L1136"><a href="#L1136">1136</a></th><td></td></tr><tr><th id="L1137"><a href="#L1137">1137</a></th><td>  Â &lt;!ELEMENT can-be-published EMPTY&gt;</td></tr><tr><th id="L1138"><a href="#L1138">1138</a></th><td></td></tr><tr><th id="L1139"><a href="#L1139">1139</a></th><td>6.5.  CS:user</td></tr><tr><th id="L1140"><a href="#L1140">1140</a></th><td></td></tr><tr><th id="L1141"><a href="#L1141">1141</a></th><td>  Â Name:  user</td></tr><tr><th id="L1142"><a href="#L1142">1142</a></th><td></td></tr><tr><th id="L1143"><a href="#L1143">1143</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1144"><a href="#L1144">1144</a></th><td></td></tr><tr><th id="L1145"><a href="#L1145">1145</a></th><td>  Â Purpose:  Used to show status of sharing invites sent to sharees.</td></tr><tr><th id="L1146"><a href="#L1146">1146</a></th><td></td></tr><tr><th id="L1147"><a href="#L1147">1147</a></th><td>  Â Description:  This element provides the "status" of a sharing invite</td></tr><tr><th id="L1148"><a href="#L1148">1148</a></th><td>  Â  Â  sent to a particular user.  See Section 5.2.2.</td></tr><tr><th id="L1149"><a href="#L1149">1149</a></th><td></td></tr><tr><th id="L1150"><a href="#L1150">1150</a></th><td>  Â Definition:</td></tr><tr><th id="L1151"><a href="#L1151">1151</a></th><td></td></tr><tr><th id="L1152"><a href="#L1152">1152</a></th><td>  Â &lt;!ELEMENT user (DAV:href, common-name?, (invite-noresponse |</td></tr><tr><th id="L1153"><a href="#L1153">1153</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â invite-accepted | invite-declined | invite-invalid),</td></tr><tr><th id="L1154"><a href="#L1154">1154</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â access, summary?)&gt;</td></tr><tr><th id="L1155"><a href="#L1155">1155</a></th><td></td></tr><tr><th id="L1156"><a href="#L1156">1156</a></th><td>6.6.  CS:invite-noresponse</td></tr><tr><th id="L1157"><a href="#L1157">1157</a></th><td></td></tr><tr><th id="L1158"><a href="#L1158">1158</a></th><td>  Â Name:  invite-noresponse</td></tr><tr><th id="L1159"><a href="#L1159">1159</a></th><td></td></tr><tr><th id="L1160"><a href="#L1160">1160</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1161"><a href="#L1161">1161</a></th><td></td></tr><tr><th id="L1162"><a href="#L1162">1162</a></th><td>  Â Purpose:  Sharing invite status.</td></tr><tr><th id="L1163"><a href="#L1163">1163</a></th><td></td></tr><tr><th id="L1164"><a href="#L1164">1164</a></th><td>  Â Description:  When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1165"><a href="#L1165">1165</a></th><td>  Â  Â  element is used to indicate that the sharee has never replied to</td></tr><tr><th id="L1166"><a href="#L1166">1166</a></th><td>  Â  Â  the corresponding sharing invite.  When used in a CS:invite-</td></tr><tr><th id="L1167"><a href="#L1167">1167</a></th><td>  Â  Â  notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1168"><a href="#L1168">1168</a></th><td>  Â  Â  indicate to the sharee that a sharing reply is needed.</td></tr><tr><th id="L1169"><a href="#L1169">1169</a></th><td></td></tr><tr><th id="L1170"><a href="#L1170">1170</a></th><td></td></tr><tr><th id="L1171"><a href="#L1171">1171</a></th><td></td></tr><tr><th id="L1172"><a href="#L1172">1172</a></th><td></td></tr><tr><th id="L1173"><a href="#L1173">1173</a></th><td></td></tr><tr><th id="L1174"><a href="#L1174">1174</a></th><td></td></tr><tr><th id="L1175"><a href="#L1175">1175</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 21]</td></tr><tr><th id="L1176"><a href="#L1176">1176</a></th><td></td></tr><tr><th id="L1177"><a href="#L1177">1177</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1178"><a href="#L1178">1178</a></th><td></td></tr><tr><th id="L1179"><a href="#L1179">1179</a></th><td></td></tr><tr><th id="L1180"><a href="#L1180">1180</a></th><td>  Â Definition:</td></tr><tr><th id="L1181"><a href="#L1181">1181</a></th><td></td></tr><tr><th id="L1182"><a href="#L1182">1182</a></th><td>  Â &lt;!ELEMENT invite-noresponse EMPTY&gt;</td></tr><tr><th id="L1183"><a href="#L1183">1183</a></th><td></td></tr><tr><th id="L1184"><a href="#L1184">1184</a></th><td>6.7.  CS:invite-deleted</td></tr><tr><th id="L1185"><a href="#L1185">1185</a></th><td></td></tr><tr><th id="L1186"><a href="#L1186">1186</a></th><td>  Â Name:  invite-deleted</td></tr><tr><th id="L1187"><a href="#L1187">1187</a></th><td></td></tr><tr><th id="L1188"><a href="#L1188">1188</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1189"><a href="#L1189">1189</a></th><td></td></tr><tr><th id="L1190"><a href="#L1190">1190</a></th><td>  Â Purpose:  Sharing invite status.</td></tr><tr><th id="L1191"><a href="#L1191">1191</a></th><td></td></tr><tr><th id="L1192"><a href="#L1192">1192</a></th><td>  Â Description:  When used in a CS:invite-notification (Section 6.15)</td></tr><tr><th id="L1193"><a href="#L1193">1193</a></th><td>  Â  Â  element, this element is used to indicate to the sharee that a</td></tr><tr><th id="L1194"><a href="#L1194">1194</a></th><td>  Â  Â  shared calendar has been unshared by the sharer.</td></tr><tr><th id="L1195"><a href="#L1195">1195</a></th><td></td></tr><tr><th id="L1196"><a href="#L1196">1196</a></th><td>  Â Definition:</td></tr><tr><th id="L1197"><a href="#L1197">1197</a></th><td></td></tr><tr><th id="L1198"><a href="#L1198">1198</a></th><td>  Â &lt;!ELEMENT invite-deleted EMPTY&gt;</td></tr><tr><th id="L1199"><a href="#L1199">1199</a></th><td></td></tr><tr><th id="L1200"><a href="#L1200">1200</a></th><td>6.8.  CS:invite-accepted</td></tr><tr><th id="L1201"><a href="#L1201">1201</a></th><td></td></tr><tr><th id="L1202"><a href="#L1202">1202</a></th><td>  Â Name:  invite-accepted</td></tr><tr><th id="L1203"><a href="#L1203">1203</a></th><td></td></tr><tr><th id="L1204"><a href="#L1204">1204</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1205"><a href="#L1205">1205</a></th><td></td></tr><tr><th id="L1206"><a href="#L1206">1206</a></th><td>  Â Purpose:  Sharing invite status.</td></tr><tr><th id="L1207"><a href="#L1207">1207</a></th><td></td></tr><tr><th id="L1208"><a href="#L1208">1208</a></th><td>  Â Description:  When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1209"><a href="#L1209">1209</a></th><td>  Â  Â  element is used to indicate that the sharee has accepted the</td></tr><tr><th id="L1210"><a href="#L1210">1210</a></th><td>  Â  Â  corresponding sharing invite.  When used in a CS:invite-</td></tr><tr><th id="L1211"><a href="#L1211">1211</a></th><td>  Â  Â  notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1212"><a href="#L1212">1212</a></th><td>  Â  Â  indicate to the sharee that the sharing invite is an update for</td></tr><tr><th id="L1213"><a href="#L1213">1213</a></th><td>  Â  Â  one they previously accepted.</td></tr><tr><th id="L1214"><a href="#L1214">1214</a></th><td></td></tr><tr><th id="L1215"><a href="#L1215">1215</a></th><td>  Â Definition:</td></tr><tr><th id="L1216"><a href="#L1216">1216</a></th><td></td></tr><tr><th id="L1217"><a href="#L1217">1217</a></th><td>  Â &lt;!ELEMENT invite-accepted EMPTY&gt;</td></tr><tr><th id="L1218"><a href="#L1218">1218</a></th><td></td></tr><tr><th id="L1219"><a href="#L1219">1219</a></th><td>6.9.  CS:invite-declined</td></tr><tr><th id="L1220"><a href="#L1220">1220</a></th><td></td></tr><tr><th id="L1221"><a href="#L1221">1221</a></th><td>  Â Name:  invite-declined</td></tr><tr><th id="L1222"><a href="#L1222">1222</a></th><td></td></tr><tr><th id="L1223"><a href="#L1223">1223</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1224"><a href="#L1224">1224</a></th><td></td></tr><tr><th id="L1225"><a href="#L1225">1225</a></th><td></td></tr><tr><th id="L1226"><a href="#L1226">1226</a></th><td></td></tr><tr><th id="L1227"><a href="#L1227">1227</a></th><td></td></tr><tr><th id="L1228"><a href="#L1228">1228</a></th><td></td></tr><tr><th id="L1229"><a href="#L1229">1229</a></th><td></td></tr><tr><th id="L1230"><a href="#L1230">1230</a></th><td></td></tr><tr><th id="L1231"><a href="#L1231">1231</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 22]</td></tr><tr><th id="L1232"><a href="#L1232">1232</a></th><td></td></tr><tr><th id="L1233"><a href="#L1233">1233</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1234"><a href="#L1234">1234</a></th><td></td></tr><tr><th id="L1235"><a href="#L1235">1235</a></th><td></td></tr><tr><th id="L1236"><a href="#L1236">1236</a></th><td>  Â Purpose:  Sharing invite status.</td></tr><tr><th id="L1237"><a href="#L1237">1237</a></th><td></td></tr><tr><th id="L1238"><a href="#L1238">1238</a></th><td>  Â Description:  When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1239"><a href="#L1239">1239</a></th><td>  Â  Â  element is used to indicate that the sharee has declined the</td></tr><tr><th id="L1240"><a href="#L1240">1240</a></th><td>  Â  Â  corresponding sharing invite.  When used in a CS:invite-</td></tr><tr><th id="L1241"><a href="#L1241">1241</a></th><td>  Â  Â  notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1242"><a href="#L1242">1242</a></th><td>  Â  Â  indicate to the sharee that the sharing invite is an update for</td></tr><tr><th id="L1243"><a href="#L1243">1243</a></th><td>  Â  Â  one they previously declined.</td></tr><tr><th id="L1244"><a href="#L1244">1244</a></th><td></td></tr><tr><th id="L1245"><a href="#L1245">1245</a></th><td>  Â Definition:</td></tr><tr><th id="L1246"><a href="#L1246">1246</a></th><td></td></tr><tr><th id="L1247"><a href="#L1247">1247</a></th><td>  Â &lt;!ELEMENT invite-declined EMPTY&gt;</td></tr><tr><th id="L1248"><a href="#L1248">1248</a></th><td></td></tr><tr><th id="L1249"><a href="#L1249">1249</a></th><td>6.10.  CS:invite-invalid</td></tr><tr><th id="L1250"><a href="#L1250">1250</a></th><td></td></tr><tr><th id="L1251"><a href="#L1251">1251</a></th><td>  Â Name:  invite-invalid</td></tr><tr><th id="L1252"><a href="#L1252">1252</a></th><td></td></tr><tr><th id="L1253"><a href="#L1253">1253</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1254"><a href="#L1254">1254</a></th><td></td></tr><tr><th id="L1255"><a href="#L1255">1255</a></th><td>  Â Purpose:  Sharing invite status.</td></tr><tr><th id="L1256"><a href="#L1256">1256</a></th><td></td></tr><tr><th id="L1257"><a href="#L1257">1257</a></th><td>  Â Description:  When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1258"><a href="#L1258">1258</a></th><td>  Â  Â  element is used to indicate that the corresponding sharee is not a</td></tr><tr><th id="L1259"><a href="#L1259">1259</a></th><td>  Â  Â  valid calendar user known to the server.</td></tr><tr><th id="L1260"><a href="#L1260">1260</a></th><td></td></tr><tr><th id="L1261"><a href="#L1261">1261</a></th><td>  Â Definition:</td></tr><tr><th id="L1262"><a href="#L1262">1262</a></th><td></td></tr><tr><th id="L1263"><a href="#L1263">1263</a></th><td>  Â &lt;!ELEMENT invite-invalid EMPTY&gt;</td></tr><tr><th id="L1264"><a href="#L1264">1264</a></th><td></td></tr><tr><th id="L1265"><a href="#L1265">1265</a></th><td>6.11.  CS:access</td></tr><tr><th id="L1266"><a href="#L1266">1266</a></th><td></td></tr><tr><th id="L1267"><a href="#L1267">1267</a></th><td>  Â Name:  access</td></tr><tr><th id="L1268"><a href="#L1268">1268</a></th><td></td></tr><tr><th id="L1269"><a href="#L1269">1269</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1270"><a href="#L1270">1270</a></th><td></td></tr><tr><th id="L1271"><a href="#L1271">1271</a></th><td>  Â Purpose:  Shared calendar access level.</td></tr><tr><th id="L1272"><a href="#L1272">1272</a></th><td></td></tr><tr><th id="L1273"><a href="#L1273">1273</a></th><td>  Â Description:  When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1274"><a href="#L1274">1274</a></th><td>  Â  Â  element is used to indicate the sharing access level granted to</td></tr><tr><th id="L1275"><a href="#L1275">1275</a></th><td>  Â  Â  the corresponding sharee.</td></tr><tr><th id="L1276"><a href="#L1276">1276</a></th><td></td></tr><tr><th id="L1277"><a href="#L1277">1277</a></th><td>  Â Definition:</td></tr><tr><th id="L1278"><a href="#L1278">1278</a></th><td></td></tr><tr><th id="L1279"><a href="#L1279">1279</a></th><td>  Â &lt;!ELEMENT invite-invalid (read | read-write)&gt;</td></tr><tr><th id="L1280"><a href="#L1280">1280</a></th><td></td></tr><tr><th id="L1281"><a href="#L1281">1281</a></th><td></td></tr><tr><th id="L1282"><a href="#L1282">1282</a></th><td></td></tr><tr><th id="L1283"><a href="#L1283">1283</a></th><td></td></tr><tr><th id="L1284"><a href="#L1284">1284</a></th><td></td></tr><tr><th id="L1285"><a href="#L1285">1285</a></th><td></td></tr><tr><th id="L1286"><a href="#L1286">1286</a></th><td></td></tr><tr><th id="L1287"><a href="#L1287">1287</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 23]</td></tr><tr><th id="L1288"><a href="#L1288">1288</a></th><td></td></tr><tr><th id="L1289"><a href="#L1289">1289</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1290"><a href="#L1290">1290</a></th><td></td></tr><tr><th id="L1291"><a href="#L1291">1291</a></th><td></td></tr><tr><th id="L1292"><a href="#L1292">1292</a></th><td>6.12.  CS:read</td></tr><tr><th id="L1293"><a href="#L1293">1293</a></th><td></td></tr><tr><th id="L1294"><a href="#L1294">1294</a></th><td>  Â Name:  read</td></tr><tr><th id="L1295"><a href="#L1295">1295</a></th><td></td></tr><tr><th id="L1296"><a href="#L1296">1296</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1297"><a href="#L1297">1297</a></th><td></td></tr><tr><th id="L1298"><a href="#L1298">1298</a></th><td>  Â Purpose:  Shared calendar access level privilege.</td></tr><tr><th id="L1299"><a href="#L1299">1299</a></th><td></td></tr><tr><th id="L1300"><a href="#L1300">1300</a></th><td>  Â Description:  Indicates that the access level granted only allows</td></tr><tr><th id="L1301"><a href="#L1301">1301</a></th><td>  Â  Â  sharees to read data in the shared calendar (though they can write</td></tr><tr><th id="L1302"><a href="#L1302">1302</a></th><td>  Â  Â  per-user data (Section 5.5.4)).</td></tr><tr><th id="L1303"><a href="#L1303">1303</a></th><td></td></tr><tr><th id="L1304"><a href="#L1304">1304</a></th><td>  Â Definition:</td></tr><tr><th id="L1305"><a href="#L1305">1305</a></th><td></td></tr><tr><th id="L1306"><a href="#L1306">1306</a></th><td>  Â &lt;!ELEMENT read EMPTY&gt;</td></tr><tr><th id="L1307"><a href="#L1307">1307</a></th><td></td></tr><tr><th id="L1308"><a href="#L1308">1308</a></th><td>6.13.  CS:read-write</td></tr><tr><th id="L1309"><a href="#L1309">1309</a></th><td></td></tr><tr><th id="L1310"><a href="#L1310">1310</a></th><td>  Â Name:  read-write</td></tr><tr><th id="L1311"><a href="#L1311">1311</a></th><td></td></tr><tr><th id="L1312"><a href="#L1312">1312</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1313"><a href="#L1313">1313</a></th><td></td></tr><tr><th id="L1314"><a href="#L1314">1314</a></th><td>  Â Purpose:  Shared calendar access level privilege.</td></tr><tr><th id="L1315"><a href="#L1315">1315</a></th><td></td></tr><tr><th id="L1316"><a href="#L1316">1316</a></th><td>  Â Description:  Indicates that the access level granted allows sharees</td></tr><tr><th id="L1317"><a href="#L1317">1317</a></th><td>  Â  Â  to read and write all data in the shared calendar, with the</td></tr><tr><th id="L1318"><a href="#L1318">1318</a></th><td>  Â  Â  exception of components that would trigger scheduling.</td></tr><tr><th id="L1319"><a href="#L1319">1319</a></th><td></td></tr><tr><th id="L1320"><a href="#L1320">1320</a></th><td>  Â Definition:</td></tr><tr><th id="L1321"><a href="#L1321">1321</a></th><td></td></tr><tr><th id="L1322"><a href="#L1322">1322</a></th><td>  Â &lt;!ELEMENT read-write EMPTY&gt;</td></tr><tr><th id="L1323"><a href="#L1323">1323</a></th><td></td></tr><tr><th id="L1324"><a href="#L1324">1324</a></th><td>6.14.  CS:summary</td></tr><tr><th id="L1325"><a href="#L1325">1325</a></th><td></td></tr><tr><th id="L1326"><a href="#L1326">1326</a></th><td>  Â Name:  summary</td></tr><tr><th id="L1327"><a href="#L1327">1327</a></th><td></td></tr><tr><th id="L1328"><a href="#L1328">1328</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1329"><a href="#L1329">1329</a></th><td></td></tr><tr><th id="L1330"><a href="#L1330">1330</a></th><td>  Â Purpose:  Summary or title of shared calendar.</td></tr><tr><th id="L1331"><a href="#L1331">1331</a></th><td></td></tr><tr><th id="L1332"><a href="#L1332">1332</a></th><td>  Â Description:  A brief description of a shared calendar.  This can be</td></tr><tr><th id="L1333"><a href="#L1333">1333</a></th><td>  Â  Â  used by sharers to communicate the nature of a shared calendar to</td></tr><tr><th id="L1334"><a href="#L1334">1334</a></th><td>  Â  Â  sharees, as well as used by sharees to indicate back to the sharer</td></tr><tr><th id="L1335"><a href="#L1335">1335</a></th><td>  Â  Â  how each sharee is refering to the shared calendar.</td></tr><tr><th id="L1336"><a href="#L1336">1336</a></th><td></td></tr><tr><th id="L1337"><a href="#L1337">1337</a></th><td></td></tr><tr><th id="L1338"><a href="#L1338">1338</a></th><td></td></tr><tr><th id="L1339"><a href="#L1339">1339</a></th><td></td></tr><tr><th id="L1340"><a href="#L1340">1340</a></th><td></td></tr><tr><th id="L1341"><a href="#L1341">1341</a></th><td></td></tr><tr><th id="L1342"><a href="#L1342">1342</a></th><td></td></tr><tr><th id="L1343"><a href="#L1343">1343</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 24]</td></tr><tr><th id="L1344"><a href="#L1344">1344</a></th><td></td></tr><tr><th id="L1345"><a href="#L1345">1345</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1346"><a href="#L1346">1346</a></th><td></td></tr><tr><th id="L1347"><a href="#L1347">1347</a></th><td></td></tr><tr><th id="L1348"><a href="#L1348">1348</a></th><td>  Â Definition:</td></tr><tr><th id="L1349"><a href="#L1349">1349</a></th><td></td></tr><tr><th id="L1350"><a href="#L1350">1350</a></th><td>  Â &lt;!ELEMENT summary (#PCDATA)&gt;</td></tr><tr><th id="L1351"><a href="#L1351">1351</a></th><td></td></tr><tr><th id="L1352"><a href="#L1352">1352</a></th><td>6.15.  CS:invite-notification</td></tr><tr><th id="L1353"><a href="#L1353">1353</a></th><td></td></tr><tr><th id="L1354"><a href="#L1354">1354</a></th><td>  Â Name:  invite-notification</td></tr><tr><th id="L1355"><a href="#L1355">1355</a></th><td></td></tr><tr><th id="L1356"><a href="#L1356">1356</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1357"><a href="#L1357">1357</a></th><td></td></tr><tr><th id="L1358"><a href="#L1358">1358</a></th><td>  Â Purpose:  A notification used as a shared calendar invite.</td></tr><tr><th id="L1359"><a href="#L1359">1359</a></th><td></td></tr><tr><th id="L1360"><a href="#L1360">1360</a></th><td>  Â Description:  Defines a notification message sent automatically by</td></tr><tr><th id="L1361"><a href="#L1361">1361</a></th><td>  Â  Â  the server when a sharer adds, changes or removes a sharee from a</td></tr><tr><th id="L1362"><a href="#L1362">1362</a></th><td>  Â  Â  shared calendar.  The DAV:href element specifies the calendar user</td></tr><tr><th id="L1363"><a href="#L1363">1363</a></th><td>  Â  Â  address of the sharee to whom the message was sent.</td></tr><tr><th id="L1364"><a href="#L1364">1364</a></th><td></td></tr><tr><th id="L1365"><a href="#L1365">1365</a></th><td>  Â Definition:</td></tr><tr><th id="L1366"><a href="#L1366">1366</a></th><td></td></tr><tr><th id="L1367"><a href="#L1367">1367</a></th><td>  Â &lt;!ELEMENT invite-notification (uid, DAV:href,</td></tr><tr><th id="L1368"><a href="#L1368">1368</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  (invite-noresponse | invite-deleted |</td></tr><tr><th id="L1369"><a href="#L1369">1369</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â invite-accepted | invite-declined),</td></tr><tr><th id="L1370"><a href="#L1370">1370</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  access, hosturl, organizer, summary?&gt;</td></tr><tr><th id="L1371"><a href="#L1371">1371</a></th><td></td></tr><tr><th id="L1372"><a href="#L1372">1372</a></th><td>6.16.  CS:uid</td></tr><tr><th id="L1373"><a href="#L1373">1373</a></th><td></td></tr><tr><th id="L1374"><a href="#L1374">1374</a></th><td>  Â Name:  uid</td></tr><tr><th id="L1375"><a href="#L1375">1375</a></th><td></td></tr><tr><th id="L1376"><a href="#L1376">1376</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1377"><a href="#L1377">1377</a></th><td></td></tr><tr><th id="L1378"><a href="#L1378">1378</a></th><td>  Â Purpose:  Unique identifier.</td></tr><tr><th id="L1379"><a href="#L1379">1379</a></th><td></td></tr><tr><th id="L1380"><a href="#L1380">1380</a></th><td>  Â Description:  A unique identifier for an invitation to a shared</td></tr><tr><th id="L1381"><a href="#L1381">1381</a></th><td>  Â  Â  calendar.</td></tr><tr><th id="L1382"><a href="#L1382">1382</a></th><td></td></tr><tr><th id="L1383"><a href="#L1383">1383</a></th><td>  Â Definition:</td></tr><tr><th id="L1384"><a href="#L1384">1384</a></th><td></td></tr><tr><th id="L1385"><a href="#L1385">1385</a></th><td>  Â &lt;!ELEMENT uid (#PCDATA)&gt;</td></tr><tr><th id="L1386"><a href="#L1386">1386</a></th><td></td></tr><tr><th id="L1387"><a href="#L1387">1387</a></th><td>6.17.  CS:hosturl</td></tr><tr><th id="L1388"><a href="#L1388">1388</a></th><td></td></tr><tr><th id="L1389"><a href="#L1389">1389</a></th><td>  Â Name:  hosturl</td></tr><tr><th id="L1390"><a href="#L1390">1390</a></th><td></td></tr><tr><th id="L1391"><a href="#L1391">1391</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1392"><a href="#L1392">1392</a></th><td></td></tr><tr><th id="L1393"><a href="#L1393">1393</a></th><td></td></tr><tr><th id="L1394"><a href="#L1394">1394</a></th><td></td></tr><tr><th id="L1395"><a href="#L1395">1395</a></th><td></td></tr><tr><th id="L1396"><a href="#L1396">1396</a></th><td></td></tr><tr><th id="L1397"><a href="#L1397">1397</a></th><td></td></tr><tr><th id="L1398"><a href="#L1398">1398</a></th><td></td></tr><tr><th id="L1399"><a href="#L1399">1399</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 25]</td></tr><tr><th id="L1400"><a href="#L1400">1400</a></th><td></td></tr><tr><th id="L1401"><a href="#L1401">1401</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1402"><a href="#L1402">1402</a></th><td></td></tr><tr><th id="L1403"><a href="#L1403">1403</a></th><td></td></tr><tr><th id="L1404"><a href="#L1404">1404</a></th><td>  Â Purpose:  Identifies the source URL of a shared calendar.</td></tr><tr><th id="L1405"><a href="#L1405">1405</a></th><td></td></tr><tr><th id="L1406"><a href="#L1406">1406</a></th><td>  Â Description:  Contains a single DAV:href element that refers to the</td></tr><tr><th id="L1407"><a href="#L1407">1407</a></th><td>  Â  Â  source of a shared calendar - i.e., the URL of the calendar shared</td></tr><tr><th id="L1408"><a href="#L1408">1408</a></th><td>  Â  Â  by the sharer.</td></tr><tr><th id="L1409"><a href="#L1409">1409</a></th><td></td></tr><tr><th id="L1410"><a href="#L1410">1410</a></th><td>  Â Definition:</td></tr><tr><th id="L1411"><a href="#L1411">1411</a></th><td></td></tr><tr><th id="L1412"><a href="#L1412">1412</a></th><td>  Â &lt;!ELEMENT hosturl (DAV:href)&gt;</td></tr><tr><th id="L1413"><a href="#L1413">1413</a></th><td></td></tr><tr><th id="L1414"><a href="#L1414">1414</a></th><td>6.18.  CS:organizer</td></tr><tr><th id="L1415"><a href="#L1415">1415</a></th><td></td></tr><tr><th id="L1416"><a href="#L1416">1416</a></th><td>  Â Name:  organizer</td></tr><tr><th id="L1417"><a href="#L1417">1417</a></th><td></td></tr><tr><th id="L1418"><a href="#L1418">1418</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1419"><a href="#L1419">1419</a></th><td></td></tr><tr><th id="L1420"><a href="#L1420">1420</a></th><td>  Â Purpose:  Identifies the sharer of a shared calendar.</td></tr><tr><th id="L1421"><a href="#L1421">1421</a></th><td></td></tr><tr><th id="L1422"><a href="#L1422">1422</a></th><td>  Â Description:  Contains a single DAV:href element that identifies the</td></tr><tr><th id="L1423"><a href="#L1423">1423</a></th><td>  Â  Â  calendar user address of the sharer of a shared calendar, and an</td></tr><tr><th id="L1424"><a href="#L1424">1424</a></th><td>  Â  Â  optional CS:common-name element that matches that user.</td></tr><tr><th id="L1425"><a href="#L1425">1425</a></th><td></td></tr><tr><th id="L1426"><a href="#L1426">1426</a></th><td>  Â Definition:</td></tr><tr><th id="L1427"><a href="#L1427">1427</a></th><td></td></tr><tr><th id="L1428"><a href="#L1428">1428</a></th><td>  Â &lt;!ELEMENT organizer (DAV:href, CS:common-name?)&gt;</td></tr><tr><th id="L1429"><a href="#L1429">1429</a></th><td></td></tr><tr><th id="L1430"><a href="#L1430">1430</a></th><td>6.19.  CS:common-name</td></tr><tr><th id="L1431"><a href="#L1431">1431</a></th><td></td></tr><tr><th id="L1432"><a href="#L1432">1432</a></th><td>  Â Name:  common-name</td></tr><tr><th id="L1433"><a href="#L1433">1433</a></th><td></td></tr><tr><th id="L1434"><a href="#L1434">1434</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1435"><a href="#L1435">1435</a></th><td></td></tr><tr><th id="L1436"><a href="#L1436">1436</a></th><td>  Â Purpose:  The common name of a sharer or sharee.</td></tr><tr><th id="L1437"><a href="#L1437">1437</a></th><td></td></tr><tr><th id="L1438"><a href="#L1438">1438</a></th><td>  Â Description:  The common name is optionally provided by a client when</td></tr><tr><th id="L1439"><a href="#L1439">1439</a></th><td>  Â  Â  adding a sharee and optionally included (or modified) by the</td></tr><tr><th id="L1440"><a href="#L1440">1440</a></th><td>  Â  Â  server when returning results for sharers or sharees and in</td></tr><tr><th id="L1441"><a href="#L1441">1441</a></th><td>  Â  Â  notifications.</td></tr><tr><th id="L1442"><a href="#L1442">1442</a></th><td></td></tr><tr><th id="L1443"><a href="#L1443">1443</a></th><td>  Â Definition:</td></tr><tr><th id="L1444"><a href="#L1444">1444</a></th><td></td></tr><tr><th id="L1445"><a href="#L1445">1445</a></th><td>  Â &lt;!ELEMENT common-name (#PCDATA)&gt;</td></tr><tr><th id="L1446"><a href="#L1446">1446</a></th><td></td></tr><tr><th id="L1447"><a href="#L1447">1447</a></th><td>6.20.  CS:invite-reply</td></tr><tr><th id="L1448"><a href="#L1448">1448</a></th><td></td></tr><tr><th id="L1449"><a href="#L1449">1449</a></th><td></td></tr><tr><th id="L1450"><a href="#L1450">1450</a></th><td></td></tr><tr><th id="L1451"><a href="#L1451">1451</a></th><td></td></tr><tr><th id="L1452"><a href="#L1452">1452</a></th><td></td></tr><tr><th id="L1453"><a href="#L1453">1453</a></th><td></td></tr><tr><th id="L1454"><a href="#L1454">1454</a></th><td></td></tr><tr><th id="L1455"><a href="#L1455">1455</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 26]</td></tr><tr><th id="L1456"><a href="#L1456">1456</a></th><td></td></tr><tr><th id="L1457"><a href="#L1457">1457</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1458"><a href="#L1458">1458</a></th><td></td></tr><tr><th id="L1459"><a href="#L1459">1459</a></th><td></td></tr><tr><th id="L1460"><a href="#L1460">1460</a></th><td>  Â Name:  invite-reply</td></tr><tr><th id="L1461"><a href="#L1461">1461</a></th><td></td></tr><tr><th id="L1462"><a href="#L1462">1462</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1463"><a href="#L1463">1463</a></th><td></td></tr><tr><th id="L1464"><a href="#L1464">1464</a></th><td>  Â Purpose:  A notification used as a reply to a shared calendar invite.</td></tr><tr><th id="L1465"><a href="#L1465">1465</a></th><td></td></tr><tr><th id="L1466"><a href="#L1466">1466</a></th><td>  Â Description:  Defines a notification message sent automatically by</td></tr><tr><th id="L1467"><a href="#L1467">1467</a></th><td>  Â  Â  the server when a sharee replies to a shared calendar invite.  The</td></tr><tr><th id="L1468"><a href="#L1468">1468</a></th><td>  Â  Â  DAV:href element specifies the calendar user address of the sharee</td></tr><tr><th id="L1469"><a href="#L1469">1469</a></th><td>  Â  Â  to whom the original invite message was sent.</td></tr><tr><th id="L1470"><a href="#L1470">1470</a></th><td></td></tr><tr><th id="L1471"><a href="#L1471">1471</a></th><td>  Â Definition:</td></tr><tr><th id="L1472"><a href="#L1472">1472</a></th><td></td></tr><tr><th id="L1473"><a href="#L1473">1473</a></th><td>  Â &lt;!ELEMENT invite-reply (DAV:href,</td></tr><tr><th id="L1474"><a href="#L1474">1474</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â (invite-accepted | invite-declined),</td></tr><tr><th id="L1475"><a href="#L1475">1475</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â hosturl, in-reply-to, summary?&gt;</td></tr><tr><th id="L1476"><a href="#L1476">1476</a></th><td></td></tr><tr><th id="L1477"><a href="#L1477">1477</a></th><td>6.21.  CS:in-reply-to</td></tr><tr><th id="L1478"><a href="#L1478">1478</a></th><td></td></tr><tr><th id="L1479"><a href="#L1479">1479</a></th><td>  Â Name:  in-reply-to</td></tr><tr><th id="L1480"><a href="#L1480">1480</a></th><td></td></tr><tr><th id="L1481"><a href="#L1481">1481</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1482"><a href="#L1482">1482</a></th><td></td></tr><tr><th id="L1483"><a href="#L1483">1483</a></th><td>  Â Purpose:  Unique identifier.</td></tr><tr><th id="L1484"><a href="#L1484">1484</a></th><td></td></tr><tr><th id="L1485"><a href="#L1485">1485</a></th><td>  Â Description:  Specifies the unique identifier of the inviate message</td></tr><tr><th id="L1486"><a href="#L1486">1486</a></th><td>  Â  Â  that this notification message is a reply to.</td></tr><tr><th id="L1487"><a href="#L1487">1487</a></th><td></td></tr><tr><th id="L1488"><a href="#L1488">1488</a></th><td>  Â Definition:</td></tr><tr><th id="L1489"><a href="#L1489">1489</a></th><td></td></tr><tr><th id="L1490"><a href="#L1490">1490</a></th><td>  Â &lt;!ELEMENT in-reply-to (#PCDATA)&gt;</td></tr><tr><th id="L1491"><a href="#L1491">1491</a></th><td></td></tr><tr><th id="L1492"><a href="#L1492">1492</a></th><td>6.22.  CS:notification</td></tr><tr><th id="L1493"><a href="#L1493">1493</a></th><td></td></tr><tr><th id="L1494"><a href="#L1494">1494</a></th><td>  Â Name:  notification</td></tr><tr><th id="L1495"><a href="#L1495">1495</a></th><td></td></tr><tr><th id="L1496"><a href="#L1496">1496</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1497"><a href="#L1497">1497</a></th><td></td></tr><tr><th id="L1498"><a href="#L1498">1498</a></th><td>  Â Purpose:  Notification message root element.</td></tr><tr><th id="L1499"><a href="#L1499">1499</a></th><td></td></tr><tr><th id="L1500"><a href="#L1500">1500</a></th><td>  Â Description:  The root element used in notification resources.</td></tr><tr><th id="L1501"><a href="#L1501">1501</a></th><td></td></tr><tr><th id="L1502"><a href="#L1502">1502</a></th><td>  Â Definition:</td></tr><tr><th id="L1503"><a href="#L1503">1503</a></th><td></td></tr><tr><th id="L1504"><a href="#L1504">1504</a></th><td>  Â &lt;!ELEMENT notification (CS:dtstamp,</td></tr><tr><th id="L1505"><a href="#L1505">1505</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â (invite-notification | invite-reply)&gt;</td></tr><tr><th id="L1506"><a href="#L1506">1506</a></th><td>  Â &lt;!-- Any notification type element can appear after CS:dtstamp,</td></tr><tr><th id="L1507"><a href="#L1507">1507</a></th><td>  Â  Â  Â  this specification defines only the two listed above --&gt;</td></tr><tr><th id="L1508"><a href="#L1508">1508</a></th><td></td></tr><tr><th id="L1509"><a href="#L1509">1509</a></th><td></td></tr><tr><th id="L1510"><a href="#L1510">1510</a></th><td></td></tr><tr><th id="L1511"><a href="#L1511">1511</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 27]</td></tr><tr><th id="L1512"><a href="#L1512">1512</a></th><td></td></tr><tr><th id="L1513"><a href="#L1513">1513</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1514"><a href="#L1514">1514</a></th><td></td></tr><tr><th id="L1515"><a href="#L1515">1515</a></th><td></td></tr><tr><th id="L1516"><a href="#L1516">1516</a></th><td>6.23.  CS:dtstamp</td></tr><tr><th id="L1517"><a href="#L1517">1517</a></th><td></td></tr><tr><th id="L1518"><a href="#L1518">1518</a></th><td>  Â Name:  dtstamp</td></tr><tr><th id="L1519"><a href="#L1519">1519</a></th><td></td></tr><tr><th id="L1520"><a href="#L1520">1520</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1521"><a href="#L1521">1521</a></th><td></td></tr><tr><th id="L1522"><a href="#L1522">1522</a></th><td>  Â Purpose:  Date-time stamp.</td></tr><tr><th id="L1523"><a href="#L1523">1523</a></th><td></td></tr><tr><th id="L1524"><a href="#L1524">1524</a></th><td>  Â Description:  Contains the date-time stamp corresponding to the</td></tr><tr><th id="L1525"><a href="#L1525">1525</a></th><td>  Â  Â  creation of a notification message.</td></tr><tr><th id="L1526"><a href="#L1526">1526</a></th><td></td></tr><tr><th id="L1527"><a href="#L1527">1527</a></th><td>  Â Definition:</td></tr><tr><th id="L1528"><a href="#L1528">1528</a></th><td></td></tr><tr><th id="L1529"><a href="#L1529">1529</a></th><td>  Â &lt;!ELEMENT dtstamp (#PCDATA)&gt;</td></tr><tr><th id="L1530"><a href="#L1530">1530</a></th><td></td></tr><tr><th id="L1531"><a href="#L1531">1531</a></th><td>6.24.  CS:share</td></tr><tr><th id="L1532"><a href="#L1532">1532</a></th><td></td></tr><tr><th id="L1533"><a href="#L1533">1533</a></th><td>  Â Name:  share</td></tr><tr><th id="L1534"><a href="#L1534">1534</a></th><td></td></tr><tr><th id="L1535"><a href="#L1535">1535</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1536"><a href="#L1536">1536</a></th><td></td></tr><tr><th id="L1537"><a href="#L1537">1537</a></th><td>  Â Purpose:  Describes changes to sharees.</td></tr><tr><th id="L1538"><a href="#L1538">1538</a></th><td></td></tr><tr><th id="L1539"><a href="#L1539">1539</a></th><td>  Â Description:  The root element used in POST requests on calendars by</td></tr><tr><th id="L1540"><a href="#L1540">1540</a></th><td>  Â  Â  sharers to manipulate the sharee list of a shared calendar.</td></tr><tr><th id="L1541"><a href="#L1541">1541</a></th><td></td></tr><tr><th id="L1542"><a href="#L1542">1542</a></th><td>  Â Definition:</td></tr><tr><th id="L1543"><a href="#L1543">1543</a></th><td></td></tr><tr><th id="L1544"><a href="#L1544">1544</a></th><td>  Â &lt;!ELEMENT share (set | remove)*&gt;</td></tr><tr><th id="L1545"><a href="#L1545">1545</a></th><td></td></tr><tr><th id="L1546"><a href="#L1546">1546</a></th><td>6.25.  CS:set</td></tr><tr><th id="L1547"><a href="#L1547">1547</a></th><td></td></tr><tr><th id="L1548"><a href="#L1548">1548</a></th><td>  Â Name:  set</td></tr><tr><th id="L1549"><a href="#L1549">1549</a></th><td></td></tr><tr><th id="L1550"><a href="#L1550">1550</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1551"><a href="#L1551">1551</a></th><td></td></tr><tr><th id="L1552"><a href="#L1552">1552</a></th><td>  Â Purpose:  Sets access for a sharee.</td></tr><tr><th id="L1553"><a href="#L1553">1553</a></th><td></td></tr><tr><th id="L1554"><a href="#L1554">1554</a></th><td>  Â Description:  Used to add or modify sharee access to a shared</td></tr><tr><th id="L1555"><a href="#L1555">1555</a></th><td>  Â  Â  calendar.  The specified access to the shared calendar is given to</td></tr><tr><th id="L1556"><a href="#L1556">1556</a></th><td>  Â  Â  the sharee.</td></tr><tr><th id="L1557"><a href="#L1557">1557</a></th><td></td></tr><tr><th id="L1558"><a href="#L1558">1558</a></th><td>  Â Definition:</td></tr><tr><th id="L1559"><a href="#L1559">1559</a></th><td></td></tr><tr><th id="L1560"><a href="#L1560">1560</a></th><td>  Â &lt;!ELEMENT set (DAV:href, common-name?, summary?,</td></tr><tr><th id="L1561"><a href="#L1561">1561</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  (read | read-write)&gt;</td></tr><tr><th id="L1562"><a href="#L1562">1562</a></th><td></td></tr><tr><th id="L1563"><a href="#L1563">1563</a></th><td></td></tr><tr><th id="L1564"><a href="#L1564">1564</a></th><td></td></tr><tr><th id="L1565"><a href="#L1565">1565</a></th><td></td></tr><tr><th id="L1566"><a href="#L1566">1566</a></th><td></td></tr><tr><th id="L1567"><a href="#L1567">1567</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 28]</td></tr><tr><th id="L1568"><a href="#L1568">1568</a></th><td></td></tr><tr><th id="L1569"><a href="#L1569">1569</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1570"><a href="#L1570">1570</a></th><td></td></tr><tr><th id="L1571"><a href="#L1571">1571</a></th><td></td></tr><tr><th id="L1572"><a href="#L1572">1572</a></th><td>6.26.  CS:remove</td></tr><tr><th id="L1573"><a href="#L1573">1573</a></th><td></td></tr><tr><th id="L1574"><a href="#L1574">1574</a></th><td>  Â Name:  remove</td></tr><tr><th id="L1575"><a href="#L1575">1575</a></th><td></td></tr><tr><th id="L1576"><a href="#L1576">1576</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1577"><a href="#L1577">1577</a></th><td></td></tr><tr><th id="L1578"><a href="#L1578">1578</a></th><td>  Â Purpose:  Removes access for a sharee.</td></tr><tr><th id="L1579"><a href="#L1579">1579</a></th><td></td></tr><tr><th id="L1580"><a href="#L1580">1580</a></th><td>  Â Description:  Used to remove sharee access to a shared calendar.  All</td></tr><tr><th id="L1581"><a href="#L1581">1581</a></th><td>  Â  Â  access to the shared calendar is removed for the sharee.</td></tr><tr><th id="L1582"><a href="#L1582">1582</a></th><td></td></tr><tr><th id="L1583"><a href="#L1583">1583</a></th><td>  Â Definition:</td></tr><tr><th id="L1584"><a href="#L1584">1584</a></th><td></td></tr><tr><th id="L1585"><a href="#L1585">1585</a></th><td>  Â &lt;!ELEMENT remove (DAV:href)&gt;</td></tr><tr><th id="L1586"><a href="#L1586">1586</a></th><td></td></tr><tr><th id="L1587"><a href="#L1587">1587</a></th><td>6.27.  CS:shared-as</td></tr><tr><th id="L1588"><a href="#L1588">1588</a></th><td></td></tr><tr><th id="L1589"><a href="#L1589">1589</a></th><td>  Â Name:  shared-as</td></tr><tr><th id="L1590"><a href="#L1590">1590</a></th><td></td></tr><tr><th id="L1591"><a href="#L1591">1591</a></th><td>  Â Namespace:  http://calendarserver.org/ns/</td></tr><tr><th id="L1592"><a href="#L1592">1592</a></th><td></td></tr><tr><th id="L1593"><a href="#L1593">1593</a></th><td>  Â Purpose:  Identifies a shared calendar.</td></tr><tr><th id="L1594"><a href="#L1594">1594</a></th><td></td></tr><tr><th id="L1595"><a href="#L1595">1595</a></th><td>  Â Description:  Returned by the server for a POST request by a sharee</td></tr><tr><th id="L1596"><a href="#L1596">1596</a></th><td>  Â  Â  accepting a shared calendar invite.  The DAV:href element</td></tr><tr><th id="L1597"><a href="#L1597">1597</a></th><td>  Â  Â  specifies the URI of the calendar created by the acceptance.</td></tr><tr><th id="L1598"><a href="#L1598">1598</a></th><td></td></tr><tr><th id="L1599"><a href="#L1599">1599</a></th><td>  Â Definition:</td></tr><tr><th id="L1600"><a href="#L1600">1600</a></th><td></td></tr><tr><th id="L1601"><a href="#L1601">1601</a></th><td>  Â &lt;!ELEMENT shared-as (DAV:href)&gt;</td></tr><tr><th id="L1602"><a href="#L1602">1602</a></th><td></td></tr><tr><th id="L1603"><a href="#L1603">1603</a></th><td></td></tr><tr><th id="L1604"><a href="#L1604">1604</a></th><td>7.  Security Considerations</td></tr><tr><th id="L1605"><a href="#L1605">1605</a></th><td></td></tr><tr><th id="L1606"><a href="#L1606">1606</a></th><td>  Â Per-user WebDAV properties and iCalendar data MUST only be accessible</td></tr><tr><th id="L1607"><a href="#L1607">1607</a></th><td>  Â by the user that created them.</td></tr><tr><th id="L1608"><a href="#L1608">1608</a></th><td></td></tr><tr><th id="L1609"><a href="#L1609">1609</a></th><td>  Â Alarms set by the sharer SHOULD NOT be propagated to sharees by</td></tr><tr><th id="L1610"><a href="#L1610">1610</a></th><td>  Â default.  Clients SHOULD NOT automatically enable triggering of</td></tr><tr><th id="L1611"><a href="#L1611">1611</a></th><td>  Â alarms on shared calendars that have just been accepted without</td></tr><tr><th id="L1612"><a href="#L1612">1612</a></th><td>  Â confirmation by the user.</td></tr><tr><th id="L1613"><a href="#L1613">1613</a></th><td></td></tr><tr><th id="L1614"><a href="#L1614">1614</a></th><td>  Â TBD</td></tr><tr><th id="L1615"><a href="#L1615">1615</a></th><td></td></tr><tr><th id="L1616"><a href="#L1616">1616</a></th><td></td></tr><tr><th id="L1617"><a href="#L1617">1617</a></th><td>8.  IANA Considerations</td></tr><tr><th id="L1618"><a href="#L1618">1618</a></th><td></td></tr><tr><th id="L1619"><a href="#L1619">1619</a></th><td>  Â This document does not require any actions on the part of IANA.</td></tr><tr><th id="L1620"><a href="#L1620">1620</a></th><td></td></tr><tr><th id="L1621"><a href="#L1621">1621</a></th><td></td></tr><tr><th id="L1622"><a href="#L1622">1622</a></th><td></td></tr><tr><th id="L1623"><a href="#L1623">1623</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 29]</td></tr><tr><th id="L1624"><a href="#L1624">1624</a></th><td></td></tr><tr><th id="L1625"><a href="#L1625">1625</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1626"><a href="#L1626">1626</a></th><td></td></tr><tr><th id="L1627"><a href="#L1627">1627</a></th><td></td></tr><tr><th id="L1628"><a href="#L1628">1628</a></th><td>9.  Acknowledgments</td></tr><tr><th id="L1629"><a href="#L1629">1629</a></th><td></td></tr><tr><th id="L1630"><a href="#L1630">1630</a></th><td>  Â This specification is the result of discussions between the Apple</td></tr><tr><th id="L1631"><a href="#L1631">1631</a></th><td>  Â calendar server and client teams.</td></tr><tr><th id="L1632"><a href="#L1632">1632</a></th><td></td></tr><tr><th id="L1633"><a href="#L1633">1633</a></th><td></td></tr><tr><th id="L1634"><a href="#L1634">1634</a></th><td>10.  Normative References</td></tr><tr><th id="L1635"><a href="#L1635">1635</a></th><td></td></tr><tr><th id="L1636"><a href="#L1636">1636</a></th><td>  Â [I-D.desruisseaux-caldav-sched]</td></tr><tr><th id="L1637"><a href="#L1637">1637</a></th><td>  Â  Â  Â  Â  Â  Â  Daboo, C. and B. Desruisseaux, "CalDAV Scheduling</td></tr><tr><th id="L1638"><a href="#L1638">1638</a></th><td>  Â  Â  Â  Â  Â  Â  Extensions to WebDAV", draft-desruisseaux-caldav-sched-08</td></tr><tr><th id="L1639"><a href="#L1639">1639</a></th><td>  Â  Â  Â  Â  Â  Â  (work in progress), August 2009.</td></tr><tr><th id="L1640"><a href="#L1640">1640</a></th><td></td></tr><tr><th id="L1641"><a href="#L1641">1641</a></th><td>  Â [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate</td></tr><tr><th id="L1642"><a href="#L1642">1642</a></th><td>  Â  Â  Â  Â  Â  Â  Requirement Levels", BCP 14, RFC 2119, March 1997.</td></tr><tr><th id="L1643"><a href="#L1643">1643</a></th><td></td></tr><tr><th id="L1644"><a href="#L1644">1644</a></th><td>  Â [RFC3744]  Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web</td></tr><tr><th id="L1645"><a href="#L1645">1645</a></th><td>  Â  Â  Â  Â  Â  Â  Distributed Authoring and Versioning (WebDAV)</td></tr><tr><th id="L1646"><a href="#L1646">1646</a></th><td>  Â  Â  Â  Â  Â  Â  Access Control Protocol", RFC 3744, May 2004.</td></tr><tr><th id="L1647"><a href="#L1647">1647</a></th><td></td></tr><tr><th id="L1648"><a href="#L1648">1648</a></th><td>  Â [RFC4791]  Daboo, C., Desruisseaux, B., and L. Dusseault,</td></tr><tr><th id="L1649"><a href="#L1649">1649</a></th><td>  Â  Â  Â  Â  Â  Â  "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,</td></tr><tr><th id="L1650"><a href="#L1650">1650</a></th><td>  Â  Â  Â  Â  Â  Â  March 2007.</td></tr><tr><th id="L1651"><a href="#L1651">1651</a></th><td></td></tr><tr><th id="L1652"><a href="#L1652">1652</a></th><td>  Â [RFC4918]  Dusseault, L., "HTTP Extensions for Web Distributed</td></tr><tr><th id="L1653"><a href="#L1653">1653</a></th><td>  Â  Â  Â  Â  Â  Â  Authoring and Versioning (WebDAV)", RFC 4918, June 2007.</td></tr><tr><th id="L1654"><a href="#L1654">1654</a></th><td></td></tr><tr><th id="L1655"><a href="#L1655">1655</a></th><td>  Â [RFC5689]  Daboo, C., "Extended MKCOL for Web Distributed Authoring</td></tr><tr><th id="L1656"><a href="#L1656">1656</a></th><td>  Â  Â  Â  Â  Â  Â  and Versioning (WebDAV)", RFC 5689, September 2009.</td></tr><tr><th id="L1657"><a href="#L1657">1657</a></th><td></td></tr><tr><th id="L1658"><a href="#L1658">1658</a></th><td></td></tr><tr><th id="L1659"><a href="#L1659">1659</a></th><td>Appendix A.  Change History</td></tr><tr><th id="L1660"><a href="#L1660">1660</a></th><td></td></tr><tr><th id="L1661"><a href="#L1661">1661</a></th><td>  Â Changes in -02:</td></tr><tr><th id="L1662"><a href="#L1662">1662</a></th><td></td></tr><tr><th id="L1663"><a href="#L1663">1663</a></th><td>  Â 1.  Removed read-write-shared access mode - now a server that does</td></tr><tr><th id="L1664"><a href="#L1664">1664</a></th><td>  Â  Â  Â not support shared scheduling should advertise that via a DAV</td></tr><tr><th id="L1665"><a href="#L1665">1665</a></th><td>  Â  Â  Â header</td></tr><tr><th id="L1666"><a href="#L1666">1666</a></th><td></td></tr><tr><th id="L1667"><a href="#L1667">1667</a></th><td></td></tr><tr><th id="L1668"><a href="#L1668">1668</a></th><td>Appendix B.  Change History</td></tr><tr><th id="L1669"><a href="#L1669">1669</a></th><td></td></tr><tr><th id="L1670"><a href="#L1670">1670</a></th><td>  Â Changes in -01:</td></tr><tr><th id="L1671"><a href="#L1671">1671</a></th><td></td></tr><tr><th id="L1672"><a href="#L1672">1672</a></th><td>  Â 1.  Added CS:shared-url property</td></tr><tr><th id="L1673"><a href="#L1673">1673</a></th><td></td></tr><tr><th id="L1674"><a href="#L1674">1674</a></th><td>  Â 2.  Clarified that notifications are only required to be sent when</td></tr><tr><th id="L1675"><a href="#L1675">1675</a></th><td>  Â  Â  Â sharee status is changed</td></tr><tr><th id="L1676"><a href="#L1676">1676</a></th><td></td></tr><tr><th id="L1677"><a href="#L1677">1677</a></th><td></td></tr><tr><th id="L1678"><a href="#L1678">1678</a></th><td></td></tr><tr><th id="L1679"><a href="#L1679">1679</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 30]</td></tr><tr><th id="L1680"><a href="#L1680">1680</a></th><td></td></tr><tr><th id="L1681"><a href="#L1681">1681</a></th><td>  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CalDAV Sharing and Publishing  Â  Â  Â  Â October 2010</td></tr><tr><th id="L1682"><a href="#L1682">1682</a></th><td></td></tr><tr><th id="L1683"><a href="#L1683">1683</a></th><td></td></tr><tr><th id="L1684"><a href="#L1684">1684</a></th><td>Authors' Addresses</td></tr><tr><th id="L1685"><a href="#L1685">1685</a></th><td></td></tr><tr><th id="L1686"><a href="#L1686">1686</a></th><td>  Â Cyrus Daboo</td></tr><tr><th id="L1687"><a href="#L1687">1687</a></th><td>  Â Apple Inc.</td></tr><tr><th id="L1688"><a href="#L1688">1688</a></th><td>  Â 1 Infinite Loop</td></tr><tr><th id="L1689"><a href="#L1689">1689</a></th><td>  Â Cupertino, CA  95014</td></tr><tr><th id="L1690"><a href="#L1690">1690</a></th><td>  Â USA</td></tr><tr><th id="L1691"><a href="#L1691">1691</a></th><td></td></tr><tr><th id="L1692"><a href="#L1692">1692</a></th><td>  Â Email: cyrus@daboo.name</td></tr><tr><th id="L1693"><a href="#L1693">1693</a></th><td>  Â URI:  Â http://www.apple.com/</td></tr><tr><th id="L1694"><a href="#L1694">1694</a></th><td></td></tr><tr><th id="L1695"><a href="#L1695">1695</a></th><td></td></tr><tr><th id="L1696"><a href="#L1696">1696</a></th><td>  Â Eric York</td></tr><tr><th id="L1697"><a href="#L1697">1697</a></th><td>  Â Apple Inc.</td></tr><tr><th id="L1698"><a href="#L1698">1698</a></th><td>  Â 1 Infinite Loop</td></tr><tr><th id="L1699"><a href="#L1699">1699</a></th><td>  Â Cupertino, CA  95014</td></tr><tr><th id="L1700"><a href="#L1700">1700</a></th><td>  Â USA</td></tr><tr><th id="L1701"><a href="#L1701">1701</a></th><td></td></tr><tr><th id="L1702"><a href="#L1702">1702</a></th><td>  Â Email:</td></tr><tr><th id="L1703"><a href="#L1703">1703</a></th><td>  Â URI:  Â http://www.apple.com/</td></tr><tr><th id="L1704"><a href="#L1704">1704</a></th><td></td></tr><tr><th id="L1705"><a href="#L1705">1705</a></th><td></td></tr><tr><th id="L1706"><a href="#L1706">1706</a></th><td></td></tr><tr><th id="L1707"><a href="#L1707">1707</a></th><td></td></tr><tr><th id="L1708"><a href="#L1708">1708</a></th><td></td></tr><tr><th id="L1709"><a href="#L1709">1709</a></th><td></td></tr><tr><th id="L1710"><a href="#L1710">1710</a></th><td></td></tr><tr><th id="L1711"><a href="#L1711">1711</a></th><td></td></tr><tr><th id="L1712"><a href="#L1712">1712</a></th><td></td></tr><tr><th id="L1713"><a href="#L1713">1713</a></th><td></td></tr><tr><th id="L1714"><a href="#L1714">1714</a></th><td></td></tr><tr><th id="L1715"><a href="#L1715">1715</a></th><td></td></tr><tr><th id="L1716"><a href="#L1716">1716</a></th><td></td></tr><tr><th id="L1717"><a href="#L1717">1717</a></th><td></td></tr><tr><th id="L1718"><a href="#L1718">1718</a></th><td></td></tr><tr><th id="L1719"><a href="#L1719">1719</a></th><td></td></tr><tr><th id="L1720"><a href="#L1720">1720</a></th><td></td></tr><tr><th id="L1721"><a href="#L1721">1721</a></th><td></td></tr><tr><th id="L1722"><a href="#L1722">1722</a></th><td></td></tr><tr><th id="L1723"><a href="#L1723">1723</a></th><td></td></tr><tr><th id="L1724"><a href="#L1724">1724</a></th><td></td></tr><tr><th id="L1725"><a href="#L1725">1725</a></th><td></td></tr><tr><th id="L1726"><a href="#L1726">1726</a></th><td></td></tr><tr><th id="L1727"><a href="#L1727">1727</a></th><td></td></tr><tr><th id="L1728"><a href="#L1728">1728</a></th><td></td></tr><tr><th id="L1729"><a href="#L1729">1729</a></th><td></td></tr><tr><th id="L1730"><a href="#L1730">1730</a></th><td></td></tr><tr><th id="L1731"><a href="#L1731">1731</a></th><td></td></tr><tr><th id="L1732"><a href="#L1732">1732</a></th><td></td></tr><tr><th id="L1733"><a href="#L1733">1733</a></th><td></td></tr><tr><th id="L1734"><a href="#L1734">1734</a></th><td></td></tr><tr><th id="L1735"><a href="#L1735">1735</a></th><td>Daboo &amp; York  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  Â [Page 31]</td></tr><tr><th id="L1736"><a href="#L1736">1736</a></th><td></td></tr></tbody></table>
+      </div>
+      <div id="help">
+        <strong>Note:</strong> See <a href="/wiki/TracBrowser">TracBrowser</a>
+        for help on using the browser.
+      </div>
+      <div id="anydiff">
+        <form action="/diff" method="get">
+          <div class="buttons">
+            <input type="hidden" name="new_path" value="/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" />
+            <input type="hidden" name="old_path" value="/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" />
+            <input type="hidden" name="new_rev" />
+            <input type="hidden" name="old_rev" />
+            <input type="submit" value="View changes..." title="Select paths and revs for Diff" />
+          </div>
+        </form>
+      </div>
+    </div>
+    <div id="altlinks">
+      <h3>Download in other formats:</h3>
+      <ul>
+        <li class="first">
+          <a rel="nofollow" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?format=txt">Plain Text</a>
+        </li><li class="last">
+          <a rel="nofollow" href="/export/9403/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Original Format</a>
+        </li>
+      </ul>
+    </div>
+    </div>
+    </div>
+  </div>
+ </div>
+    <script type="text/javascript" src="https://static2.macosforge.org/static/js/jquery.js"></script>
+    <script type="text/javascript" src="https://static2.macosforge.org/static/js/main.js"></script>
+ </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/dav/SabreDAV/docs/rfc5785.txt b/dav/SabreDAV/docs/rfc5785.txt
new file mode 100644 (file)
index 0000000..c28ccf6
--- /dev/null
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF)                     M. Nottingham
+Request for Comments: 5785                               E. Hammer-Lahav
+Updates: 2616, 2818                                           April 2010
+Category: Standards Track
+ISSN: 2070-1721
+
+
+        Defining Well-Known Uniform Resource Identifiers (URIs)
+
+Abstract
+
+   This memo defines a path prefix for "well-known locations",
+   "/.well-known/", in selected Uniform Resource Identifier (URI)
+   schemes.
+
+Status of This Memo
+
+   This is an Internet Standards Track document.
+
+   This document is a product of the Internet Engineering Task Force
+   (IETF).  It represents the consensus of the IETF community.  It has
+   received public review and has been approved for publication by the
+   Internet Engineering Steering Group (IESG).  Further information on
+   Internet Standards is available in Section 2 of RFC 5741.
+
+   Information about the current status of this document, any errata,
+   and how to provide feedback on it may be obtained at
+   http://www.rfc-editor.org/info/rfc5785.
+
+Copyright Notice
+
+   Copyright (c) 2010 IETF Trust and the persons identified as the
+   document authors.  All rights reserved.
+
+   This document is subject to BCP 78 and the IETF Trust's Legal
+   Provisions Relating to IETF Documents
+   (http://trustee.ietf.org/license-info) in effect on the date of
+   publication of this document.  Please review these documents
+   carefully, as they describe your rights and restrictions with respect
+   to this document.  Code Components extracted from this document must
+   include Simplified BSD License text as described in Section 4.e of
+   the Trust Legal Provisions and are provided without warranty as
+   described in the Simplified BSD License.
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 1]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+Table of Contents
+
+   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . . . 2
+     1.1.  Appropriate Use of Well-Known URIs  . . . . . . . . . . . . 3
+   2.  Notational Conventions  . . . . . . . . . . . . . . . . . . . . 3
+   3.  Well-Known URIs . . . . . . . . . . . . . . . . . . . . . . . . 3
+   4.  Security Considerations . . . . . . . . . . . . . . . . . . . . 4
+   5.  IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 4
+     5.1.  The Well-Known URI Registry . . . . . . . . . . . . . . . . 4
+       5.1.1.  Registration Template . . . . . . . . . . . . . . . . . 5
+   6.  References  . . . . . . . . . . . . . . . . . . . . . . . . . . 5
+     6.1.  Normative References  . . . . . . . . . . . . . . . . . . . 5
+     6.2.  Informative References  . . . . . . . . . . . . . . . . . . 5
+   Appendix A.  Acknowledgements . . . . . . . . . . . . . . . . . . . 7
+   Appendix B.  Frequently Asked Questions . . . . . . . . . . . . . . 7
+
+1.  Introduction
+
+   It is increasingly common for Web-based protocols to require the
+   discovery of policy or other information about a host ("site-wide
+   metadata") before making a request.  For example, the Robots
+   Exclusion Protocol <http://www.robotstxt.org/> specifies a way for
+   automated processes to obtain permission to access resources;
+   likewise, the Platform for Privacy Preferences [W3C.REC-P3P-20020416]
+   tells user-agents how to discover privacy policy beforehand.
+
+   While there are several ways to access per-resource metadata (e.g.,
+   HTTP headers, WebDAV's PROPFIND [RFC4918]), the perceived overhead
+   (either in terms of client-perceived latency and/or deployment
+   difficulties) associated with them often precludes their use in these
+   scenarios.
+
+   When this happens, it is common to designate a "well-known location"
+   for such data, so that it can be easily located.  However, this
+   approach has the drawback of risking collisions, both with other such
+   designated "well-known locations" and with pre-existing resources.
+
+   To address this, this memo defines a path prefix in HTTP(S) URIs for
+   these "well-known locations", "/.well-known/".  Future specifications
+   that need to define a resource for such site-wide metadata can
+   register their use to avoid collisions and minimise impingement upon
+   sites' URI space.
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 2]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+1.1.  Appropriate Use of Well-Known URIs
+
+   There are a number of possible ways that applications could use Well-
+   known URIs.  However, in keeping with the Architecture of the World-
+   Wide Web [W3C.REC-webarch-20041215], well-known URIs are not intended
+   for general information retrieval or establishment of large URI
+   namespaces on the Web.  Rather, they are designed to facilitate
+   discovery of information on a site when it isn't practical to use
+   other mechanisms; for example, when discovering policy that needs to
+   be evaluated before a resource is accessed, or when using multiple
+   round-trips is judged detrimental to performance.
+
+   As such, the well-known URI space was created with the expectation
+   that it will be used to make site-wide policy information and other
+   metadata available directly (if sufficiently concise), or provide
+   references to other URIs that provide such metadata.
+
+2.  Notational Conventions
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in RFC 2119 [RFC2119].
+
+3.  Well-Known URIs
+
+   A well-known URI is a URI [RFC3986] whose path component begins with
+   the characters "/.well-known/", and whose scheme is "HTTP", "HTTPS",
+   or another scheme that has explicitly been specified to use well-
+   known URIs.
+
+   Applications that wish to mint new well-known URIs MUST register
+   them, following the procedures in Section 5.1.
+
+   For example, if an application registers the name 'example', the
+   corresponding well-known URI on 'http://www.example.com/' would be
+   'http://www.example.com/.well-known/example'.
+
+   Registered names MUST conform to the segment-nz production in
+   [RFC3986].
+
+   Note that this specification defines neither how to determine the
+   authority to use for a particular context, nor the scope of the
+   metadata discovered by dereferencing the well-known URI; both should
+   be defined by the application itself.
+
+   Typically, a registration will reference a specification that defines
+   the format and associated media type to be obtained by dereferencing
+   the well-known URI.
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 3]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+   It MAY also contain additional information, such as the syntax of
+   additional path components, query strings and/or fragment identifiers
+   to be appended to the well-known URI, or protocol-specific details
+   (e.g., HTTP [RFC2616] method handling).
+
+   Note that this specification does not define a format or media-type
+   for the resource located at "/.well-known/" and clients should not
+   expect a resource to exist at that location.
+
+4.  Security Considerations
+
+   This memo does not specify the scope of applicability of metadata or
+   policy obtained from a well-known URI, and does not specify how to
+   discover a well-known URI for a particular application.  Individual
+   applications using this mechanism must define both aspects.
+
+   Applications minting new well-known URIs, as well as administrators
+   deploying them, will need to consider several security-related
+   issues, including (but not limited to) exposure of sensitive data,
+   denial-of-service attacks (in addition to normal load issues), server
+   and client authentication, vulnerability to DNS rebinding attacks,
+   and attacks where limited access to a server grants the ability to
+   affect how well-known URIs are served.
+
+5.  IANA Considerations
+
+5.1.  The Well-Known URI Registry
+
+   This document establishes the well-known URI registry.
+
+   Well-known URIs are registered on the advice of one or more
+   Designated Experts (appointed by the IESG or their delegate), with a
+   Specification Required (using terminology from [RFC5226]).  However,
+   to allow for the allocation of values prior to publication, the
+   Designated Expert(s) may approve registration once they are satisfied
+   that such a specification will be published.
+
+   Registration requests should be sent to the
+   wellknown-uri-review@ietf.org mailing list for review and comment,
+   with an appropriate subject (e.g., "Request for well-known URI:
+   example").
+
+   Before a period of 14 days has passed, the Designated Expert(s) will
+   either approve or deny the registration request, communicating this
+   decision both to the review list and to IANA.  Denials should include
+   an explanation and, if applicable, suggestions as to how to make the
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 4]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+   request successful.  Registration requests that are undetermined for
+   a period longer than 21 days can be brought to the IESG's attention
+   (using the iesg@iesg.org mailing list) for resolution.
+
+5.1.1.  Registration Template
+
+   URI suffix:  The name requested for the well-known URI, relative to
+      "/.well-known/"; e.g., "example".
+
+   Change controller:  For Standards-Track RFCs, state "IETF".  For
+      others, give the name of the responsible party.  Other details
+      (e.g., postal address, e-mail address, home page URI) may also be
+      included.
+
+   Specification document(s):  Reference to the document that specifies
+      the field, preferably including a URI that can be used to retrieve
+      a copy of the document.  An indication of the relevant sections
+      may also be included, but is not required.
+
+   Related information:  Optionally, citations to additional documents
+      containing further relevant information.
+
+6.  References
+
+6.1.  Normative References
+
+   [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+             Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [RFC3986] Berners-Lee, T., Fielding, R., and L.  Masinter, "Uniform
+             Resource Identifier (URI): Generic Syntax", STD 66,
+             RFC 3986, January 2005.
+
+   [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
+             IANA Considerations Section in RFCs", BCP 26, RFC 5226,
+             May 2008.
+
+6.2.  Informative References
+
+   [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter,
+             L., Leach, P., and T. Berners-Lee, "Hypertext Transfer
+             Protocol -- HTTP/1.1", RFC 2616, June 1999.
+
+   [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
+             Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 5]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+   [W3C.REC-P3P-20020416]
+             Marchiori, M., "The Platform for Privacy Preferences 1.0
+             (P3P1.0) Specification", World Wide Web Consortium
+             Recommendation REC-P3P-20020416, April 2002,
+             <http://www.w3.org/TR/2002/ REC-P3P-20020416>.
+
+   [W3C.REC-webarch-20041215]
+             Jacobs, I. and N. Walsh, "Architecture of the World Wide
+             Web, Volume One", World Wide Web Consortium
+             Recommendation REC- webarch-20041215, December 2004,
+             <http:// www.w3.org/TR/2004/REC-webarch-20041215>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 6]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+Appendix A.  Acknowledgements
+
+   We would like to acknowledge the contributions of everyone who
+   provided feedback and use cases for this document; in particular,
+   Phil Archer, Dirk Balfanz, Adam Barth, Tim Bray, Brian Eaton, Brad
+   Fitzpatrick, Joe Gregorio, Paul Hoffman, Barry Leiba, Ashok Malhotra,
+   Breno de Medeiros, John Panzer, and Drummond Reed.  However, they are
+   not responsible for errors and omissions.
+
+Appendix B.  Frequently Asked Questions
+
+   1. Aren't well-known locations bad for the Web?
+
+      They are, but for various reasons -- both technical and social --
+      they are commonly used and their use is increasing.  This memo
+      defines a "sandbox" for them, to reduce the risks of collision and
+      to minimise the impact upon pre-existing URIs on sites.
+
+   2. Why /.well-known?
+
+      It's short, descriptive, and according to search indices, not
+      widely used.
+
+   3. What impact does this have on existing mechanisms, such as P3P and
+      robots.txt?
+
+      None, until they choose to use this mechanism.
+
+   4. Why aren't per-directory well-known locations defined?
+
+      Allowing every URI path segment to have a well-known location
+      (e.g., "/images/.well-known/") would increase the risks of
+      colliding with a pre-existing URI on a site, and generally these
+      solutions are found not to scale well, because they're too
+      "chatty".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 7]
+\f
+RFC 5785                Defining Well-Known URIs              April 2010
+
+
+Authors' Addresses
+
+   Mark Nottingham
+
+   EMail: mnot@mnot.net
+   URI:   http://www.mnot.net/
+
+
+   Eran Hammer-Lahav
+
+   EMail: eran@hueniverse.com
+   URI:   http://hueniverse.com/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav    Standards Track                    [Page 8]
+\f
index 2fafac9cb23fc3bfc5629d4671f4ce0221410f2d..cf3caf4f71789fd2b292cdb3715664c386b0f6af 100644 (file)
@@ -6,5 +6,8 @@ CREATE TABLE locks (
     token VARCHAR(100),
     scope TINYINT,
     depth TINYINT,
-    uri text
+    uri VARCHAR(1000),
+    INDEX(token),
+    INDEX(uri)
 );
+
index 5a816e1f5c1a65339043ff7f831c88a50a88b205..bb374eb0feb37b000a02523897001e6b6a0d6b82 100644 (file)
@@ -7,27 +7,27 @@
 # settings as well.
 <VirtualHost *:*>
 
-       # Don't forget to change the server name
-       # ServerName dav.example.org
+    # Don't forget to change the server name
+    # ServerName dav.example.org
 
-       # The DocumentRoot is also required
+    # The DocumentRoot is also required
     # DocumentRoot /home/sabredav/
 
-       RewriteEngine On
-       # This makes every request go to server.php
-       RewriteRule ^/(.*)$ /server.php [L]
+    RewriteEngine On
+    # This makes every request go to server.php
+    RewriteRule ^/(.*)$ /server.php [L]
 
-       # Output buffering needs to be off, to prevent high memory usage
-       php_flag output_buffering off
+    # Output buffering needs to be off, to prevent high memory usage
+    php_flag output_buffering off
 
-       # This is also to prevent high memory usage
-       php_flag always_populate_raw_post_data off
+    # This is also to prevent high memory usage
+    php_flag always_populate_raw_post_data off
 
-       # This is almost a given, but magic quotes is *still* on on some
-       # linux distributions
-       php_flag magic_quotes_gpc off
+    # This is almost a given, but magic quotes is *still* on on some
+    # linux distributions
+    php_flag magic_quotes_gpc off
 
-       # SabreDAV is not compatible with mbstring function overloading
-       php_flag mbstring.func_overload off
+    # SabreDAV is not compatible with mbstring function overloading
+    php_flag mbstring.func_overload off
 
 </VirtualHost *:*>
index a034d7fcae354717bc6e81a18bd11cb10744f7aa..607254c6e9352ed95a3547480c6b247710a3277a 100644 (file)
@@ -6,16 +6,16 @@
 # This configuration assumes CGI or FastCGI is used.
 <VirtualHost *:*>
 
-       # Don't forget to change the server name
-       # ServerName dav.example.org
+    # Don't forget to change the server name
+    # ServerName dav.example.org
 
-       # The DocumentRoot is also required
+    # The DocumentRoot is also required
     # DocumentRoot /home/sabredav/
 
-       # This makes every request go to server.php. This also makes sure
-       # the Authentication information is available. If your server script is
-       # not called server.php, be sure to change it.
-       RewriteEngine On
-       RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+    # This makes every request go to server.php. This also makes sure
+    # the Authentication information is available. If your server script is
+    # not called server.php, be sure to change it.
+    RewriteEngine On
+    RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
 
 </VirtualHost *:*>
index 064cad7ce84c067bc6e5bb1ce18d4b562c2faac6..8adf10e1ba8a736793c46e2a5593f1b1de753b7c 100644 (file)
@@ -1,47 +1,19 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * Abstract Calendaring backend. Extend this class to create your own backends.
  *
+ * Checkout the BackendInterface for all the methods that must be implemented.
+ *
  * @package Sabre
  * @subpackage CalDAV
  * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
  * @author Evert Pot (http://www.rooftopsolutions.nl/)
  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  */
-abstract class Sabre_CalDAV_Backend_Abstract {
-
-    /**
-     * Returns a list of calendars for a principal.
-     *
-     * Every project is an array with the following keys:
-     *  * id, a unique id that will be used by other functions to modify the
-     *    calendar. This can be the same as the uri or a database key.
-     *  * uri, which the basename of the uri with which the calendar is
-     *    accessed.
-     *  * principaluri. The owner of the calendar. Almost always the same as
-     *    principalUri passed to this method.
-     *
-     * Furthermore it can contain webdav properties in clark notation. A very
-     * common one is '{DAV:}displayname'.
-     *
-     * @param string $principalUri
-     * @return array
-     */
-    abstract function getCalendarsForUser($principalUri);
-
-    /**
-     * Creates a new calendar for a principal.
-     *
-     * If the creation was a success, an id must be returned that can be used to reference
-     * this calendar in other methods, such as updateCalendar.
-     *
-     * @param string $principalUri
-     * @param string $calendarUri
-     * @param array $properties
-     * @return void
-     */
-    abstract function createCalendar($principalUri,$calendarUri,array $properties);
+abstract class Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_BackendInterface {
 
     /**
      * Updates properties for a calendar.
@@ -85,102 +57,6 @@ abstract class Sabre_CalDAV_Backend_Abstract {
 
     }
 
-    /**
-     * Delete a calendar and all it's objects
-     *
-     * @param mixed $calendarId
-     * @return void
-     */
-    abstract function deleteCalendar($calendarId);
-
-    /**
-     * Returns all calendar objects within a calendar.
-     *
-     * Every item contains an array with the following keys:
-     *   * id - unique identifier which will be used for subsequent updates
-     *   * calendardata - The iCalendar-compatible calendar data
-     *   * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
-     *   * lastmodified - a timestamp of the last modification time
-     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
-     *   '  "abcdef"')
-     *   * calendarid - The calendarid as it was passed to this function.
-     *   * size - The size of the calendar objects, in bytes.
-     *
-     * Note that the etag is optional, but it's highly encouraged to return for
-     * speed reasons.
-     *
-     * The calendardata is also optional. If it's not returned
-     * 'getCalendarObject' will be called later, which *is* expected to return
-     * calendardata.
-     *
-     * If neither etag or size are specified, the calendardata will be
-     * used/fetched to determine these numbers. If both are specified the
-     * amount of times this is needed is reduced by a great degree.
-     *
-     * @param mixed $calendarId
-     * @return array
-     */
-    abstract function getCalendarObjects($calendarId);
-
-    /**
-     * Returns information from a single calendar object, based on it's object
-     * uri.
-     *
-     * The returned array must have the same keys as getCalendarObjects. The
-     * 'calendardata' object is required here though, while it's not required
-     * for getCalendarObjects.
-     *
-     * @param mixed $calendarId
-     * @param string $objectUri
-     * @return array
-     */
-    abstract function getCalendarObject($calendarId,$objectUri);
-
-    /**
-     * Creates a new calendar object.
-     *
-     * It is possible return an etag from this function, which will be used in
-     * the response to this PUT request. Note that the ETag must be surrounded
-     * by double-quotes.
-     *
-     * However, you should only really return this ETag if you don't mangle the
-     * calendar-data. If the result of a subsequent GET to this object is not
-     * the exact same as this request body, you should omit the ETag.
-     *
-     * @param mixed $calendarId
-     * @param string $objectUri
-     * @param string $calendarData
-     * @return string|null
-     */
-    abstract function createCalendarObject($calendarId,$objectUri,$calendarData);
-
-    /**
-     * Updates an existing calendarobject, based on it's uri.
-     *
-     * It is possible return an etag from this function, which will be used in
-     * the response to this PUT request. Note that the ETag must be surrounded
-     * by double-quotes.
-     *
-     * However, you should only really return this ETag if you don't mangle the
-     * calendar-data. If the result of a subsequent GET to this object is not
-     * the exact same as this request body, you should omit the ETag.
-     *
-     * @param mixed $calendarId
-     * @param string $objectUri
-     * @param string $calendarData
-     * @return string|null
-     */
-    abstract function updateCalendarObject($calendarId,$objectUri,$calendarData);
-
-    /**
-     * Deletes an existing calendar object.
-     *
-     * @param mixed $calendarId
-     * @param string $objectUri
-     * @return void
-     */
-    abstract function deleteCalendarObject($calendarId,$objectUri);
-
     /**
      * Performs a calendar-query on the contents of this calendar.
      *
@@ -266,7 +142,7 @@ abstract class Sabre_CalDAV_Backend_Abstract {
             $object = $this->getCalendarObject($object['calendarid'], $object['uri']);
         }
 
-        $vObject = Sabre_VObject_Reader::read($object['calendardata']);
+        $vObject = VObject\Reader::read($object['calendardata']);
 
         $validator = new Sabre_CalDAV_CalendarQueryValidator();
         return $validator->validate($vObject, $filters);
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php
new file mode 100644 (file)
index 0000000..881538a
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * Every CalDAV backend must at least implement this interface.
+ * 
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Backend_BackendInterface {
+
+    /**
+     * Returns a list of calendars for a principal.
+     *
+     * Every project is an array with the following keys:
+     *  * id, a unique id that will be used by other functions to modify the
+     *    calendar. This can be the same as the uri or a database key.
+     *  * uri, which the basename of the uri with which the calendar is
+     *    accessed.
+     *  * principaluri. The owner of the calendar. Almost always the same as
+     *    principalUri passed to this method.
+     *
+     * Furthermore it can contain webdav properties in clark notation. A very
+     * common one is '{DAV:}displayname'.
+     *
+     * @param string $principalUri
+     * @return array
+     */
+    public function getCalendarsForUser($principalUri);
+
+    /**
+     * Creates a new calendar for a principal.
+     *
+     * If the creation was a success, an id must be returned that can be used to reference
+     * this calendar in other methods, such as updateCalendar.
+     *
+     * @param string $principalUri
+     * @param string $calendarUri
+     * @param array $properties
+     * @return void
+     */
+    public function createCalendar($principalUri,$calendarUri,array $properties);
+
+    /**
+     * Updates properties for a calendar.
+     *
+     * The mutations array uses the propertyName in clark-notation as key,
+     * and the array value for the property value. In the case a property
+     * should be deleted, the property value will be null.
+     *
+     * This method must be atomic. If one property cannot be changed, the
+     * entire operation must fail.
+     *
+     * If the operation was successful, true can be returned.
+     * If the operation failed, false can be returned.
+     *
+     * Deletion of a non-existent property is always successful.
+     *
+     * Lastly, it is optional to return detailed information about any
+     * failures. In this case an array should be returned with the following
+     * structure:
+     *
+     * array(
+     *   403 => array(
+     *      '{DAV:}displayname' => null,
+     *   ),
+     *   424 => array(
+     *      '{DAV:}owner' => null,
+     *   )
+     * )
+     *
+     * In this example it was forbidden to update {DAV:}displayname.
+     * (403 Forbidden), which in turn also caused {DAV:}owner to fail
+     * (424 Failed Dependency) because the request needs to be atomic.
+     *
+     * @param mixed $calendarId
+     * @param array $mutations
+     * @return bool|array
+     */
+    public function updateCalendar($calendarId, array $mutations); 
+
+    /**
+     * Delete a calendar and all it's objects
+     *
+     * @param mixed $calendarId
+     * @return void
+     */
+    public function deleteCalendar($calendarId);
+
+    /**
+     * Returns all calendar objects within a calendar.
+     *
+     * Every item contains an array with the following keys:
+     *   * id - unique identifier which will be used for subsequent updates
+     *   * calendardata - The iCalendar-compatible calendar data
+     *   * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
+     *   * lastmodified - a timestamp of the last modification time
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+     *   '  "abcdef"')
+     *   * calendarid - The calendarid as it was passed to this function.
+     *   * size - The size of the calendar objects, in bytes.
+     *
+     * Note that the etag is optional, but it's highly encouraged to return for
+     * speed reasons.
+     *
+     * The calendardata is also optional. If it's not returned
+     * 'getCalendarObject' will be called later, which *is* expected to return
+     * calendardata.
+     *
+     * If neither etag or size are specified, the calendardata will be
+     * used/fetched to determine these numbers. If both are specified the
+     * amount of times this is needed is reduced by a great degree.
+     *
+     * @param mixed $calendarId
+     * @return array
+     */
+    public function getCalendarObjects($calendarId);
+
+    /**
+     * Returns information from a single calendar object, based on it's object
+     * uri.
+     *
+     * The returned array must have the same keys as getCalendarObjects. The
+     * 'calendardata' object is required here though, while it's not required
+     * for getCalendarObjects.
+     *
+     * @param mixed $calendarId
+     * @param string $objectUri
+     * @return array
+     */
+    public function getCalendarObject($calendarId,$objectUri);
+
+    /**
+     * Creates a new calendar object.
+     *
+     * It is possible return an etag from this function, which will be used in
+     * the response to this PUT request. Note that the ETag must be surrounded
+     * by double-quotes.
+     *
+     * However, you should only really return this ETag if you don't mangle the
+     * calendar-data. If the result of a subsequent GET to this object is not
+     * the exact same as this request body, you should omit the ETag.
+     *
+     * @param mixed $calendarId
+     * @param string $objectUri
+     * @param string $calendarData
+     * @return string|null
+     */
+    public function createCalendarObject($calendarId,$objectUri,$calendarData);
+
+    /**
+     * Updates an existing calendarobject, based on it's uri.
+     *
+     * It is possible return an etag from this function, which will be used in
+     * the response to this PUT request. Note that the ETag must be surrounded
+     * by double-quotes.
+     *
+     * However, you should only really return this ETag if you don't mangle the
+     * calendar-data. If the result of a subsequent GET to this object is not
+     * the exact same as this request body, you should omit the ETag.
+     *
+     * @param mixed $calendarId
+     * @param string $objectUri
+     * @param string $calendarData
+     * @return string|null
+     */
+    public function updateCalendarObject($calendarId,$objectUri,$calendarData);
+
+    /**
+     * Deletes an existing calendar object.
+     *
+     * @param mixed $calendarId
+     * @param string $objectUri
+     * @return void
+     */
+    public function deleteCalendarObject($calendarId,$objectUri);
+
+    /**
+     * Performs a calendar-query on the contents of this calendar.
+     *
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
+     * calendar-query it is possible for a client to request a specific set of
+     * object, based on contents of iCalendar properties, date-ranges and
+     * iCalendar component types (VTODO, VEVENT).
+     *
+     * This method should just return a list of (relative) urls that match this
+     * query.
+     *
+     * The list of filters are specified as an array. The exact array is
+     * documented by Sabre_CalDAV_CalendarQueryParser.
+     *
+     * Note that it is extremely likely that getCalendarObject for every path
+     * returned from this method will be called almost immediately after. You
+     * may want to anticipate this to speed up these requests.
+     *
+     * This method provides a default implementation, which parses *all* the
+     * iCalendar objects in the specified calendar.
+     *
+     * This default may well be good enough for personal use, and calendars
+     * that aren't very large. But if you anticipate high usage, big calendars
+     * or high loads, you are strongly adviced to optimize certain paths.
+     *
+     * The best way to do so is override this method and to optimize
+     * specifically for 'common filters'.
+     *
+     * Requests that are extremely common are:
+     *   * requests for just VEVENTS
+     *   * requests for just VTODO
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
+     *
+     * ..and combinations of these requests. It may not be worth it to try to
+     * handle every possible situation and just rely on the (relatively
+     * easy to use) CalendarQueryValidator to handle the rest.
+     *
+     * Note that especially time-range-filters may be difficult to parse. A
+     * time-range filter specified on a VEVENT must for instance also handle
+     * recurrence rules correctly.
+     * A good example of how to interprete all these filters can also simply
+     * be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
+     * as possible, so it gives you a good idea on what type of stuff you need
+     * to think of.
+     *
+     * @param mixed $calendarId
+     * @param array $filters
+     * @return array
+     */
+    public function calendarQuery($calendarId, array $filters); 
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php
new file mode 100644 (file)
index 0000000..ad905b1
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Adds caldav notification support to a backend.
+ *
+ * Notifications are defined at:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
+ *
+ * These notifications are basically a list of server-generated notifications 
+ * displayed to the user. Users can dismiss notifications by deleting them.
+ *
+ * The primary usecase is to allow for calendar-sharing.
+ * 
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Backend_NotificationSupport extends Sabre_CalDAV_Backend_BackendInterface {
+
+    /**
+     * Returns a list of notifications for a given principal url.
+     *
+     * The returned array should only consist of implementations of
+     * Sabre_CalDAV_Notifications_INotificationType. 
+     * 
+     * @param string $principalUri
+     * @return array 
+     */
+    public function getNotificationsForPrincipal($principalUri);
+
+    /**
+     * This deletes a specific notifcation.
+     *
+     * This may be called by a client once it deems a notification handled. 
+     * 
+     * @param string $principalUri 
+     * @param Sabre_CalDAV_Notifications_INotificationType $notification 
+     * @return void
+     */
+    public function deleteNotification($principalUri, Sabre_CalDAV_Notifications_INotificationType $notification); 
+
+}
index aafea1c55e1a5e7e649e471610fb2af72efa4310..74b34267fb54e0b30ac80b0d1f7274e42f5a5416 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * PDO CalDAV backend
  *
@@ -16,8 +18,13 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
 
     /**
      * We need to specify a max date, because we need to stop *somewhere*
+     *
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
+     * in 2038-01-19 to avoid problems when the date is converted
+     * to a unix timestamp.
      */
-    const MAX_DATE = '2040-01-01';
+    const MAX_DATE = '2038-01-01';
 
     /**
      * pdo
@@ -457,7 +464,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
      */
     protected function getDenormalizedData($calendarData) {
 
-        $vObject = Sabre_VObject_Reader::read($calendarData);
+        $vObject = VObject\Reader::read($calendarData);
         $componentType = null;
         $component = null;
         $firstOccurence = null;
@@ -479,9 +486,9 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
                     $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
                 } elseif (isset($component->DURATION)) {
                     $endDate = clone $component->DTSTART->getDateTime();
-                    $endDate->add(Sabre_VObject_DateTimeParser::parse($component->DURATION->value));
+                    $endDate->add(VObject\DateTimeParser::parse($component->DURATION->value));
                     $lastOccurence = $endDate->getTimeStamp();
-                } elseif ($component->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) {
+                } elseif ($component->DTSTART->getDateType()===VObject\Property\DateTime::DATE) {
                     $endDate = clone $component->DTSTART->getDateTime();
                     $endDate->modify('+1 day');
                     $lastOccurence = $endDate->getTimeStamp();
@@ -489,7 +496,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
                     $lastOccurence = $firstOccurence;
                 }
             } else {
-                $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID);
+                $it = new VObject\RecurrenceIterator($vObject, (string)$component->UID);
                 $maxDate = new DateTime(self::MAX_DATE);
                 if ($it->isInfinite()) {
                     $lastOccurence = $maxDate->getTimeStamp();
index 81708198b5c7fab45934eaa88c6334a35985c4b3..bff1b41e719ba05b59fd4dffbd7d403f3a937a93 100644 (file)
@@ -24,7 +24,7 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
     /**
      * CalDAV backend
      *
-     * @var Sabre_CalDAV_Backend_Abstract
+     * @var Sabre_CalDAV_Backend_BackendInterface
      */
     protected $caldavBackend;
 
@@ -39,10 +39,10 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
      * Constructor
      *
      * @param Sabre_DAVACL_IPrincipalBackend $principalBackend
-     * @param Sabre_CalDAV_Backend_Abstract $caldavBackend
+     * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
      * @param array $calendarInfo
      */
-    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) {
+    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $calendarInfo) {
 
         $this->caldavBackend = $caldavBackend;
         $this->principalBackend = $principalBackend;
index 1adf8689c1094484d0032ea2d142060d8cf4ce9a..318a4fb52834b74410ad6e608ed340d880d76c8a 100644 (file)
@@ -12,7 +12,7 @@
 class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV_ICalendarObject, Sabre_DAVACL_IACL {
 
     /**
-     * Sabre_CalDAV_Backend_Abstract
+     * Sabre_CalDAV_Backend_BackendInterface
      *
      * @var array
      */
@@ -35,11 +35,11 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
     /**
      * Constructor
      *
-     * @param Sabre_CalDAV_Backend_Abstract $caldavBackend
+     * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
      * @param array $calendarInfo
      * @param array $objectData
      */
-    public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,array $calendarInfo,array $objectData) {
+    public function __construct(Sabre_CalDAV_Backend_BackendInterface $caldavBackend,array $calendarInfo,array $objectData) {
 
         $this->caldavBackend = $caldavBackend;
 
index bd0d343382f8b684b396e6addcc445e1dbbbaae1..b95095f96fc51177fa9fb06e1a07458aad94e4de 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * Parses the calendar-query report request body.
  *
@@ -68,7 +70,7 @@ class Sabre_CalDAV_CalendarQueryParser {
 
         $this->xpath = new DOMXPath($dom);
         $this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
-        $this->xpath->registerNameSpace('dav','urn:DAV');
+        $this->xpath->registerNameSpace('dav','DAV:');
 
     }
 
@@ -241,12 +243,12 @@ class Sabre_CalDAV_CalendarQueryParser {
         $timeRangeNode = $timeRangeNodes->item(0);
 
         if ($start = $timeRangeNode->getAttribute('start')) {
-            $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
+            $start = VObject\DateTimeParser::parseDateTime($start);
         } else {
             $start = null;
         }
         if ($end = $timeRangeNode->getAttribute('end')) {
-            $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
+            $end = VObject\DateTimeParser::parseDateTime($end);
         } else {
             $end = null;
         }
@@ -274,13 +276,13 @@ class Sabre_CalDAV_CalendarQueryParser {
         if(!$start) {
             throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
         } 
-        $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
+        $start = VObject\DateTimeParser::parseDateTime($start);
 
         $end = $parentNode->getAttribute('end');
         if(!$end) {
             throw new Sabre_DAV_Exception_BadRequest('The "end" attribute is required for the CALDAV:expand element');
         } 
-        $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
+        $end = VObject\DateTimeParser::parseDateTime($end);
         
         if ($end <= $start) {
             throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
index 4bcd32cdf885c55af54ee023acc232506bd79143..53e86fc509f16d24e11b14737fb15e92c4328c57 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * CalendarQuery Validator
  *
@@ -22,11 +24,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
      *
      * The list of filters must be formatted as parsed by Sabre_CalDAV_CalendarQueryParser
      *
-     * @param Sabre_VObject_Component $vObject
+     * @param VObject\Component $vObject
      * @param array $filters
      * @return bool
      */
-    public function validate(Sabre_VObject_Component $vObject,array $filters) {
+    public function validate(VObject\Component $vObject,array $filters) {
 
         // The top level object is always a component filter.
         // We'll parse it manually, as it's pretty simple.
@@ -48,11 +50,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
      * component we're checking should be specified, not the component to check
      * itself.
      *
-     * @param Sabre_VObject_Component $parent
+     * @param VObject\Component $parent
      * @param array $filters
      * @return bool
      */
-    protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) {
+    protected function validateCompFilters(VObject\Component $parent, array $filters) {
 
         foreach($filters as $filter) {
 
@@ -117,11 +119,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
      * property we're checking should be specified, not the property to check
      * itself.
      *
-     * @param Sabre_VObject_Component $parent
+     * @param VObject\Component $parent
      * @param array $filters
      * @return bool
      */
-    protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) {
+    protected function validatePropFilters(VObject\Component $parent, array $filters) {
 
         foreach($filters as $filter) {
 
@@ -187,11 +189,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
      * parameter we're checking should be specified, not the parameter to check
      * itself.
      *
-     * @param Sabre_VObject_Property $parent
+     * @param VObject\Property $parent
      * @param array $filters
      * @return bool
      */
-    protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) {
+    protected function validateParamFilters(VObject\Property $parent, array $filters) {
 
         foreach($filters as $filter) {
 
@@ -243,11 +245,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
      * A single text-match should be specified as well as the specific property
      * or parameter we need to validate.
      *
-     * @param Sabre_VObject_Node $parent
+     * @param VObject\Node $parent
      * @param array $textMatch
      * @return bool
      */
-    protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) {
+    protected function validateTextMatch(VObject\Node $parent, array $textMatch) {
 
         $value = (string)$parent;
 
@@ -263,12 +265,12 @@ class Sabre_CalDAV_CalendarQueryValidator {
      * This is all based on the rules specified in rfc4791, which are quite
      * complex.
      *
-     * @param Sabre_VObject_Node $component
+     * @param VObject\Node $component
      * @param DateTime $start
      * @param DateTime $end
      * @return bool
      */
-    protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) {
+    protected function validateTimeRange(VObject\Node $component, $start, $end) {
 
         if (is_null($start)) {
             $start = new DateTime('1900-01-01');
@@ -296,7 +298,7 @@ class Sabre_CalDAV_CalendarQueryValidator {
                 if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
 
                     // Fire up the iterator!
-                    $it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
+                    $it = new VObject\RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
                     while($it->valid()) {
                         $expandedEvent = $it->getEventObject();
 
@@ -304,28 +306,29 @@ class Sabre_CalDAV_CalendarQueryValidator {
                         // one is the first to trigger. Based on this, we can
                         // determine if we can 'give up' expanding events.
                         $firstAlarm = null;
-                        foreach($expandedEvent->VALARM as $expandedAlarm) {
+                        if ($expandedEvent->VALARM !== null) {
+                            foreach($expandedEvent->VALARM as $expandedAlarm) {
 
-                            $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
-                            if ($expandedAlarm->isInTimeRange($start, $end)) {
-                                return true;
-                            }
+                                $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
+                                if ($expandedAlarm->isInTimeRange($start, $end)) {
+                                    return true;
+                                }
 
-                            if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
-                                // This is an alarm with a non-relative trigger
-                                // time, likely created by a buggy client. The
-                                // implication is that every alarm in this
-                                // recurring event trigger at the exact same
-                                // time. It doesn't make sense to traverse
-                                // further.
-                            } else {
-                                // We store the first alarm as a means to
-                                // figure out when we can stop traversing.
-                                if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
-                                    $firstAlarm = $effectiveTrigger;
+                                if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
+                                    // This is an alarm with a non-relative trigger
+                                    // time, likely created by a buggy client. The
+                                    // implication is that every alarm in this
+                                    // recurring event trigger at the exact same
+                                    // time. It doesn't make sense to traverse
+                                    // further.
+                                } else {
+                                    // We store the first alarm as a means to
+                                    // figure out when we can stop traversing.
+                                    if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
+                                        $firstAlarm = $effectiveTrigger;
+                                    }
                                 }
                             }
-
                         }
                         if (is_null($firstAlarm)) {
                             // No alarm was found.
index 5c4265c51a3cf9be4d68bc8c1794d854aedd6b0b..eb62eea75a6116ad0ddb07b994c37095919afdc5 100644 (file)
@@ -17,7 +17,7 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec
     /**
      * CalDAV backend
      *
-     * @var Sabre_CalDAV_Backend_Abstract
+     * @var Sabre_CalDAV_Backend_BackendInterface
      */
     protected $caldavBackend;
 
@@ -33,10 +33,10 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec
      *
      *
      * @param Sabre_DAVACL_IPrincipalBackend $principalBackend
-     * @param Sabre_CalDAV_Backend_Abstract $caldavBackend
+     * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
      * @param string $principalPrefix
      */
-    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend, $principalPrefix = 'principals') {
+    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $principalPrefix = 'principals') {
 
         parent::__construct($principalBackend, $principalPrefix);
         $this->caldavBackend = $caldavBackend;
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php b/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php
new file mode 100644 (file)
index 0000000..4ac617d
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Sabre_CalDAV_Exception_InvalidComponentType
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Exception_InvalidComponentType extends Sabre_DAV_Exception_Forbidden {
+
+    /**
+     * Adds in extra information in the xml response.
+     *
+     * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
+     *
+     * @param Sabre_DAV_Server $server
+     * @param DOMElement $errorNode
+     * @return void
+     */
+    public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
+
+        $doc = $errorNode->ownerDocument;
+
+        $np = $doc->createElementNS(Sabre_CalDAV_Plugin::NS_CALDAV,'cal:supported-calendar-component');
+        $errorNode->appendChild($np);
+
+    }
+
+}
\ No newline at end of file
index ec42b406b2f172c7289a00ba7975fc35f59fa877..d3e4e7b7201c27613bd333065e3da1bd5c59d247 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * ICS Exporter
  *
@@ -82,7 +84,7 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin {
      */
     public function generateICS(array $nodes) {
 
-        $calendar = new Sabre_VObject_Component('vcalendar');
+        $calendar = new VObject\Component('vcalendar');
         $calendar->version = '2.0';
         if (Sabre_DAV_Server::$exposeVersion) {
             $calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN';
@@ -103,7 +105,7 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin {
             }
             $nodeData = $node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'];
 
-            $nodeComp = Sabre_VObject_Reader::read($nodeData);
+            $nodeComp = VObject\Reader::read($nodeData);
 
             foreach($nodeComp->children() as $child) {
 
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php
new file mode 100644 (file)
index 0000000..8f6cb26
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre_CalDAV_Notifications_INode nodes as
+ * its children.
+ *
+ * @package Sabre
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Collection extends Sabre_DAV_Collection implements Sabre_CalDAV_Notifications_ICollection {
+
+    /**
+     * The notification backend
+     *
+     * @var Sabre_CalDAV_Backend_NotificationSupport
+     */
+    protected $caldavBackend;
+
+    /**
+     * Principal uri
+     *
+     * @var string
+     */
+    protected $principalUri;
+
+    /**
+     * Constructor
+     *
+     * @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
+     * @param string $principalUri
+     */
+    public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, $principalUri) {
+
+        $this->caldavBackend = $caldavBackend;
+        $this->principalUri = $principalUri;
+
+    }
+
+    /**
+     * Returns all notifications for a principal
+     *
+     * @return array
+     */
+    public function getChildren() {
+
+        $children = array();
+        $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
+
+        foreach($notifications as $notification) {
+
+            $children[] = new Sabre_CalDAV_Notifications_Node(
+                $this->caldavBackend,
+                $notification
+            );
+        }
+
+        return $children;
+
+    }
+
+    /**
+     * Returns the name of this object
+     *
+     * @return string
+     */
+    public function getName() {
+
+        return 'notifications';
+
+    }
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php
new file mode 100644 (file)
index 0000000..eb873af
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre_CalDAV_Notifications_INode nodes as
+ * its children.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_ICollection extends Sabre_DAV_ICollection {
+
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INode.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INode.php
new file mode 100644 (file)
index 0000000..5beb427
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method 
+ * MUST return an xml document that matches the requirements of the 
+ * 'caldav-notifications.txt' spec.
+ *
+ * For a complete example, check out the Notification class, which contains 
+ * some helper functions.
+ * 
+ * @package Sabre
+ * @subpackage CalDAV 
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_INode {
+
+    /**
+     * This method must return an xml element, using the 
+     * Sabre_CalDAV_Notifications_INotificationType classes.
+     * 
+     * @return Sabre_DAVNotification_INotificationType
+     */
+    function getNotificationType();
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INotificationType.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INotificationType.php
new file mode 100644 (file)
index 0000000..ecacd65
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * This interface reflects a single notification type.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_INotificationType extends Sabre_DAV_PropertyInterface {
+
+    /**
+     * Serializes the notification as a single property.
+     *
+     * You should usually just encode the single top-level element of the
+     * notification.
+     *
+     * @param Sabre_DAV_Server $server
+     * @param DOMElement $node
+     * @return void
+     */
+    function serialize(Sabre_DAV_Server $server, \DOMElement $node);
+
+    /**
+     * This method serializes the entire notification, as it is used in the
+     * response body.
+     *
+     * @param Sabre_DAV_Server $server
+     * @param DOMElement $node
+     * @return void
+     */
+    function serializeBody(Sabre_DAV_Server $server, \DOMElement $node);
+
+    /**
+     * Returns a unique id for this notification
+     *
+     * This is just the base url. This should generally be some kind of unique
+     * id.
+     *
+     * @return string
+     */
+    function getId();
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Node.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Node.php
new file mode 100644 (file)
index 0000000..910e952
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method 
+ * MUST return an xml document that matches the requirements of the 
+ * 'caldav-notifications.txt' spec.
+
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Node extends Sabre_DAV_Node implements Sabre_CalDAV_Notifications_INode {
+
+    /**
+     * The notification backend
+     * 
+     * @var Sabre_CalDAV_Backend_NotificationSupport
+     */
+    protected $caldavBackend;
+
+    /**
+     * The actual notification
+     * 
+     * @var Sabre_CalDAV_Notifications_INotificationType 
+     */
+    protected $notification;
+        
+    /**
+     * Constructor
+     *
+     * @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
+     * @param Sabre_CalDAV_Notifications_INotificationType $notification
+     */
+    public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, Sabre_CalDAV_Notifications_INotificationType $notification) {        
+
+        $this->caldavBackend = $caldavBackend;
+        $this->notification = $notification;
+
+    }
+
+    /**
+     * Returns the path name for this notification
+     * 
+     * @return id 
+     */
+    public function getName() {
+
+        return $this->notification->getId();
+
+    }
+
+    /**
+     * This method must return an xml element, using the 
+     * Sabre_CalDAV_Notifications_INotificationType classes.
+     * 
+     * @return Sabre_DAVNotification_INotificationType
+     */
+    public function getNotificationType() {
+
+        return $this->notification;
+
+    }
+
+}
diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php
new file mode 100644 (file)
index 0000000..21c8663
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * SystemStatus notification
+ *
+ * This notification can be used to indicate to the user that the system is
+ * down.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Notification_SystemStatus extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
+
+    const TYPE_LOW = 1;
+    const TYPE_MEDIUM = 2;
+    const TYPE_HIGH = 3;
+
+    /**
+     * A unique id
+     *
+     * @var string
+     */
+    protected $id;
+
+    /**
+     * The type of alert. This should be one of the TYPE_ constants.
+     *
+     * @var int
+     */
+    protected $type;
+
+    /**
+     * A human-readable description of the problem.
+     *
+     * @var string
+     */
+    protected $description;
+
+    /**
+     * A url to a website with more information for the user.
+     *
+     * @var string
+     */
+    protected $href;
+
+    /**
+     * Creates the notification.
+     *
+     * Some kind of unique id should be provided. This is used to generate a
+     * url.
+     *
+     * @param string $id
+     * @param int $type
+     * @param string $description
+     * @param string $href
+     */
+    public function __construct($id, $type = self::TYPE_HIGH, $description = null, $href = null) {
+
+        $this->id = $id;
+        $this->type = $type;
+        $this->description = $description;
+        $this->href = $href;
+
+    }
+
+    /**
+     * Serializes the notification as a single property.
+     *
+     * You should usually just encode the single top-level element of the
+     * notification.
+     *
+     * @param Sabre_DAV_Server $server
+     * @param DOMElement $node
+     * @return void
+     */
+    public function serialize(Sabre_DAV_Server $server, \DOMElement $node) {
+
+        switch($this->type) {
+            case self::TYPE_LOW :
+                $type = 'low';
+                break;
+            case self::TYPE_MEDIUM :
+                $type = 'medium';
+                break;
+            default :
+            case self::TYPE_HIGH :
+                $type = 'high';
+                break;
+        }
+
+        $prop = $node->ownerDocument->createElement('cs:systemstatus');
+        $prop->setAttribute('type', $type);
+
+        $node->appendChild($prop);
+
+    }
+
+    /**
+     * This method serializes the entire notification, as it is used in the
+     * response body.
+     *
+     * @param Sabre_DAV_Server $server
+     * @param DOMElement $node
+     * @return void
+     */
+    public function serializeBody(Sabre_DAV_Server $server, \DOMElement $node) {
+
+        switch($this->type) {
+            case self::TYPE_LOW :
+                $type = 'low';
+                break;
+            case self::TYPE_MEDIUM :
+                $type = 'medium';
+                break;
+            default :
+            case self::TYPE_HIGH :
+                $type = 'high';
+                break;
+        }
+
+        $prop = $node->ownerDocument->createElement('cs:systemstatus');
+        $prop->setAttribute('type', $type);
+
+        if ($this->description) {
+            $text = $node->ownerDocument->createTextNode($this->description);
+            $desc = $node->ownerDocument->createElement('cs:description');
+            $desc->appendChild($text);
+            $prop->appendChild($desc);
+        }
+        if ($this->href) {
+            $text = $node->ownerDocument->createTextNode($this->href);
+            $href = $node->ownerDocument->createElement('d:href');
+            $href->appendChild($text);
+            $prop->appendChild($href);
+        }
+
+        $node->appendChild($prop);
+
+    }
+
+    /**
+     * Returns a unique id for this notification
+     *
+     * This is just the base url. This should generally be some kind of unique
+     * id.
+     *
+     * @return string
+     */
+    public function getId() {
+
+        return $this->id;
+
+    }
+
+}
index faf426f81e2dd200b51d74d02fd312a5d6958009..c0e4a206de7ef36a22016c960a210a6a65b18505 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * CalDAV plugin
  *
@@ -162,6 +164,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
         $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
         $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
+        $server->subscribeEvent('beforeMethod', array($this,'beforeMethod'));
 
         $server->xmlNamespaces[self::NS_CALDAV] = 'cal';
         $server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs';
@@ -172,6 +175,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         $server->resourceTypeMapping['Sabre_CalDAV_Schedule_IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox';
         $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
         $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
+        $server->resourceTypeMapping['Sabre_CalDAV_Notifications_ICollection'] = '{' . self::NS_CALENDARSERVER . '}notifications';
+        $server->resourceTypeMapping['Sabre_CalDAV_Notifications_INode'] = '{' . self::NS_CALENDARSERVER . '}notification';
 
         array_push($server->protectedProperties,
 
@@ -195,7 +200,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             // CalendarServer extensions
             '{' . self::NS_CALENDARSERVER . '}getctag',
             '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
-            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
+            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for',
+            '{' . self::NS_CALENDARSERVER . '}notification-URL',
+            '{' . self::NS_CALENDARSERVER . '}notificationtype'
 
         );
     }
@@ -380,8 +387,31 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
             }
 
+            // notification-URL property
+            $notificationUrl = '{' . self::NS_CALENDARSERVER . '}notification-URL';
+            if (($index = array_search($notificationUrl, $requestedProperties)) !== false) {
+                $principalId = $node->getName();
+                $calendarHomePath = 'calendars/' . $principalId . '/notifications/';
+                unset($requestedProperties[$index]);
+                $returnedProperties[200][$notificationUrl] = new Sabre_DAV_Property_Href($calendarHomePath);
+            }
+
         } // instanceof IPrincipal
 
+        if ($node instanceof Sabre_CalDAV_Notifications_INode) {
+
+            $propertyName = '{' . self::NS_CALENDARSERVER . '}notificationtype';
+            if (($index = array_search($propertyName, $requestedProperties)) !== false) {
+
+                $returnedProperties[200][$propertyName] =
+                    $node->getNotificationType();
+
+                unset($requestedProperties[$index]);
+
+            }
+
+        } // instanceof Notifications_INode
+
 
         if ($node instanceof Sabre_CalDAV_ICalendarObject) {
             // The calendar-data property is not supposed to be a 'real'
@@ -414,11 +444,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
     public function calendarMultiGetReport($dom) {
 
         $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
-        $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
+        $hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
 
         $xpath = new DOMXPath($dom);
         $xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
-        $xpath->registerNameSpace('dav','urn:DAV');
+        $xpath->registerNameSpace('dav','DAV:');
 
         $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand');
         if ($expand->length>0) {
@@ -428,8 +458,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             if(!$start || !$end) {
                 throw new Sabre_DAV_Exception_BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element');
             }
-            $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
-            $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
+            $start = VObject\DateTimeParser::parseDateTime($start);
+            $end = VObject\DateTimeParser::parseDateTime($end);
 
             if ($end <= $start) {
                 throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
@@ -448,7 +478,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
 
             if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
-                $vObject = Sabre_VObject_Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
+                $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
                 $vObject->expand($start, $end);
                 $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
             }
@@ -515,7 +545,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
 
                 $validator = new Sabre_CalDAV_CalendarQueryValidator();
-                $vObject = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+                $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                 if ($validator->validate($vObject,$parser->filters)) {
 
                     // If the client didn't require the calendar-data property,
@@ -549,7 +579,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
                 if ($parser->expand) {
                     // We need to do some post-processing
-                    $vObject = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+                    $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                     $vObject->expand($parser->expand['start'], $parser->expand['end']);
                     $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                 }
@@ -589,10 +619,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
         }
         if ($start) {
-            $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
+            $start = VObject\DateTimeParser::parseDateTime($start);
         }
         if ($end) {
-            $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
+            $end = VObject\DateTimeParser::parseDateTime($end);
         }
 
         if (!$start && !$end) {
@@ -619,7 +649,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             return $obj;
         }, $calendar->getChildren());
 
-        $generator = new Sabre_VObject_FreeBusyGenerator();
+        $generator = new VObject\FreeBusyGenerator();
         $generator->setObjects($objects);
         $generator->setTimeRange($start, $end);
         $result = $generator->getResult();
@@ -648,7 +678,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         if (!$node instanceof Sabre_CalDAV_ICalendarObject)
             return;
 
-        $this->validateICalendar($data);
+        $this->validateICalendar($data, $path);
 
     }
 
@@ -668,7 +698,49 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         if (!$parentNode instanceof Sabre_CalDAV_Calendar)
             return;
 
-        $this->validateICalendar($data);
+        $this->validateICalendar($data, $path);
+
+    }
+
+    /**
+     * This event is triggered before any HTTP request is handled.
+     *
+     * We use this to intercept GET calls to notification nodes, and return the
+     * proper response.
+     * 
+     * @param string $method 
+     * @param string $path 
+     * @return void 
+     */
+    public function beforeMethod($method, $path) {
+
+        if ($method!=='GET') return;
+
+        try {
+            $node = $this->server->tree->getNodeForPath($path);
+        } catch (Sabre_DAV_Exception_NotFound $e) {
+            return;
+        }
+
+        if (!$node instanceof Sabre_CalDAV_Notifications_INode)
+            return;
+
+        $dom = new DOMDocument('1.0', 'UTF-8');
+        $dom->formatOutput = true;
+
+        $root = $dom->createElement('cs:notification');
+        foreach($this->server->xmlNamespaces as $namespace => $prefix) {
+            $root->setAttribute('xmlns:' . $prefix, $namespace);
+        }
+
+        $dom->appendChild($root);
+        $node->getNotificationType()->serializeBody($this->server, $root);
+
+        $this->server->httpResponse->setHeader('Content-Type','application/xml');
+        $this->server->httpResponse->sendStatus(200);
+        $this->server->httpResponse->sendBody($dom->saveXML());
+
+        return false;
 
     }
 
@@ -678,9 +750,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
      * An exception is thrown if it's not.
      *
      * @param resource|string $data
+     * @param string $path
      * @return void
      */
-    protected function validateICalendar(&$data) {
+    protected function validateICalendar(&$data, $path) {
 
         // If it's a stream, we convert it to a string first.
         if (is_resource($data)) {
@@ -692,9 +765,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
         try {
 
-            $vobj = Sabre_VObject_Reader::read($data);
+            $vobj = VObject\Reader::read($data);
 
-        } catch (Sabre_VObject_ParseException $e) {
+        } catch (VObject\ParseException $e) {
 
             throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
 
@@ -704,6 +777,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.');
         }
 
+        // Get the Supported Components for the target calendar
+        list($parentPath,$object) = Sabre_Dav_URLUtil::splitPath($path);
+        $calendarProperties = $this->server->getProperties($parentPath,array('{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'));
+        $supportedComponents = $calendarProperties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue();
+
         $foundType = null;
         $foundUID = null;
         foreach($vobj->getComponents() as $component) {
@@ -715,6 +793,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
                 case 'VJOURNAL' :
                     if (is_null($foundType)) {
                         $foundType = $component->name;
+                        if (!in_array($foundType, $supportedComponents)) {
+                            throw new Sabre_CalDAV_Exception_InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
+                        }
                         if (!isset($component->UID)) {
                             throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID');
                         }
@@ -756,7 +837,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
             throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests');
         }
 
-        if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) {
+        if (!preg_match('/^mailto:(.*)@(.*)$/i', $originator)) {
             throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address');
         }
         $originator = substr($originator,7);
@@ -765,7 +846,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         foreach($recipients as $k=>$recipient) {
 
             $recipient = trim($recipient);
-            if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) {
+            if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) {
                 throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address');
             }
             $recipient = substr($recipient, 7);
@@ -789,8 +870,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         }
 
         try {
-            $vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true));
-        } catch (Sabre_VObject_ParseException $e) {
+            $vObject = VObject\Reader::read($this->server->httpRequest->getBody(true));
+        } catch (VObject\ParseException $e) {
             throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
         }
 
@@ -813,9 +894,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
         }
 
         if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') {
-            $this->iMIPMessage($originator, $recipients, $vObject, $principal);
+            $result = $this->iMIPMessage($originator, $recipients, $vObject, $principal);
             $this->server->httpResponse->sendStatus(200);
-            $this->server->httpResponse->sendBody('Messages sent');
+            $this->server->httpResponse->setHeader('Content-Type','application/xml');
+            $this->server->httpResponse->sendBody($this->generateScheduleResponse($result));
         } else {
             throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented');
         }
@@ -825,18 +907,81 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
     /**
      * Sends an iMIP message by email.
      *
+     * This method must return an array with status codes per recipient.
+     * This should look something like:
+     *
+     * array(
+     *    'user1@example.org' => '2.0;Success'
+     * )
+     *
+     * Formatting for this status code can be found at:
+     * https://tools.ietf.org/html/rfc5545#section-3.8.8.3
+     *
+     * A list of valid status codes can be found at:
+     * https://tools.ietf.org/html/rfc5546#section-3.6
+     *
      * @param string $originator
      * @param array $recipients
-     * @param Sabre_VObject_Component $vObject
-     * @param string $principal Principal url
-     * @return void
+     * @param Sabre\VObject\Component $vObject
+     * @return array
      */
-    protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) {
+    protected function iMIPMessage($originator, array $recipients, VObject\Component $vObject, $principal) {
 
         if (!$this->imipHandler) {
-            throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.');
+            $resultStatus = '5.2;This server does not support this operation';
+        } else {
+            $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
+            $resultStatus = '2.0;Success';
         }
-        $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
+
+        $result = array();
+        foreach($recipients as $recipient) {
+            $result[$recipient] = $resultStatus;
+        }
+
+        return $result;
+
+    }
+
+    /**
+     * Generates a schedule-response XML body
+     *
+     * The recipients array is a key->value list, containing email addresses
+     * and iTip status codes. See the iMIPMessage method for a description of
+     * the value.
+     *
+     * @param array $recipients
+     * @return string
+     */
+    public function generateScheduleResponse(array $recipients) {
+
+        $dom = new DOMDocument('1.0','utf-8');
+        $dom->formatOutput = true;
+        $xscheduleResponse = $dom->createElement('cal:schedule-response');
+        $dom->appendChild($xscheduleResponse);
+
+        foreach($this->server->xmlNamespaces as $namespace=>$prefix) {
+
+            $xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
+
+        }
+
+        foreach($recipients as $recipient=>$status) {
+            $xresponse = $dom->createElement('cal:response');
+
+            $xrecipient = $dom->createElement('cal:recipient');
+            $xrecipient->appendChild($dom->createTextNode($recipient));
+            $xresponse->appendChild($xrecipient);
+
+            $xrequestStatus = $dom->createElement('cal:request-status');
+            $xrequestStatus->appendChild($dom->createTextNode($status));
+            $xresponse->appendChild($xrequestStatus);
+
+            $xscheduleResponse->appendChild($xresponse);
+
+        }
+
+        return $dom->saveXML();
 
     }
 
index 1be63a06bc4989d931665ee4c84b0371a5725796..f62f94af394e8aae6ba0112278b119b96be01ee7 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * iMIP handler.
  *
@@ -44,11 +46,11 @@ class Sabre_CalDAV_Schedule_IMip {
      *
      * @param string $originator Originator Email
      * @param array $recipients Array of email addresses
-     * @param Sabre_VObject_Component $vObject
+     * @param Sabre\VObject\Component $vObject
      * @param string $principal Principal Url of the originator
      * @return void
      */
-    public function sendMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) {
+    public function sendMessage($originator, array $recipients, VObject\Component $vObject, $principal) {
 
         foreach($recipients as $recipient) {
 
index b8d3f0573fa4c9c2b43084b1a63937aaf137b33f..da8c3b60d7aaaa02dd0c43f72ea8aa452813d599 100644 (file)
@@ -21,7 +21,7 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
     /**
      * CalDAV backend
      *
-     * @var Sabre_CalDAV_Backend_Abstract
+     * @var Sabre_CalDAV_Backend_BackendInterface
      */
     protected $caldavBackend;
 
@@ -36,10 +36,10 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
      * Constructor
      *
      * @param Sabre_DAVACL_IPrincipalBackend $principalBackend
-     * @param Sabre_CalDAV_Backend_Abstract $caldavBackend
+     * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
      * @param mixed $userUri
      */
-    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) {
+    public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $userUri) {
 
         $this->principalBackend = $principalBackend;
         $this->caldavBackend = $caldavBackend;
@@ -171,6 +171,11 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
             $objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar);
         }
         $objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']);
+
+        // We're adding a notifications node, if it's supported by the backend.
+        if ($this->caldavBackend instanceof Sabre_CalDAV_Backend_NotificationSupport) {
+            $objs[] = new Sabre_CalDAV_Notifications_Collection($this->caldavBackend, $this->principalInfo['uri']);
+        }
         return $objs;
 
     }
index 1ec584bb6bb504968e43fd1dff9b927d4ceb3aed..413a77f3bccfb86695a294fcffcb2bb244beafd6 100644 (file)
@@ -238,7 +238,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract {
      * Creates a new card.
      *
      * The addressbook id will be passed as the first argument. This is the
-     * same id as it is returned from the getCards method.
+     * same id as it is returned from the getAddressbooksForUser method.
      *
      * The cardUri is a base uri, and doesn't include the full path. The
      * cardData argument is the vcard body, and is passed as a string.
index 66f91af3b0cfd93d50b7f5275b42c6e76140cce6..30194bb6469f9c85c3164e04b69267d42cf7de42 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * CardDAV plugin
  *
@@ -154,8 +156,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
                 if (is_resource($val))
                     $val = stream_get_contents($val);
 
-                // Taking out \r to not screw up the xml output
-                $returnedProperties[200][$addressDataProp] = str_replace("\r","", $val);
+                $returnedProperties[200][$addressDataProp] = $val;
 
             }
         }
@@ -270,7 +271,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
         $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
 
-        $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
+        $hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
         $propertyList = array();
 
         foreach($hrefElems as $elem) {
@@ -346,9 +347,9 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
         try {
 
-            $vobj = Sabre_VObject_Reader::read($data);
+            $vobj = VObject\Reader::read($data);
 
-        } catch (Sabre_VObject_ParseException $e) {
+        } catch (VObject\ParseException $e) {
 
             throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
 
@@ -358,6 +359,10 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
             throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
         }
 
+        if (!isset($vobj->UID)) {
+            throw new Sabre_DAV_Exception_BadRequest('Every vcard must have an UID.');
+        }
+
     }
 
 
@@ -438,7 +443,9 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
      */
     public function validateFilters($vcardData, array $filters, $test) {
 
-        $vcard = Sabre_VObject_Reader::read($vcardData);
+        $vcard = VObject\Reader::read($vcardData);
+
+        if (!$filters) return true;
 
         foreach($filters as $filter) {
 
index 98126f8e55e80b4dcb965b3638ad8cbfdbe63bc8..8f3dcc78f86319d73467702e9eddb65b032f1398 100644 (file)
@@ -485,19 +485,17 @@ class Sabre_DAV_Client {
      */
     public function parseMultiStatus($body) {
 
-        $body = Sabre_DAV_XMLUtil::convertDAVNamespace($body);
-
         $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
         if ($responseXML===false) {
             throw new InvalidArgumentException('The passed data is not valid XML');
         }
 
-        $responseXML->registerXPathNamespace('d', 'urn:DAV');
+        $responseXML->registerXPathNamespace('d', 'DAV:');
 
         $propResult = array();
 
         foreach($responseXML->xpath('d:response') as $response) {
-            $response->registerXPathNamespace('d', 'urn:DAV');
+            $response->registerXPathNamespace('d', 'DAV:');
             $href = $response->xpath('d:href');
             $href = (string)$href[0];
 
@@ -505,7 +503,7 @@ class Sabre_DAV_Client {
 
             foreach($response->xpath('d:propstat') as $propStat) {
 
-                $propStat->registerXPathNamespace('d', 'urn:DAV');
+                $propStat->registerXPathNamespace('d', 'DAV:');
                 $status = $propStat->xpath('d:status');
                 list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3);
 
index 3719d095102a3a213cfbd5b51ef8242d7b2f276d..957ac506a9c1bb9baf96e2689fae69ea17ca50b1 100644 (file)
@@ -293,7 +293,10 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin {
             $this->server->tree->getNodeForPath($uri);
 
             // We need to call the beforeWriteContent event for RFC3744
-            $this->server->broadcastEvent('beforeWriteContent',array($uri));
+            // Edit: looks like this is not used, and causing problems now.
+            //
+            // See Issue 222
+            // $this->server->broadcastEvent('beforeWriteContent',array($uri));
 
         } catch (Sabre_DAV_Exception_NotFound $e) {
 
index 1cfada3236c5500a64e4c343d0437db7f22dbb76..26f2c1d08489bbf2adcddc6bf6f800306474186b 100644 (file)
@@ -11,9 +11,7 @@
  * @author Evert Pot (http://www.rooftopsolutions.nl/)
  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  */
-abstract class Sabre_DAV_Property {
-
-    abstract function serialize(Sabre_DAV_Server $server, DOMElement $prop);
+abstract class Sabre_DAV_Property implements Sabre_DAV_PropertyInterface {
 
     static function unserialize(DOMElement $prop) {
 
index 88afbcfb26d4e6d8b96f27e6b6e6d2978e81ed3d..9f21163d12ef0fce55b021c7279b5e3d6d6aec4e 100644 (file)
@@ -138,7 +138,7 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA
                 if (is_scalar($propertyValue)) {
                     $text = $document->createTextNode($propertyValue);
                     $currentProperty->appendChild($text);
-                } elseif ($propertyValue instanceof Sabre_DAV_Property) {
+                } elseif ($propertyValue instanceof Sabre_DAV_PropertyInterface) {
                     $propertyValue->serialize($server,$currentProperty);
                 } elseif (!is_null($propertyValue)) {
                     throw new Sabre_DAV_Exception('Unknown property value type: ' . gettype($propertyValue) . ' for property: ' . $propertyName);
diff --git a/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php b/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php
new file mode 100644 (file)
index 0000000..515072c
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * PropertyInterface
+ *
+ * Implement this interface to create new complex properties
+ *
+ * @package Sabre
+ * @subpackage DAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_DAV_PropertyInterface {
+
+    public function serialize(Sabre_DAV_Server $server, DOMElement $prop);
+
+    static function unserialize(DOMElement $prop); 
+
+}
+
index 65a88cfc1dbf6b590e95f73a04f13a29c9aec9fd..aa586ae3536cfec81ed811b3b6029e251ad54d77 100644 (file)
@@ -207,6 +207,10 @@ class Sabre_DAV_Server {
 
         } catch (Exception $e) {
 
+            try {
+                $this->broadcastEvent('exception', array($e));
+            } catch (Exception $ignore) {
+            }
             $DOM = new DOMDocument('1.0','utf-8');
             $DOM->formatOutput = true;
 
@@ -508,7 +512,7 @@ class Sabre_DAV_Server {
 
         if (!$this->checkPreconditions(true)) return false;
 
-        if (!($node instanceof Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects');
+        if (!$node instanceof Sabre_DAV_IFile) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects');
         $body = $node->get();
 
         // Converting string into stream, if needed.
@@ -1995,7 +1999,7 @@ class Sabre_DAV_Server {
         if (!$body) return array();
 
         $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
-        $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0);
+        $elem = $dom->getElementsByTagNameNS('DAV:','propfind')->item(0);
         return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem));
 
     }
index 85a9ee317be3c43daa2495908e2800d119ddc11c..40580ae366fe235173661769fd408461e976e999 100644 (file)
@@ -42,9 +42,9 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree {
         $realPath = $this->getRealPath($path);
         if (!file_exists($realPath)) throw new Sabre_DAV_Exception_NotFound('File at location ' . $realPath . ' not found');
         if (is_dir($realPath)) {
-            return new Sabre_DAV_FS_Directory($path);
+            return new Sabre_DAV_FS_Directory($realPath);
         } else {
-            return new Sabre_DAV_FS_File($path);
+            return new Sabre_DAV_FS_File($realPath);
         }
 
     }
index 60eff3b159ac15bf34a4f09fd0f2d7a721542785..712fa3fc014f37d43c028cba7d6d69b1d8a0f4e3 100644 (file)
@@ -20,9 +20,6 @@ class Sabre_DAV_XMLUtil {
      * {http://www.example.org}myelem
      *
      * This format is used throughout the SabreDAV sourcecode.
-     * Elements encoded with the urn:DAV namespace will
-     * be returned as if they were in the DAV: namespace. This is to avoid
-     * compatibility problems.
      *
      * This function will return null if a nodetype other than an Element is passed.
      *
@@ -33,8 +30,7 @@ class Sabre_DAV_XMLUtil {
 
         if ($dom->nodeType !== XML_ELEMENT_NODE) return null;
 
-        // Mapping back to the real namespace, in case it was dav
-        if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI;
+        $ns = $dom->namespaceURI;
 
         // Mapping to clark notation
         return '{' . $ns . '}' . $dom->localName;
@@ -64,29 +60,11 @@ class Sabre_DAV_XMLUtil {
 
     }
 
-    /**
-     * This method takes an XML document (as string) and converts all instances of the
-     * DAV: namespace to urn:DAV
-     *
-     * This is unfortunately needed, because the DAV: namespace violates the xml namespaces
-     * spec, and causes the DOM to throw errors
-     *
-     * @param string $xmlDocument
-     * @return array|string|null
-     */
-    static function convertDAVNamespace($xmlDocument) {
-
-        // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
-        // namespace is actually a violation of the XML namespaces specification, and will cause errors
-        return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument);
-
-    }
-
     /**
      * This method provides a generic way to load a DOMDocument for WebDAV use.
      *
      * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors.
-     * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
+     * It does not preserve whitespace.
      *
      * @param string $xml
      * @throws Sabre_DAV_Exception_BadRequest
@@ -118,10 +96,11 @@ class Sabre_DAV_XMLUtil {
         libxml_clear_errors();
 
         $dom = new DOMDocument();
-        $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR);
 
         // We don't generally care about any whitespace
         $dom->preserveWhiteSpace = false;
+        
+        $dom->loadXML($xml,LIBXML_NOWARNING | LIBXML_NOERROR);
 
         if ($error = libxml_get_last_error()) {
             libxml_clear_errors();
index 05e1a690b3cb23c1fdb3ba05ce19b18aa41f9fd7..3f79a8d532e3a9419700c0f72bba37e94fe7e7e2 100644 (file)
@@ -88,11 +88,11 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property {
     static public function unserialize(DOMElement $dom) {
 
         $privileges = array();
-        $xaces = $dom->getElementsByTagNameNS('urn:DAV','ace');
+        $xaces = $dom->getElementsByTagNameNS('DAV:','ace');
         for($ii=0; $ii < $xaces->length; $ii++) {
 
             $xace = $xaces->item($ii);
-            $principal = $xace->getElementsByTagNameNS('urn:DAV','principal');
+            $principal = $xace->getElementsByTagNameNS('DAV:','principal');
             if ($principal->length !== 1) {
                 throw new Sabre_DAV_Exception_BadRequest('Each {DAV:}ace element must have one {DAV:}principal element');
             }
@@ -116,17 +116,17 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property {
 
             $protected = false;
 
-            if ($xace->getElementsByTagNameNS('urn:DAV','protected')->length > 0) {
+            if ($xace->getElementsByTagNameNS('DAV:','protected')->length > 0) {
                 $protected = true;
             }
 
-            $grants = $xace->getElementsByTagNameNS('urn:DAV','grant');
+            $grants = $xace->getElementsByTagNameNS('DAV:','grant');
             if ($grants->length < 1) {
                 throw new Sabre_DAV_Exception_NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported');
             }
             $grant = $grants->item(0);
 
-            $xprivs = $grant->getElementsByTagNameNS('urn:DAV','privilege');
+            $xprivs = $grant->getElementsByTagNameNS('DAV:','privilege');
             for($jj=0; $jj<$xprivs->length; $jj++) {
 
                 $xpriv = $xprivs->item($jj);
index a747cc6a31bf134e2b409d4d39d002d12e38c6f9..f90ed24f5d80c8622a1d806607be31d2771cc1bd 100644 (file)
@@ -46,7 +46,7 @@ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth {
 
         if (strpos(strtolower($auth),'basic')!==0) return false;
 
-        return explode(':', base64_decode(substr($auth, 6)));
+        return explode(':', base64_decode(substr($auth, 6)),2);
 
     }
 
index 23dc7f8a7a17d9de217836a293c09e01645d5af0..e6b4f7e53589964fd2db22be2f43ad62d771689f 100644 (file)
@@ -14,7 +14,7 @@ class Sabre_HTTP_Version {
     /**
      * Full version number
      */
-    const VERSION = '1.6.2';
+    const VERSION = '1.6.4';
 
     /**
      * Stability : alpha, beta, stable
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component.php b/dav/SabreDAV/lib/Sabre/VObject/Component.php
deleted file mode 100644 (file)
index b78a261..0000000
+++ /dev/null
@@ -1,365 +0,0 @@
-<?php
-
-/**
- * VObject Component
- *
- * This class represents a VCALENDAR/VCARD component. A component is for example
- * VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
- * ends with END:COMPONENTNAME
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component extends Sabre_VObject_Element {
-
-    /**
-     * Name, for example VEVENT
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Children properties and components
-     *
-     * @var array
-     */
-    public $children = array();
-
-    /**
-     * If coponents are added to this map, they will be automatically mapped
-     * to their respective classes, if parsed by the reader or constructed with
-     * the 'create' method.
-     *
-     * @var array
-     */
-    static public $classMap = array(
-        'VCALENDAR'     => 'Sabre_VObject_Component_VCalendar',
-        'VEVENT'        => 'Sabre_VObject_Component_VEvent',
-        'VTODO'         => 'Sabre_VObject_Component_VTodo',
-        'VJOURNAL'      => 'Sabre_VObject_Component_VJournal',
-        'VALARM'        => 'Sabre_VObject_Component_VAlarm',
-    );
-
-    /**
-     * Creates the new component by name, but in addition will also see if
-     * there's a class mapped to the property name.
-     *
-     * @param string $name
-     * @param string $value
-     * @return Sabre_VObject_Component
-     */
-    static public function create($name, $value = null) {
-
-        $name = strtoupper($name);
-
-        if (isset(self::$classMap[$name])) {
-            return new self::$classMap[$name]($name, $value);
-        } else {
-            return new self($name, $value);
-        }
-
-    }
-
-    /**
-     * Creates a new component.
-     *
-     * By default this object will iterate over its own children, but this can
-     * be overridden with the iterator argument
-     *
-     * @param string $name
-     * @param Sabre_VObject_ElementList $iterator
-     */
-    public function __construct($name, Sabre_VObject_ElementList $iterator = null) {
-
-        $this->name = strtoupper($name);
-        if (!is_null($iterator)) $this->iterator = $iterator;
-
-    }
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        $str = "BEGIN:" . $this->name . "\r\n";
-
-        /**
-         * Gives a component a 'score' for sorting purposes.
-         *
-         * This is solely used by the childrenSort method.
-         *
-         * A higher score means the item will be higher in the list
-         *
-         * @param Sabre_VObject_Node $n
-         * @return int
-         */
-        $sortScore = function($n) {
-
-            if ($n instanceof Sabre_VObject_Component) {
-                // We want to encode VTIMEZONE first, this is a personal
-                // preference.
-                if ($n->name === 'VTIMEZONE') {
-                    return 1;
-                } else {
-                    return 0;
-                }
-            } else {
-                // VCARD version 4.0 wants the VERSION property to appear first
-                if ($n->name === 'VERSION') {
-                    return 3;
-                } else {
-                    return 2;
-                }
-            }
-
-        };
-
-        usort($this->children, function($a, $b) use ($sortScore) {
-
-            $sA = $sortScore($a);
-            $sB = $sortScore($b);
-
-            if ($sA === $sB) return 0;
-
-            return ($sA > $sB) ? -1 : 1;
-
-        });
-
-        foreach($this->children as $child) $str.=$child->serialize();
-        $str.= "END:" . $this->name . "\r\n";
-
-        return $str;
-
-    }
-
-    /**
-     * Adds a new component or element
-     *
-     * You can call this method with the following syntaxes:
-     *
-     * add(Sabre_VObject_Element $element)
-     * add(string $name, $value)
-     *
-     * The first version adds an Element
-     * The second adds a property as a string.
-     *
-     * @param mixed $item
-     * @param mixed $itemValue
-     * @return void
-     */
-    public function add($item, $itemValue = null) {
-
-        if ($item instanceof Sabre_VObject_Element) {
-            if (!is_null($itemValue)) {
-                throw new InvalidArgumentException('The second argument must not be specified, when passing a VObject');
-            }
-            $item->parent = $this;
-            $this->children[] = $item;
-        } elseif(is_string($item)) {
-
-            if (!is_scalar($itemValue)) {
-                throw new InvalidArgumentException('The second argument must be scalar');
-            }
-            $item = Sabre_VObject_Property::create($item,$itemValue);
-            $item->parent = $this;
-            $this->children[] = $item;
-
-        } else {
-
-            throw new InvalidArgumentException('The first argument must either be a Sabre_VObject_Element or a string');
-
-        }
-
-    }
-
-    /**
-     * Returns an iterable list of children
-     *
-     * @return Sabre_VObject_ElementList
-     */
-    public function children() {
-
-        return new Sabre_VObject_ElementList($this->children);
-
-    }
-
-    /**
-     * Returns an array with elements that match the specified name.
-     *
-     * This function is also aware of MIME-Directory groups (as they appear in
-     * vcards). This means that if a property is grouped as "HOME.EMAIL", it
-     * will also be returned when searching for just "EMAIL". If you want to
-     * search for a property in a specific group, you can select on the entire
-     * string ("HOME.EMAIL"). If you want to search on a specific property that
-     * has not been assigned a group, specify ".EMAIL".
-     *
-     * Keys are retained from the 'children' array, which may be confusing in
-     * certain cases.
-     *
-     * @param string $name
-     * @return array
-     */
-    public function select($name) {
-
-        $group = null;
-        $name = strtoupper($name);
-        if (strpos($name,'.')!==false) {
-            list($group,$name) = explode('.', $name, 2);
-        }
-
-        $result = array();
-        foreach($this->children as $key=>$child) {
-
-            if (
-                strtoupper($child->name) === $name &&
-                (is_null($group) || ( $child instanceof Sabre_VObject_Property && strtoupper($child->group) === $group))
-            ) {
-
-                $result[$key] = $child;
-
-            }
-        }
-
-        reset($result);
-        return $result;
-
-    }
-
-    /**
-     * This method only returns a list of sub-components. Properties are
-     * ignored.
-     *
-     * @return array
-     */
-    public function getComponents() {
-
-        $result = array();
-        foreach($this->children as $child) {
-            if ($child instanceof Sabre_VObject_Component) {
-                $result[] = $child;
-            }
-        }
-
-        return $result;
-
-    }
-
-    /* Magic property accessors {{{ */
-
-    /**
-     * Using 'get' you will either get a property or component,
-     *
-     * If there were no child-elements found with the specified name,
-     * null is returned.
-     *
-     * @param string $name
-     * @return Sabre_VObject_Property
-     */
-    public function __get($name) {
-
-        $matches = $this->select($name);
-        if (count($matches)===0) {
-            return null;
-        } else {
-            $firstMatch = current($matches);
-            /** @var $firstMatch Sabre_VObject_Property */
-            $firstMatch->setIterator(new Sabre_VObject_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 Sabre_VObject_Component, Sabre_VObject_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 Sabre_VObject_Component || $value instanceof Sabre_VObject_Property) {
-            $value->parent = $this;
-            if (!is_null($overWrite)) {
-                $this->children[$overWrite] = $value;
-            } else {
-                $this->children[] = $value;
-            }
-        } elseif (is_scalar($value)) {
-            $property = Sabre_VObject_Property::create($name,$value);
-            $property->parent = $this;
-            if (!is_null($overWrite)) {
-                $this->children[$overWrite] = $property;
-            } else {
-                $this->children[] = $property;
-            }
-        } else {
-            throw new InvalidArgumentException('You must pass a Sabre_VObject_Component, Sabre_VObject_Property or scalar type');
-        }
-
-    }
-
-    /**
-     * Removes all properties and components within this component.
-     *
-     * @param string $name
-     * @return void
-     */
-    public function __unset($name) {
-
-        $matches = $this->select($name);
-        foreach($matches as $k=>$child) {
-
-            unset($this->children[$k]);
-            $child->parent = null;
-
-        }
-
-    }
-
-    /* }}} */
-
-    /**
-     * This method is automatically called when the object is cloned.
-     * Specifically, this will ensure all child elements are also cloned.
-     *
-     * @return void
-     */
-    public function __clone() {
-
-        foreach($this->children as $key=>$child) {
-            $this->children[$key] = clone $child;
-            $this->children[$key]->parent = $this;
-        }
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VAlarm.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VAlarm.php
deleted file mode 100644 (file)
index 1d1dd69..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-/**
- * VAlarm component
- *
- * This component contains some additional functionality specific for VALARMs.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component_VAlarm extends Sabre_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 = Sabre_VObject_DateTimeParser::parseDuration($this->TRIGGER);
-            $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
-
-            $parentComponent = $this->parent;
-            if ($related === 'START') {
-                $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
-                $effectiveTrigger->add($triggerDuration);
-            } else {
-                if ($parentComponent->name === 'VTODO') {
-                    $endProp = 'DUE';
-                } elseif ($parentComponent->name === 'VEVENT') {
-                    $endProp = 'DTEND';
-                } else {
-                    throw new Sabre_DAV_Exception('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 = Sabre_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 = Sabre_VObject_DateTimeParser::parseDuration($this->DURATION);
-            $repeat = (string)$this->repeat;
-            if (!$repeat) {
-                $repeat = 1;
-            }
-
-            $period = new DatePeriod($effectiveTrigger, $duration, (int)$repeat);
-
-            foreach($period as $occurrence) {
-
-                if ($start <= $occurrence && $end > $occurrence) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
-        }
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php
deleted file mode 100644 (file)
index f3be29a..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-/**
- * The VCalendar component
- *
- * This component adds functionality to a component, specific for a VCALENDAR.
- * 
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/) 
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component_VCalendar extends Sabre_VObject_Component {
-
-    /**
-     * Returns a list of all 'base components'. For instance, if an Event has 
-     * a recurrence rule, and one instance is overridden, the overridden event 
-     * will have the same UID, but will be excluded from this list.
-     *
-     * VTIMEZONE components will always be excluded. 
-     *
-     * @param string $componentName filter by component name 
-     * @return array 
-     */
-    public function getBaseComponents($componentName = null) {
-
-        $components = array();
-        foreach($this->children as $component) {
-
-            if (!$component instanceof Sabre_VObject_Component)
-                continue;
-
-            if (isset($component->{'RECURRENCE-ID'})) 
-                continue;
-
-            if ($componentName && $component->name !== strtoupper($componentName)) 
-                continue;
-
-            if ($component->name === 'VTIMEZONE')
-                continue;
-
-            $components[] = $component;
-
-        }
-
-        return $components;
-
-    }
-
-    /**
-     * If this calendar object, has events with recurrence rules, this method 
-     * can be used to expand the event into multiple sub-events.
-     *
-     * Each event will be stripped from it's recurrence information, and only 
-     * the instances of the event in the specified timerange will be left 
-     * alone.
-     *
-     * In addition, this method will cause timezone information to be stripped, 
-     * and normalized to UTC.
-     *
-     * This method will alter the VCalendar. This cannot be reversed.
-     *
-     * This functionality is specifically used by the CalDAV standard. It is 
-     * possible for clients to request expand events, if they are rather simple 
-     * clients and do not have the possibility to calculate recurrences.
-     *
-     * @param DateTime $start
-     * @param DateTime $end 
-     * @return void
-     */
-    public function expand(DateTime $start, DateTime $end) {
-
-        $newEvents = array();
-
-        foreach($this->select('VEVENT') as $key=>$vevent) {
-
-            if (isset($vevent->{'RECURRENCE-ID'})) {
-                unset($this->children[$key]);
-                continue;
-            } 
-
-
-            if (!$vevent->rrule) {
-                unset($this->children[$key]);
-                if ($vevent->isInTimeRange($start, $end)) {
-                    $newEvents[] = $vevent;
-                }
-                continue;
-            }
-
-            $uid = (string)$vevent->uid;
-            if (!$uid) {
-                throw new LogicException('Event did not have a UID!');
-            }
-
-            $it = new Sabre_VObject_RecurrenceIterator($this, $vevent->uid);
-            $it->fastForward($start);
-
-            while($it->valid() && $it->getDTStart() < $end) {
-
-                if ($it->getDTEnd() > $start) {
-
-                    $newEvents[] = $it->getEventObject();
-
-                }
-                $it->next();
-
-            }
-            unset($this->children[$key]);
-
-        }
-
-        foreach($newEvents as $newEvent) {
-
-            foreach($newEvent->children as $child) {
-                if ($child instanceof Sabre_VObject_Property_DateTime &&
-                    $child->getDateType() == Sabre_VObject_Property_DateTime::LOCALTZ) {
-                        $child->setDateTime($child->getDateTime(),Sabre_VObject_Property_DateTime::UTC);
-                    }
-            }
-
-            $this->add($newEvent);
-
-        }
-
-        // Removing all VTIMEZONE components
-        unset($this->VTIMEZONE);
-
-    } 
-
-}
-
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VEvent.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VEvent.php
deleted file mode 100644 (file)
index 684f5f5..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/**
- * VEvent component
- *
- * This component contains some additional functionality specific for VEVENT's.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component_VEvent extends Sabre_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 Sabre_VObject_RecurrenceIterator($this);
-            $it->fastForward($start);
-
-            // We fast-forwarded to a spot where the end-time of the
-            // recurrence instance exceeded the start of the requested
-            // time-range.
-            //
-            // If the starttime of the recurrence did not exceed the
-            // end of the time range as well, we have a match.
-            return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
-
-        }
-
-        $effectiveStart = $this->DTSTART->getDateTime();
-        if (isset($this->DTEND)) {
-            $effectiveEnd = $this->DTEND->getDateTime();
-            // If this was an all-day event, we should just increase the
-            // end-date by 1. Otherwise the event will last until the second
-            // the date changed, by increasing this by 1 day the event lasts
-            // all of the last day as well.
-            if ($this->DTSTART->getDateType() == Sabre_VObject_Property_DateTime::DATE) {
-                $effectiveEnd->modify('+1 day');
-            }
-        } elseif (isset($this->DURATION)) {
-            $effectiveEnd = clone $effectiveStart;
-            $effectiveEnd->add( Sabre_VObject_DateTimeParser::parseDuration($this->DURATION) );
-        } elseif ($this->DTSTART->getDateType() == Sabre_VObject_Property_DateTime::DATE) {
-            $effectiveEnd = clone $effectiveStart;
-            $effectiveEnd->modify('+1 day');
-        } else {
-            $effectiveEnd = clone $effectiveStart;
-        }
-        return (
-            ($start <= $effectiveEnd) && ($end > $effectiveStart)
-        );
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VJournal.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VJournal.php
deleted file mode 100644 (file)
index 6d39492..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * VJournal component
- *
- * This component contains some additional functionality specific for VJOURNALs.
- * 
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/) 
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component_VJournal extends Sabre_VObject_Component {
-
-    /**
-     * Returns true or false depending on if the event falls in the specified 
-     * time-range. This is used for filtering purposes. 
-     *
-     * The rules used to determine if an event falls within the specified 
-     * time-range is based on the CalDAV specification.
-     *
-     * @param DateTime $start
-     * @param DateTime $end 
-     * @return bool 
-     */
-    public function isInTimeRange(DateTime $start, DateTime $end) {
-
-        $dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
-        if ($dtstart) {
-            $effectiveEnd = clone $dtstart;
-            if ($this->DTSTART->getDateType() == Sabre_VObject_Property_DateTime::DATE) {
-                $effectiveEnd->modify('+1 day');
-            }
-
-            return ($start <= $effectiveEnd && $end > $dtstart);
-
-        }
-        return false;
-
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VTodo.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VTodo.php
deleted file mode 100644 (file)
index 2fb6654..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-/**
- * VTodo component
- *
- * This component contains some additional functionality specific for VTODOs.
- * 
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/) 
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Component_VTodo extends Sabre_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)?Sabre_VObject_DateTimeParser::parseDuration($this->DURATION):null;
-        $due = isset($this->DUE)?$this->DUE->getDateTime():null;
-        $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
-        $created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
-
-        if ($dtstart) {
-            if ($duration) {
-                $effectiveEnd = clone $dtstart;
-                $effectiveEnd->add($duration);
-                return $start <= $effectiveEnd && $end > $dtstart;
-            } elseif ($due) {
-                return
-                    ($start < $due || $start <= $dtstart) &&
-                    ($end > $dtstart || $end >= $due);
-            } else {
-                return $start <= $dtstart && $end > $dtstart;
-            }
-        }
-        if ($due) {
-            return ($start < $due && $end >= $due);
-        }
-        if ($completed && $created) {
-            return
-                ($start <= $created || $start <= $completed) &&
-                ($end >= $created || $end >= $completed);
-        }
-        if ($completed) {
-            return ($start <= $completed && $end >= $completed);
-        }
-        if ($created) {
-            return ($end > $created);
-        }
-        return true;
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/DateTimeParser.php b/dav/SabreDAV/lib/Sabre/VObject/DateTimeParser.php
deleted file mode 100644 (file)
index 23a4bb6..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-<?php
-
-/**
- * DateTimeParser
- *
- * This class is responsible for parsing the several different date and time
- * formats iCalendar and vCards have.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_DateTimeParser {
-
-    /**
-     * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
-     *
-     * Specifying a reference timezone is optional. It will only be used
-     * if the non-UTC format is used. The argument is used as a reference, the
-     * returned DateTime object will still be in the UTC timezone.
-     *
-     * @param string $dt
-     * @param DateTimeZone $tz
-     * @return DateTime
-     */
-    static public function parseDateTime($dt,DateTimeZone $tz = null) {
-
-        // Format is YYYYMMDD + "T" + hhmmss
-        $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
-
-        if (!$result) {
-            throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar datetime value is incorrect: ' . $dt);
-        }
-
-        if ($matches[7]==='Z' || is_null($tz)) {
-            $tz = new DateTimeZone('UTC');
-        }
-        $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
-
-        // Still resetting the timezone, to normalize everything to UTC
-        $date->setTimeZone(new DateTimeZone('UTC'));
-        return $date;
-
-    }
-
-    /**
-     * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
-     *
-     * @param string $date
-     * @return DateTime
-     */
-    static public function parseDate($date) {
-
-        // Format is YYYYMMDD
-        $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
-
-        if (!$result) {
-            throw new Sabre_DAV_Exception_BadRequest('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 Sabre_DAV_Exception_BadRequest('The supplied iCalendar duration value is incorrect: ' . $duration);
-        }
-
-        if (!$asString) {
-            $invert = false;
-            if ($matches['plusminus']==='-') {
-                $invert = true;
-            }
-
-
-            $parts = array(
-                'week',
-                'day',
-                'hour',
-                'minute',
-                'second',
-            );
-            foreach($parts as $part) {
-                $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
-            }
-
-
-            // We need to re-construct the $duration string, because weeks and
-            // days are not supported by DateInterval in the same string.
-            $duration = 'P';
-            $days = $matches['day'];
-            if ($matches['week']) {
-                $days+=$matches['week']*7;
-            }
-            if ($days)
-                $duration.=$days . 'D';
-
-            if ($matches['minute'] || $matches['second'] || $matches['hour']) {
-                $duration.='T';
-
-                if ($matches['hour'])
-                    $duration.=$matches['hour'].'H';
-
-                if ($matches['minute'])
-                    $duration.=$matches['minute'].'M';
-
-                if ($matches['second'])
-                    $duration.=$matches['second'].'S';
-
-            }
-
-            if ($duration==='P') {
-                $duration = 'PT0S';
-            }
-            $iv = new DateInterval($duration);
-            if ($invert) $iv->invert = true;
-
-            return $iv;
-
-        }
-
-
-
-        $parts = array(
-            'week',
-            'day',
-            'hour',
-            'minute',
-            'second',
-        );
-
-        $newDur = '';
-        foreach($parts as $part) {
-            if (isset($matches[$part]) && $matches[$part]) {
-                $newDur.=' '.$matches[$part] . ' ' . $part . 's';
-            }
-        }
-
-        $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
-        if ($newDur === '+') { $newDur = '+0 seconds'; };
-        return $newDur;
-
-    }
-
-    /**
-     * Parses either a Date or DateTime, or Duration value.
-     *
-     * @param string $date
-     * @param DateTimeZone|string $referenceTZ
-     * @return DateTime|DateInterval
-     */
-    static public function parse($date, $referenceTZ = null) {
-
-        if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
-            return self::parseDuration($date);
-        } elseif (strlen($date)===8) {
-            return self::parseDate($date);
-        } else {
-            return self::parseDateTime($date, $referenceTZ);
-        }
-
-    }
-
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Element.php b/dav/SabreDAV/lib/Sabre/VObject/Element.php
deleted file mode 100644 (file)
index e20ff0b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-/**
- * Base class for all elements
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-abstract class Sabre_VObject_Element extends Sabre_VObject_Node {
-
-    public $parent = null;
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/ElementList.php b/dav/SabreDAV/lib/Sabre/VObject/ElementList.php
deleted file mode 100644 (file)
index 7e508db..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?php
-
-/**
- * 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.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_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 Sabre_VObject_Element
-     */
-    public function current() {
-
-        return $this->elements[$this->key];
-
-    }
-
-    /**
-     * To the next item in the iterator
-     *
-     * @return void
-     */
-    public function next() {
-
-        $this->key++;
-
-    }
-
-    /**
-     * Returns the current iterator key
-     *
-     * @return int
-     */
-    public function key() {
-
-        return $this->key;
-
-    }
-
-    /**
-     * Returns true if the current position in the iterator is a valid one
-     *
-     * @return bool
-     */
-    public function valid() {
-
-        return isset($this->elements[$this->key]);
-
-    }
-
-    /**
-     * Rewinds the iterator
-     *
-     * @return void
-     */
-    public function rewind() {
-
-        $this->key = 0;
-
-    }
-
-    /* }}} */
-
-    /* {{{ Countable interface */
-
-    /**
-     * Returns the number of elements
-     *
-     * @return int
-     */
-    public function count() {
-
-        return count($this->elements);
-
-    }
-
-    /* }}} */
-
-    /* {{{ ArrayAccess Interface */
-
-
-    /**
-     * Checks if an item exists through ArrayAccess.
-     *
-     * @param int $offset
-     * @return bool
-     */
-    public function offsetExists($offset) {
-
-        return isset($this->elements[$offset]);
-
-    }
-
-    /**
-     * Gets an item through ArrayAccess.
-     *
-     * @param int $offset
-     * @return mixed
-     */
-    public function offsetGet($offset) {
-
-        return $this->elements[$offset];
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * @param int $offset
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($offset,$value) {
-
-        throw new LogicException('You can not add new objects to an ElementList');
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return void
-     */
-    public function offsetUnset($offset) {
-
-        throw new LogicException('You can not remove objects from an ElementList');
-
-    }
-
-    /* }}} */
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/FreeBusyGenerator.php b/dav/SabreDAV/lib/Sabre/VObject/FreeBusyGenerator.php
deleted file mode 100644 (file)
index 1c96a64..0000000
+++ /dev/null
@@ -1,297 +0,0 @@
-<?php
-
-/**
- * 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.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_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 Sabre_VObject_Component
-     */
-    protected $baseObject;
-
-    /**
-     * 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 Sabre_VObject_Component $vcalendar
-     * @return void
-     */
-    public function setBaseObject(Sabre_VObject_Component $vcalendar) {
-
-        $this->baseObject = $vcalendar;
-
-    }
-
-    /**
-     * Sets the input objects
-     *
-     * Every object must either be a string or a Sabre_VObject_Component.
-     *
-     * @param array $objects
-     * @return void
-     */
-    public function setObjects(array $objects) {
-
-        $this->objects = array();
-        foreach($objects as $object) {
-
-            if (is_string($object)) {
-                $this->objects[] = Sabre_VObject_Reader::read($object);
-            } elseif ($object instanceof Sabre_VObject_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 Sabre_VObject_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 Sabre_VObject_RecurrenceIterator($object, (string)$component->uid);
-                            if ($this->start) {
-                                $iterator->fastForward($this->start);
-                            }
-
-                            $maxRecurrences = 200;
-
-                            while($iterator->valid() && --$maxRecurrences) {
-
-                                $startTime = $iterator->getDTStart();
-                                if ($this->end && $startTime > $this->end) {
-                                    break;
-                                }
-                                $times[] = array(
-                                    $iterator->getDTStart(),
-                                    $iterator->getDTEnd(),
-                                );
-
-                                $iterator->next();
-
-                            }
-
-                        } else {
-
-                            $startTime = $component->DTSTART->getDateTime();
-                            if ($this->end && $startTime > $this->end) {
-                                break;
-                            }
-                            $endTime = null;
-                            if (isset($component->DTEND)) {
-                                $endTime = $component->DTEND->getDateTime();
-                            } elseif (isset($component->DURATION)) {
-                                $duration = Sabre_VObject_DateTimeParser::parseDuration((string)$component->DURATION);
-                                $endTime = clone $startTime;
-                                $endTime->add($duration);
-                            } elseif ($component->DTSTART->getDateType() === Sabre_VObject_Property_DateTime::DATE) {
-                                $endTime = clone $startTime;
-                                $endTime->modify('+1 day');
-                            } else {
-                                // The event had no duration (0 seconds)
-                                break;
-                            }
-
-                            $times[] = array($startTime, $endTime);
-
-                        }
-
-                        foreach($times as $time) {
-
-                            if ($this->end && $time[0] > $this->end) break;
-                            if ($this->start && $time[1] < $this->start) break;
-
-                            $busyTimes[] = array(
-                                $time[0],
-                                $time[1],
-                                $FBTYPE,
-                            );
-                        }
-                        break;
-
-                    case 'VFREEBUSY' :
-                        foreach($component->FREEBUSY as $freebusy) {
-
-                            $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
-
-                            // Skipping intervals marked as 'free'
-                            if ($fbType==='FREE')
-                                continue;
-
-                            $values = explode(',', $freebusy);
-                            foreach($values as $value) {
-                                list($startTime, $endTime) = explode('/', $value);
-                                $startTime = Sabre_VObject_DateTimeParser::parseDateTime($startTime);
-
-                                if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
-                                    $duration = Sabre_VObject_DateTimeParser::parseDuration($endTime);
-                                    $endTime = clone $startTime;
-                                    $endTime->add($duration);
-                                } else {
-                                    $endTime = Sabre_VObject_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 Sabre_VObject_Component('VCALENDAR');
-            $calendar->version = '2.0';
-            if (Sabre_DAV_Server::$exposeVersion) {
-                $calendar->prodid = '-//SabreDAV//Sabre VObject ' . Sabre_VObject_Version::VERSION . '//EN';
-            } else {
-                $calendar->prodid = '-//SabreDAV//Sabre VObject//EN';
-            }
-            $calendar->calscale = 'GREGORIAN';
-        }
-
-        $vfreebusy = new Sabre_VObject_Component('VFREEBUSY');
-        $calendar->add($vfreebusy);
-
-        if ($this->start) {
-            $dtstart = new Sabre_VObject_Property_DateTime('DTSTART');
-            $dtstart->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC);
-            $vfreebusy->add($dtstart);
-        }
-        if ($this->end) {
-            $dtend = new Sabre_VObject_Property_DateTime('DTEND');
-            $dtend->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC);
-            $vfreebusy->add($dtend);
-        }
-        $dtstamp = new Sabre_VObject_Property_DateTime('DTSTAMP');
-        $dtstamp->setDateTime(new DateTime('now'), Sabre_VObject_Property_DateTime::UTC);
-        $vfreebusy->add($dtstamp);
-
-        foreach($busyTimes as $busyTime) {
-
-            $busyTime[0]->setTimeZone(new DateTimeZone('UTC'));
-            $busyTime[1]->setTimeZone(new DateTimeZone('UTC'));
-
-            $prop = new Sabre_VObject_Property(
-                'FREEBUSY',
-                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
-            );
-            $prop['FBTYPE'] = $busyTime[2];
-            $vfreebusy->add($prop);
-
-        }
-
-        return $calendar;
-
-    }
-
-}
-
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Node.php b/dav/SabreDAV/lib/Sabre/VObject/Node.php
deleted file mode 100644 (file)
index 7701309..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-/**
- * Base class for all nodes
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Countable {
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    abstract function serialize();
-
-    /**
-     * Iterator override
-     *
-     * @var Sabre_VObject_ElementList
-     */
-    protected $iterator = null;
-
-    /**
-     * A link to the parent node
-     *
-     * @var Sabre_VObject_Node
-     */
-    public $parent = null;
-
-    /* {{{ IteratorAggregator interface */
-
-    /**
-     * Returns the iterator for this object
-     *
-     * @return Sabre_VObject_ElementList
-     */
-    public function getIterator() {
-
-        if (!is_null($this->iterator))
-            return $this->iterator;
-
-        return new Sabre_VObject_ElementList(array($this));
-
-    }
-
-    /**
-     * Sets the overridden iterator
-     *
-     * Note that this is not actually part of the iterator interface
-     *
-     * @param Sabre_VObject_ElementList $iterator
-     * @return void
-     */
-    public function setIterator(Sabre_VObject_ElementList $iterator) {
-
-        $this->iterator = $iterator;
-
-    }
-
-    /* }}} */
-
-    /* {{{ Countable interface */
-
-    /**
-     * Returns the number of elements
-     *
-     * @return int
-     */
-    public function count() {
-
-        $it = $this->getIterator();
-        return $it->count();
-
-    }
-
-    /* }}} */
-
-    /* {{{ ArrayAccess Interface */
-
-
-    /**
-     * Checks if an item exists through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return bool
-     */
-    public function offsetExists($offset) {
-
-        $iterator = $this->getIterator();
-        return $iterator->offsetExists($offset);
-
-    }
-
-    /**
-     * Gets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return mixed
-     */
-    public function offsetGet($offset) {
-
-        $iterator = $this->getIterator();
-        return $iterator->offsetGet($offset);
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($offset,$value) {
-
-        $iterator = $this->getIterator();
-        $iterator->offsetSet($offset,$value);
-
-    }
-
-    /**
-     * Sets an item through ArrayAccess.
-     *
-     * This method just forwards the request to the inner iterator
-     *
-     * @param int $offset
-     * @return void
-     */
-    public function offsetUnset($offset) {
-
-        $iterator = $this->getIterator();
-        $iterator->offsetUnset($offset);
-
-    }
-
-    /* }}} */
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Parameter.php b/dav/SabreDAV/lib/Sabre/VObject/Parameter.php
deleted file mode 100644 (file)
index 2e39af5..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-
-/**
- * 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.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Parameter extends Sabre_VObject_Node {
-
-    /**
-     * Parameter name
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Parameter value
-     *
-     * @var string
-     */
-    public $value;
-
-    /**
-     * Sets up the object
-     *
-     * @param string $name
-     * @param string $value
-     */
-    public function __construct($name, $value = null) {
-
-        $this->name = strtoupper($name);
-        $this->value = $value;
-
-    }
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        if (is_null($this->value)) {
-            return $this->name;
-        }
-        $src = array(
-            '\\',
-            "\n",
-            ';',
-            ',',
-        );
-        $out = array(
-            '\\\\',
-            '\n',
-            '\;',
-            '\,',
-        );
-
-        return $this->name . '=' . str_replace($src, $out, $this->value);
-
-    }
-
-    /**
-     * Called when this object is being cast to a string
-     *
-     * @return string
-     */
-    public function __toString() {
-
-        return $this->value;
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/ParseException.php b/dav/SabreDAV/lib/Sabre/VObject/ParseException.php
deleted file mode 100644 (file)
index 1b5e95b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * Exception thrown by Sabre_VObject_Reader if an invalid object was attempted to be parsed.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_ParseException extends Exception { }
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Property.php b/dav/SabreDAV/lib/Sabre/VObject/Property.php
deleted file mode 100644 (file)
index 3b212c8..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-<?php
-
-/**
- * VObject Property
- *
- * A property in VObject is usually in the form PARAMNAME:paramValue.
- * An example is : SUMMARY:Weekly meeting
- *
- * Properties can also have parameters:
- * SUMMARY;LANG=en:Weekly meeting.
- *
- * Parameters can be accessed using the ArrayAccess interface.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Property extends Sabre_VObject_Element {
-
-    /**
-     * Propertyname
-     *
-     * @var string
-     */
-    public $name;
-
-    /**
-     * Group name
-     *
-     * This may be something like 'HOME' for vcards.
-     *
-     * @var string
-     */
-    public $group;
-
-    /**
-     * Property parameters
-     *
-     * @var array
-     */
-    public $parameters = array();
-
-    /**
-     * Property value
-     *
-     * @var string
-     */
-    public $value;
-
-    /**
-     * If properties are added to this map, they will be automatically mapped
-     * to their respective classes, if parsed by the reader or constructed with
-     * the 'create' method.
-     *
-     * @var array
-     */
-    static public $classMap = array(
-        'COMPLETED'     => 'Sabre_VObject_Property_DateTime',
-        'CREATED'       => 'Sabre_VObject_Property_DateTime',
-        'DTEND'         => 'Sabre_VObject_Property_DateTime',
-        'DTSTAMP'       => 'Sabre_VObject_Property_DateTime',
-        'DTSTART'       => 'Sabre_VObject_Property_DateTime',
-        'DUE'           => 'Sabre_VObject_Property_DateTime',
-        'EXDATE'        => 'Sabre_VObject_Property_MultiDateTime',
-        'LAST-MODIFIED' => 'Sabre_VObject_Property_DateTime',
-        'RECURRENCE-ID' => 'Sabre_VObject_Property_DateTime',
-        'TRIGGER'       => 'Sabre_VObject_Property_DateTime',
-    );
-
-    /**
-     * Creates the new property by name, but in addition will also see if
-     * there's a class mapped to the property name.
-     *
-     * @param string $name
-     * @param string $value
-     * @return Sabre_VObject_Property
-     */
-    static public function create($name, $value = null) {
-
-        $name = strtoupper($name);
-        $shortName = $name;
-        $group = null;
-        if (strpos($shortName,'.')!==false) {
-            list($group, $shortName) = explode('.', $shortName);
-        }
-
-        if (isset(self::$classMap[$shortName])) {
-            return new self::$classMap[$shortName]($name, $value);
-        } else {
-            return new self($name, $value);
-        }
-
-    }
-
-    /**
-     * Creates a new property object
-     *
-     * By default this object will iterate over its own children, but this can
-     * be overridden with the iterator argument
-     *
-     * @param string $name
-     * @param string $value
-     * @param Sabre_VObject_ElementList $iterator
-     */
-    public function __construct($name, $value = null, $iterator = null) {
-
-        $name = strtoupper($name);
-        $group = null;
-        if (strpos($name,'.')!==false) {
-            list($group, $name) = explode('.', $name);
-        }
-        $this->name = $name;
-        $this->group = $group;
-        if (!is_null($iterator)) $this->iterator = $iterator;
-        $this->setValue($value);
-
-    }
-
-
-
-    /**
-     * Updates the internal value
-     *
-     * @param string $value
-     * @return void
-     */
-    public function setValue($value) {
-
-        $this->value = $value;
-
-    }
-
-    /**
-     * Turns the object back into a serialized blob.
-     *
-     * @return string
-     */
-    public function serialize() {
-
-        $str = $this->name;
-        if ($this->group) $str = $this->group . '.' . $this->name;
-
-        if (count($this->parameters)) {
-            foreach($this->parameters as $param) {
-
-                $str.=';' . $param->serialize();
-
-            }
-        }
-        $src = array(
-            '\\',
-            "\n",
-        );
-        $out = array(
-            '\\\\',
-            '\n',
-        );
-        $str.=':' . str_replace($src, $out, $this->value);
-
-        $out = '';
-        while(strlen($str)>0) {
-            if (strlen($str)>75) {
-                $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
-                $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
-            } else {
-                $out.=$str . "\r\n";
-                $str='';
-                break;
-            }
-        }
-
-        return $out;
-
-    }
-
-    /**
-     * Adds a new componenten or element
-     *
-     * You can call this method with the following syntaxes:
-     *
-     * add(Sabre_VObject_Parameter $element)
-     * add(string $name, $value)
-     *
-     * The first version adds an Parameter
-     * The second adds a property as a string.
-     *
-     * @param mixed $item
-     * @param mixed $itemValue
-     * @return void
-     */
-    public function add($item, $itemValue = null) {
-
-        if ($item instanceof Sabre_VObject_Parameter) {
-            if (!is_null($itemValue)) {
-                throw new InvalidArgumentException('The second argument must not be specified, when passing a VObject');
-            }
-            $item->parent = $this;
-            $this->parameters[] = $item;
-        } elseif(is_string($item)) {
-
-            if (!is_scalar($itemValue) && !is_null($itemValue)) {
-                throw new InvalidArgumentException('The second argument must be scalar');
-            }
-            $parameter = new Sabre_VObject_Parameter($item,$itemValue);
-            $parameter->parent = $this;
-            $this->parameters[] = $parameter;
-
-        } else {
-
-            throw new InvalidArgumentException('The first argument must either be a Sabre_VObject_Element or a string');
-
-        }
-
-    }
-
-    /* ArrayAccess interface {{{ */
-
-    /**
-     * Checks if an array element exists
-     *
-     * @param mixed $name
-     * @return bool
-     */
-    public function offsetExists($name) {
-
-        if (is_int($name)) return parent::offsetExists($name);
-
-        $name = strtoupper($name);
-
-        foreach($this->parameters as $parameter) {
-            if ($parameter->name == $name) return true;
-        }
-        return false;
-
-    }
-
-    /**
-     * Returns a parameter, or parameter list.
-     *
-     * @param string $name
-     * @return Sabre_VObject_Element
-     */
-    public function offsetGet($name) {
-
-        if (is_int($name)) return parent::offsetGet($name);
-        $name = strtoupper($name);
-
-        $result = array();
-        foreach($this->parameters as $parameter) {
-            if ($parameter->name == $name)
-                $result[] = $parameter;
-        }
-
-        if (count($result)===0) {
-            return null;
-        } elseif (count($result)===1) {
-            return $result[0];
-        } else {
-            $result[0]->setIterator(new Sabre_VObject_ElementList($result));
-            return $result[0];
-        }
-
-    }
-
-    /**
-     * Creates a new parameter
-     *
-     * @param string $name
-     * @param mixed $value
-     * @return void
-     */
-    public function offsetSet($name, $value) {
-
-        if (is_int($name)) parent::offsetSet($name, $value);
-
-        if (is_scalar($value)) {
-            if (!is_string($name))
-                throw new InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.');
-
-            $this->offsetUnset($name);
-            $parameter = new Sabre_VObject_Parameter($name, $value);
-            $parameter->parent = $this;
-            $this->parameters[] = $parameter;
-
-        } elseif ($value instanceof Sabre_VObject_Parameter) {
-            if (!is_null($name))
-                throw new InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a Sabre_VObject_Parameter. Add using $array[]=$parameterObject.');
-
-            $value->parent = $this;
-            $this->parameters[] = $value;
-        } else {
-            throw new InvalidArgumentException('You can only add parameters to the property object');
-        }
-
-    }
-
-    /**
-     * Removes one or more parameters with the specified name
-     *
-     * @param string $name
-     * @return void
-     */
-    public function offsetUnset($name) {
-
-        if (is_int($name)) parent::offsetUnset($name);
-        $name = strtoupper($name);
-
-        foreach($this->parameters as $key=>$parameter) {
-            if ($parameter->name == $name) {
-                $parameter->parent = null;
-                unset($this->parameters[$key]);
-            }
-
-        }
-
-    }
-
-    /* }}} */
-
-    /**
-     * Called when this object is being cast to a string
-     *
-     * @return string
-     */
-    public function __toString() {
-
-        return (string)$this->value;
-
-    }
-
-    /**
-     * This method is automatically called when the object is cloned.
-     * Specifically, this will ensure all child elements are also cloned.
-     *
-     * @return void
-     */
-    public function __clone() {
-
-        foreach($this->parameters as $key=>$child) {
-            $this->parameters[$key] = clone $child;
-            $this->parameters[$key]->parent = $this;
-        }
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php b/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php
deleted file mode 100755 (executable)
index fe2372c..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-<?php
-
-/**
- * DateTime property
- *
- * This element is used for iCalendar properties such as the DTSTART property.
- * It basically provides a few helper functions that make it easier to deal
- * with these. It supports both DATE-TIME and DATE values.
- *
- * In order to use this correctly, you must call setDateTime and getDateTime to
- * retrieve and modify dates respectively.
- *
- * If you use the 'value' or properties directly, this object does not keep
- * reference and results might appear incorrectly.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Property_DateTime extends Sabre_VObject_Property {
-
-    /**
-     * Local 'floating' time
-     */
-    const LOCAL = 1;
-
-    /**
-     * UTC-based time
-     */
-    const UTC = 2;
-
-    /**
-     * Local time plus timezone
-     */
-    const LOCALTZ = 3;
-
-    /**
-     * Only a date, time is ignored
-     */
-    const DATE = 4;
-
-    /**
-     * DateTime representation
-     *
-     * @var DateTime
-     */
-    protected $dateTime;
-
-    /**
-     * dateType
-     *
-     * @var int
-     */
-    protected $dateType;
-
-    /**
-     * Updates the Date and Time.
-     *
-     * @param DateTime $dt
-     * @param int $dateType
-     * @return void
-     */
-    public function setDateTime(DateTime $dt, $dateType = self::LOCALTZ) {
-
-        switch($dateType) {
-
-            case self::LOCAL :
-                $this->setValue($dt->format('Ymd\\THis'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case self::UTC :
-                $dt->setTimeZone(new DateTimeZone('UTC'));
-                $this->setValue($dt->format('Ymd\\THis\\Z'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case self::LOCALTZ :
-                $this->setValue($dt->format('Ymd\\THis'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE-TIME');
-                $this->offsetSet('TZID', $dt->getTimeZone()->getName());
-                break;
-            case self::DATE :
-                $this->setValue($dt->format('Ymd'));
-                $this->offsetUnset('VALUE');
-                $this->offsetUnset('TZID');
-                $this->offsetSet('VALUE','DATE');
-                break;
-            default :
-                throw new InvalidArgumentException('You must pass a valid dateType constant');
-
-        }
-        $this->dateTime = $dt;
-        $this->dateType = $dateType;
-
-    }
-
-    /**
-     * Returns the current DateTime value.
-     *
-     * If no value was set, this method returns null.
-     *
-     * @return DateTime|null
-     */
-    public function getDateTime() {
-
-        if ($this->dateTime)
-            return $this->dateTime;
-
-        list(
-            $this->dateType,
-            $this->dateTime
-        ) = self::parseData($this->value, $this);
-        return $this->dateTime;
-
-    }
-
-    /**
-     * Returns the type of Date format.
-     *
-     * This method returns one of the format constants. If no date was set,
-     * this method will return null.
-     *
-     * @return int|null
-     */
-    public function getDateType() {
-
-        if ($this->dateType)
-            return $this->dateType;
-
-        list(
-            $this->dateType,
-            $this->dateTime,
-        ) = self::parseData($this->value, $this);
-        return $this->dateType;
-
-    }
-
-    /**
-     * Parses the internal data structure to figure out what the current date
-     * and time is.
-     *
-     * The returned array contains two elements:
-     *   1. A 'DateType' constant (as defined on this class), or null.
-     *   2. A DateTime object (or null)
-     *
-     * @param string|null $propertyValue The string to parse (yymmdd or
-     *                                   ymmddThhmmss, etc..)
-     * @param Sabre_VObject_Property|null $property The instance of the
-     *                                              property we're parsing.
-     * @return array
-     */
-    static public function parseData($propertyValue, Sabre_VObject_Property $property = null) {
-
-        if (is_null($propertyValue)) {
-            return array(null, null);
-        }
-
-        $date = '(?P<year>[1-2][0-9]{3})(?P<month>[0-1][0-9])(?P<date>[0-3][0-9])';
-        $time = '(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9])';
-        $regex = "/^$date(T$time(?P<isutc>Z)?)?$/";
-
-        if (!preg_match($regex, $propertyValue, $matches)) {
-            throw new InvalidArgumentException($propertyValue . ' is not a valid DateTime or Date string');
-        }
-
-        if (!isset($matches['hour'])) {
-            // Date-only
-            return array(
-                self::DATE,
-                new DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00'),
-            );
-        }
-
-        $dateStr =
-            $matches['year'] .'-' .
-            $matches['month'] . '-' .
-            $matches['date'] . ' ' .
-            $matches['hour'] . ':' .
-            $matches['minute'] . ':' .
-            $matches['second'];
-
-        if (isset($matches['isutc'])) {
-            $dt = new DateTime($dateStr,new DateTimeZone('UTC'));
-            $dt->setTimeZone(new DateTimeZone('UTC'));
-            return array(
-                self::UTC,
-                $dt
-            );
-        }
-
-        // Finding the timezone.
-        $tzid = $property['TZID'];
-        if (!$tzid) {
-            return array(
-                self::LOCAL,
-                new DateTime($dateStr)
-            );
-        }
-
-        try {
-            // tzid an Olson identifier?
-            $tz = new DateTimeZone($tzid->value);
-        } catch (Exception $e) {
-
-            // Not an Olson id, we're going to try to find the information 
-            // through the time zone name map.
-            $newtzid = Sabre_VObject_WindowsTimezoneMap::lookup($tzid->value);
-            if (is_null($newtzid)) {
-
-                // Not a well known time zone name either, we're going to try 
-                // to find the information through the VTIMEZONE object.
-
-                // First we find the root object
-                $root = $property;
-                while($root->parent) {
-                    $root = $root->parent;
-                }
-
-                if (isset($root->VTIMEZONE)) {
-                    foreach($root->VTIMEZONE as $vtimezone) {
-                        if (((string)$vtimezone->TZID) == $tzid) {
-                            if (isset($vtimezone->{'X-LIC-LOCATION'})) {
-                                $newtzid = (string)$vtimezone->{'X-LIC-LOCATION'};
-                            } else {
-                                // No libical location specified. As a last resort we could 
-                                // try matching $vtimezone's DST rules against all known 
-                                // time zones returned by DateTimeZone::list*
-
-                                // TODO
-                            }
-                        }
-                    }
-                }
-            }
-
-            try {
-                $tz = new DateTimeZone($newtzid);
-            } catch (Exception $e) {
-                // If all else fails, we use the default PHP timezone
-                $tz = new DateTimeZone(date_default_timezone_get()); 
-            }
-        }
-        $dt = new DateTime($dateStr, $tz);
-        $dt->setTimeZone($tz);
-
-        return array(
-            self::LOCALTZ,
-            $dt
-        );
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Property/MultiDateTime.php b/dav/SabreDAV/lib/Sabre/VObject/Property/MultiDateTime.php
deleted file mode 100644 (file)
index ae53ab6..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-
-/**
- * Multi-DateTime property
- *
- * This element is used for iCalendar properties such as the EXDATE property.
- * It basically provides a few helper functions that make it easier to deal
- * with these. It supports both DATE-TIME and DATE values.
- *
- * In order to use this correctly, you must call setDateTimes and getDateTimes
- * to retrieve and modify dates respectively.
- *
- * If you use the 'value' or properties directly, this object does not keep
- * reference and results might appear incorrectly.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Property_MultiDateTime extends Sabre_VObject_Property {
-
-    /**
-     * DateTime representation
-     *
-     * @var DateTime[]
-     */
-    protected $dateTimes;
-
-    /**
-     * dateType
-     *
-     * This is one of the Sabre_VObject_Property_DateTime constants.
-     *
-     * @var int
-     */
-    protected $dateType;
-
-    /**
-     * Updates the value
-     *
-     * @param array $dt Must be an array of DateTime objects.
-     * @param int $dateType
-     * @return void
-     */
-    public function setDateTimes(array $dt, $dateType = Sabre_VObject_Property_DateTime::LOCALTZ) {
-
-        foreach($dt as $i)
-            if (!$i instanceof DateTime)
-                throw new InvalidArgumentException('You must pass an array of DateTime objects');
-
-        $this->offsetUnset('VALUE');
-        $this->offsetUnset('TZID');
-        switch($dateType) {
-
-            case Sabre_VObject_Property_DateTime::LOCAL :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd\\THis');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case Sabre_VObject_Property_DateTime::UTC :
-                $val = array();
-                foreach($dt as $i) {
-                    $i->setTimeZone(new DateTimeZone('UTC'));
-                    $val[] = $i->format('Ymd\\THis\\Z');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                break;
-            case Sabre_VObject_Property_DateTime::LOCALTZ :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd\\THis');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE-TIME');
-                $this->offsetSet('TZID', $dt[0]->getTimeZone()->getName());
-                break;
-            case Sabre_VObject_Property_DateTime::DATE :
-                $val = array();
-                foreach($dt as $i) {
-                    $val[] = $i->format('Ymd');
-                }
-                $this->setValue(implode(',',$val));
-                $this->offsetSet('VALUE','DATE');
-                break;
-            default :
-                throw new InvalidArgumentException('You must pass a valid dateType constant');
-
-        }
-        $this->dateTimes = $dt;
-        $this->dateType = $dateType;
-
-    }
-
-    /**
-     * Returns the current DateTime value.
-     *
-     * If no value was set, this method returns null.
-     *
-     * @return array|null
-     */
-    public function getDateTimes() {
-
-        if ($this->dateTimes)
-            return $this->dateTimes;
-
-        $dts = array();
-
-        if (!$this->value) {
-            $this->dateTimes = null;
-            $this->dateType = null;
-            return null;
-        }
-
-        foreach(explode(',',$this->value) as $val) {
-            list(
-                $type,
-                $dt
-            ) = Sabre_VObject_Property_DateTime::parseData($val, $this);
-            $dts[] = $dt;
-            $this->dateType = $type;
-        }
-        $this->dateTimes = $dts;
-        return $this->dateTimes;
-
-    }
-
-    /**
-     * Returns the type of Date format.
-     *
-     * This method returns one of the format constants. If no date was set,
-     * this method will return null.
-     *
-     * @return int|null
-     */
-    public function getDateType() {
-
-        if ($this->dateType)
-            return $this->dateType;
-
-        if (!$this->value) {
-            $this->dateTimes = null;
-            $this->dateType = null;
-            return null;
-        }
-
-        $dts = array();
-        foreach(explode(',',$this->value) as $val) {
-            list(
-                $type,
-                $dt
-            ) = Sabre_VObject_Property_DateTime::parseData($val, $this);
-            $dts[] = $dt;
-            $this->dateType = $type;
-        }
-        $this->dateTimes = $dts;
-        return $this->dateType;
-
-    }
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Reader.php b/dav/SabreDAV/lib/Sabre/VObject/Reader.php
deleted file mode 100644 (file)
index eea73fa..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-<?php
-
-/**
- * VCALENDAR/VCARD reader
- *
- * This class reads the vobject file, and returns a full element tree.
- *
- * TODO: this class currently completely works 'statically'. This is pointless,
- * and defeats OOP principals. Needs refactoring in a future version.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Reader {
-
-    /**
-     * Parses the file and returns the top component
-     *
-     * @param string $data
-     * @return Sabre_VObject_Element
-     */
-    static function read($data) {
-
-        // Normalizing newlines
-        $data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
-
-        $lines = explode("\n", $data);
-
-        // Unfolding lines
-        $lines2 = array();
-        foreach($lines as $line) {
-
-            // Skipping empty lines
-            if (!$line) continue;
-
-            if ($line[0]===" " || $line[0]==="\t") {
-                $lines2[count($lines2)-1].=substr($line,1);
-            } else {
-                $lines2[] = $line;
-            }
-
-        }
-
-        unset($lines);
-
-        reset($lines2);
-
-        return self::readLine($lines2);
-
-    }
-
-    /**
-     * Reads and parses a single line.
-     *
-     * This method receives the full array of lines. The array pointer is used
-     * to traverse.
-     *
-     * @param array $lines
-     * @return Sabre_VObject_Element
-     */
-    static private function readLine(&$lines) {
-
-        $line = current($lines);
-        $lineNr = key($lines);
-        next($lines);
-
-        // Components
-        if (stripos($line,"BEGIN:")===0) {
-
-            $componentName = strtoupper(substr($line,6));
-            $obj = Sabre_VObject_Component::create($componentName);
-
-            $nextLine = current($lines);
-
-            while(stripos($nextLine,"END:")!==0) {
-
-                $obj->add(self::readLine($lines));
-
-                $nextLine = current($lines);
-
-                if ($nextLine===false)
-                    throw new Sabre_VObject_ParseException('Invalid VObject. Document ended prematurely.');
-
-            }
-
-            // Checking component name of the 'END:' line.
-            if (substr($nextLine,4)!==$obj->name) {
-                throw new Sabre_VObject_ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"');
-            }
-            next($lines);
-
-            return $obj;
-
-        }
-
-        // Properties
-        //$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches);
-
-
-        $token = '[A-Z0-9-\.]+';
-        $parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
-        $regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
-
-        $result = preg_match($regex,$line,$matches);
-
-        if (!$result) {
-            throw new Sabre_VObject_ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format');
-        }
-
-        $propertyName = strtoupper($matches['name']);
-        $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
-            if ($matches[2]==='n' || $matches[2]==='N') {
-                return "\n";
-            } else {
-                return $matches[2];
-            }
-        }, $matches['value']);
-
-        $obj = Sabre_VObject_Property::create($propertyName, $propertyValue);
-
-        if ($matches['parameters']) {
-
-            foreach(self::readParameters($matches['parameters']) as $param) {
-                $obj->add($param);
-            }
-
-        }
-
-        return $obj;
-
-
-    }
-
-    /**
-     * Reads a parameter list from a property
-     *
-     * This method returns an array of Sabre_VObject_Parameter
-     *
-     * @param string $parameters
-     * @return array
-     */
-    static private function readParameters($parameters) {
-
-        $token = '[A-Z0-9-]+';
-
-        $paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")';
-
-        $regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
-        preg_match_all($regex, $parameters, $matches,  PREG_SET_ORDER);
-
-        $params = array();
-        foreach($matches as $match) {
-
-            $value = isset($match['paramValue'])?$match['paramValue']:null;
-
-            if (isset($value[0])) {
-                // Stripping quotes, if needed
-                if ($value[0] === '"') $value = substr($value,1,strlen($value)-2);
-            } else {
-                $value = '';
-            }
-
-            $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
-                if ($matches[2]==='n' || $matches[2]==='N') {
-                    return "\n";
-                } else {
-                    return $matches[2];
-                }
-            }, $value);
-
-            $params[] = new Sabre_VObject_Parameter($match['paramName'], $value);
-
-        }
-
-        return $params;
-
-    }
-
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php b/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php
deleted file mode 100644 (file)
index 39d1b69..0000000
+++ /dev/null
@@ -1,1024 +0,0 @@
-<?php
-
-/**
- * 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
- *   * FREQ=WEEKLY
- *     * BYDAY
- *     * 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.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_RecurrenceIterator implements Iterator {
-
-    /**
-     * The initial event date
-     *
-     * @var DateTime
-     */
-    public $startDate;
-
-    /**
-     * The end-date of the initial event
-     *
-     * @var DateTime
-     */
-    public $endDate;
-
-    /**
-     * The 'current' recurrence.
-     *
-     * This will be increased for every iteration.
-     *
-     * @var DateTime
-     */
-    public $currentDate;
-
-
-    /**
-     * List of dates that are excluded from the rules.
-     *
-     * This list contains the items that have been overriden by the EXDATE
-     * property.
-     *
-     * @var array
-     */
-    public $exceptionDates = array();
-
-    /**
-     * Base event
-     *
-     * @var Sabre_VObject_Component_VEvent
-     */
-    public $baseEvent;
-
-    /**
-     * List of dates that are overridden by other events.
-     * Similar to $overriddenEvents, but this just contains the original dates.
-     *
-     * @var array
-     */
-    public $overriddenDates = array();
-
-    /**
-     * list of events that are 'overridden'.
-     *
-     * This is an array of Sabre_VObject_Component_VEvent objects.
-     *
-     * @var array
-     */
-    public $overriddenEvents = array();
-
-
-    /**
-     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
-     * yearly.
-     *
-     * @var string
-     */
-    public $frequency;
-
-    /**
-     * The last instance of this recurrence, inclusively
-     *
-     * @var DateTime|null
-     */
-    public $until;
-
-    /**
-     * The number of recurrences, or 'null' if infinitely recurring.
-     *
-     * @var int
-     */
-    public $count;
-
-    /**
-     * The interval.
-     *
-     * If for example frequency is set to daily, interval = 2 would mean every
-     * 2 days.
-     *
-     * @var int
-     */
-    public $interval = 1;
-
-    /**
-     * Which seconds to recur.
-     *
-     * This is an array of integers (between 0 and 60)
-     *
-     * @var array
-     */
-    public $bySecond;
-
-    /**
-     * Which minutes to recur
-     *
-     * This is an array of integers (between 0 and 59)
-     *
-     * @var array
-     */
-    public $byMinute;
-
-    /**
-     * Which hours to recur
-     *
-     * This is an array of integers (between 0 and 23)
-     *
-     * @var array
-     */
-    public $byHour;
-
-    /**
-     * Which weekdays to recur.
-     *
-     * This is an array of weekdays
-     *
-     * This may also be preceeded by a positive or negative integer. If present,
-     * this indicates the nth occurrence of a specific day within the monthly or
-     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
-     * the month, or year.
-     *
-     * @var array
-     */
-    public $byDay;
-
-    /**
-     * Which days of the month to recur
-     *
-     * This is an array of days of the months (1-31). The value can also be
-     * negative. -5 for instance means the 5th last day of the month.
-     *
-     * @var array
-     */
-    public $byMonthDay;
-
-    /**
-     * Which days of the year to recur.
-     *
-     * This is an array with days of the year (1 to 366). The values can also
-     * be negative. For instance, -1 will always represent the last day of the
-     * year. (December 31st).
-     *
-     * @var array
-     */
-    public $byYearDay;
-
-    /**
-     * Which week numbers to recur.
-     *
-     * This is an array of integers from 1 to 53. The values can also be
-     * negative. -1 will always refer to the last week of the year.
-     *
-     * @var array
-     */
-    public $byWeekNo;
-
-    /**
-     * Which months to recur
-     *
-     * This is an array of integers from 1 to 12.
-     *
-     * @var array
-     */
-    public $byMonth;
-
-    /**
-     * Which items in an existing st to recur.
-     *
-     * These numbers work together with an existing by* rule. It specifies
-     * exactly which items of the existing by-rule to filter.
-     *
-     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
-     * used to recur the last workday of the month.
-     *
-     * This would be done by setting frequency to 'monthly', byDay to
-     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
-     *
-     * @var array
-     */
-    public $bySetPos;
-
-    /**
-     * When a week starts
-     *
-     * @var string
-     */
-    public $weekStart = 'MO';
-
-    /**
-     * The current item in the list
-     *
-     * @var int
-     */
-    public $counter = 0;
-
-    /**
-     * Simple mapping from iCalendar day names to day numbers
-     *
-     * @var array
-     */
-    private $dayMap = array(
-        'SU' => 0,
-        'MO' => 1,
-        'TU' => 2,
-        'WE' => 3,
-        'TH' => 4,
-        'FR' => 5,
-        'SA' => 6,
-    );
-
-    /**
-     * Mappings between the day number and english day name.
-     *
-     * @var array
-     */
-    private $dayNames = array(
-        0 => 'Sunday',
-        1 => 'Monday',
-        2 => 'Tuesday',
-        3 => 'Wednesday',
-        4 => 'Thursday',
-        5 => 'Friday',
-        6 => 'Saturday',
-    );
-
-    /**
-     * If the current iteration of the event is an overriden event, this
-     * property will hold the VObject
-     *
-     * @var Sabre_VObject_Component
-     */
-    private $currentOverriddenEvent;
-
-    /**
-     * This property may contain the date of the next not-overridden event.
-     * This date is calculated sometimes a bit early, before overridden events
-     * are evaluated.
-     *
-     * @var DateTime
-     */
-    private $nextDate;
-
-    /**
-     * Creates the iterator
-     *
-     * You should pass a VCALENDAR component, as well as the UID of the event
-     * we're going to traverse.
-     *
-     * @param Sabre_VObject_Component $vcal
-     * @param string|null $uid
-     */
-    public function __construct(Sabre_VObject_Component $vcal, $uid=null) {
-
-        if (is_null($uid)) {
-            if ($vcal->name === 'VCALENDAR') {
-                throw new InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well');
-            }
-            $components = array($vcal);
-            $uid = (string)$vcal->uid;
-        } else {
-            $components = $vcal->select('VEVENT');
-        }
-        foreach($components as $component) {
-            if ((string)$component->uid == $uid) {
-                if (isset($component->{'RECURRENCE-ID'})) {
-                    $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component;
-                    $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime();
-                } else {
-                    $this->baseEvent = $component;
-                }
-            }
-        }
-        if (!$this->baseEvent) {
-            throw new InvalidArgumentException('Could not find a base event with uid: ' . $uid);
-        }
-
-        $this->startDate = clone $this->baseEvent->DTSTART->getDateTime();
-
-        $this->endDate = null;
-        if (isset($this->baseEvent->DTEND)) {
-            $this->endDate = clone $this->baseEvent->DTEND->getDateTime();
-        } else {
-            $this->endDate = clone $this->startDate;
-            if (isset($this->baseEvent->DURATION)) {
-                $this->endDate->add(Sabre_VObject_DateTimeParser::parse($this->baseEvent->DURATION->value));
-            }
-        }
-        $this->currentDate = clone $this->startDate;
-
-        $rrule = (string)$this->baseEvent->RRULE;
-
-        $parts = explode(';', $rrule);
-
-        // If no rrule was specified, we create a default setting
-        if (!$rrule) {
-            $this->frequency = 'daily';
-            $this->count = 1;
-        } else foreach($parts as $part) {
-
-            list($key, $value) = explode('=', $part, 2);
-
-            switch(strtoupper($key)) {
-
-                case 'FREQ' :
-                    if (!in_array(
-                        strtolower($value),
-                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
-                    )) {
-                        throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
-
-                    }
-                    $this->frequency = strtolower($value);
-                    break;
-
-                case 'UNTIL' :
-                    $this->until = Sabre_VObject_DateTimeParser::parse($value);
-                    break;
-
-                case 'COUNT' :
-                    $this->count = (int)$value;
-                    break;
-
-                case 'INTERVAL' :
-                    $this->interval = (int)$value;
-                    break;
-
-                case 'BYSECOND' :
-                    $this->bySecond = explode(',', $value);
-                    break;
-
-                case 'BYMINUTE' :
-                    $this->byMinute = explode(',', $value);
-                    break;
-
-                case 'BYHOUR' :
-                    $this->byHour = explode(',', $value);
-                    break;
-
-                case 'BYDAY' :
-                    $this->byDay = explode(',', strtoupper($value));
-                    break;
-
-                case 'BYMONTHDAY' :
-                    $this->byMonthDay = explode(',', $value);
-                    break;
-
-                case 'BYYEARDAY' :
-                    $this->byYearDay = explode(',', $value);
-                    break;
-
-                case 'BYWEEKNO' :
-                    $this->byWeekNo = explode(',', $value);
-                    break;
-
-                case 'BYMONTH' :
-                    $this->byMonth = explode(',', $value);
-                    break;
-
-                case 'BYSETPOS' :
-                    $this->bySetPos = explode(',', $value);
-                    break;
-
-                case 'WKST' :
-                    $this->weekStart = strtoupper($value);
-                    break;
-
-            }
-
-        }
-
-        // Parsing exception dates
-        if (isset($this->baseEvent->EXDATE)) {
-            foreach($this->baseEvent->EXDATE as $exDate) {
-
-                foreach(explode(',', (string)$exDate) as $exceptionDate) {
-
-                    $this->exceptionDates[] =
-                        Sabre_VObject_DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone());
-
-                }
-
-            }
-
-        }
-
-    }
-
-    /**
-     * Returns the current item in the list
-     *
-     * @return DateTime
-     */
-    public function current() {
-
-        if (!$this->valid()) return null;
-        return clone $this->currentDate;
-
-    }
-
-    /**
-     * This method returns the startdate for the current iteration of the
-     * event.
-     *
-     * @return DateTime
-     */
-    public function getDtStart() {
-
-        if (!$this->valid()) return null;
-        return clone $this->currentDate;
-
-    }
-
-    /**
-     * This method returns the enddate for the current iteration of the
-     * event.
-     *
-     * @return DateTime
-     */
-    public function getDtEnd() {
-
-        if (!$this->valid()) return null;
-        $dtEnd = clone $this->currentDate;
-        $dtEnd->add( $this->startDate->diff( $this->endDate ) );
-        return clone $dtEnd;
-
-    }
-
-    /**
-     * Returns a VEVENT object with the updated start and end date.
-     *
-     * Any recurrence information is removed, and this function may return an
-     * 'overridden' event instead.
-     *
-     * This method always returns a cloned instance.
-     *
-     * @return Sabre_VObject_Component_VEvent
-     */
-    public function getEventObject() {
-
-        if ($this->currentOverriddenEvent) {
-            return clone $this->currentOverriddenEvent;
-        }
-        $event = clone $this->baseEvent;
-        unset($event->RRULE);
-        unset($event->EXDATE);
-        unset($event->RDATE);
-        unset($event->EXRULE);
-
-        $event->DTSTART->setDateTime($this->getDTStart(), $event->DTSTART->getDateType());
-        if (isset($event->DTEND)) {
-            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTSTART->getDateType());
-        }
-        if ($this->counter > 0) {
-            $event->{'RECURRENCE-ID'} = (string)$event->DTSTART;
-        }
-
-        return $event;
-
-    }
-
-    /**
-     * Returns the current item number
-     *
-     * @return int
-     */
-    public function key() {
-
-        return $this->counter;
-
-    }
-
-    /**
-     * Whether or not there is a 'next item'
-     *
-     * @return bool
-     */
-    public function valid() {
-
-        if (!is_null($this->count)) {
-            return $this->counter < $this->count;
-        }
-        if (!is_null($this->until)) {
-            return $this->currentDate <= $this->until;
-        }
-        return true;
-
-    }
-
-    /**
-     * Resets the iterator
-     *
-     * @return void
-     */
-    public function rewind() {
-
-        $this->currentDate = clone $this->startDate;
-        $this->counter = 0;
-
-    }
-
-    /**
-     * This method allows you to quickly go to the next occurrence after the
-     * specified date.
-     *
-     * Note that this checks the current 'endDate', not the 'stardDate'. This
-     * means that if you forward to January 1st, the iterator will stop at the
-     * first event that ends *after* January 1st.
-     *
-     * @param DateTime $dt
-     * @return void
-     */
-    public function fastForward(DateTime $dt) {
-
-        while($this->valid() && $this->getDTEnd() < $dt) {
-            $this->next();
-        }
-
-    }
-
-    /**
-     * Returns true if this recurring event never ends.
-     *
-     * @return bool
-     */
-    public function isInfinite() {
-
-        return !$this->count && !$this->until;
-
-    }
-
-    /**
-     * Goes on to the next iteration
-     *
-     * @return void
-     */
-    public function next() {
-
-        /*
-        if (!is_null($this->count) && $this->counter >= $this->count) {
-            $this->currentDate = null;
-        }*/
-
-
-        $previousStamp = $this->currentDate->getTimeStamp();
-
-        while(true) {
-
-            $this->currentOverriddenEvent = null;
-
-            // If we have a next date 'stored', we use that
-            if ($this->nextDate) {
-                $this->currentDate = $this->nextDate;
-                $currentStamp = $this->currentDate->getTimeStamp();
-                $this->nextDate = null;
-            } else {
-
-                // Otherwise, we calculate it
-                switch($this->frequency) {
-
-                    case 'daily' :
-                        $this->nextDaily();
-                        break;
-
-                    case 'weekly' :
-                        $this->nextWeekly();
-                        break;
-
-                    case 'monthly' :
-                        $this->nextMonthly();
-                        break;
-
-                    case 'yearly' :
-                        $this->nextYearly();
-                        break;
-
-                }
-                $currentStamp = $this->currentDate->getTimeStamp();
-
-                // Checking exception dates
-                foreach($this->exceptionDates as $exceptionDate) {
-                    if ($this->currentDate == $exceptionDate) {
-                        $this->counter++;
-                        continue 2;
-                    }
-                }
-                foreach($this->overriddenDates as $overriddenDate) {
-                    if ($this->currentDate == $overriddenDate) {
-                        continue 2;
-                    }
-                }
-
-            }
-
-            // Checking overridden events
-            foreach($this->overriddenEvents as $index=>$event) {
-                if ($index > $previousStamp && $index <= $currentStamp) {
-
-                    // We're moving the 'next date' aside, for later use.
-                    $this->nextDate = clone $this->currentDate;
-
-                    $this->currentDate = $event->DTSTART->getDateTime();
-                    $this->currentOverriddenEvent = $event;
-
-                    break;
-                }
-            }
-
-            break;
-
-        }
-
-        /*
-        if (!is_null($this->until)) {
-            if($this->currentDate > $this->until) {
-                $this->currentDate = null;
-            }
-        }*/
-
-        $this->counter++;
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for daily frequency.
-     *
-     * @return void
-     */
-    protected function nextDaily() {
-
-        if (!$this->byDay) {
-            $this->currentDate->modify('+' . $this->interval . ' days');
-            return;
-        }
-
-        $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)];
-
-        }
-
-        do {
-
-            $this->currentDate->modify('+' . $this->interval . ' days');
-
-            // Current day of the week
-            $currentDay = $this->currentDate->format('w');
-
-        } while (!in_array($currentDay, $recurrenceDays));
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for weekly frequency.
-     *
-     * @return void
-     */
-    protected function nextWeekly() {
-
-        if (!$this->byDay) {
-            $this->currentDate->modify('+' . $this->interval . ' weeks');
-            return;
-        }
-
-        $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)];
-
-        }
-
-        // Current day of the week
-        $currentDay = $this->currentDate->format('w');
-
-        // First day of the week:
-        $firstDay = $this->dayMap[$this->weekStart];
-
-        $time = array(
-            $this->currentDate->format('H'),
-            $this->currentDate->format('i'),
-            $this->currentDate->format('s')
-        );
-
-        // Increasing the 'current day' until we find our next
-        // occurrence.
-        while(true) {
-
-            $currentDay++;
-
-            if ($currentDay>6) {
-                $currentDay = 0;
-            }
-
-            // We need to roll over to the next week
-            if ($currentDay === $firstDay) {
-                $this->currentDate->modify('+' . $this->interval . ' 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]]);
-                    $this->currentDate->setTime($time[0],$time[1],$time[2]);
-                }
-            }
-
-            // We have a match
-            if (in_array($currentDay ,$recurrenceDays)) {
-                $this->currentDate->modify($this->dayNames[$currentDay]);
-                $this->currentDate->setTime($time[0],$time[1],$time[2]);
-                break;
-            }
-
-        }
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for monthly frequency.
-     *
-     * @return void
-     */
-    protected function nextMonthly() {
-
-        $currentDayOfMonth = $this->currentDate->format('j');
-        if (!$this->byMonthDay && !$this->byDay) {
-
-            // If the current day is higher than the 28th, rollover can
-            // occur to the next month. We Must skip these invalid
-            // entries.
-            if ($currentDayOfMonth < 29) {
-                $this->currentDate->modify('+' . $this->interval . ' months');
-            } else {
-                $increase = 0;
-                do {
-                    $increase++;
-                    $tempDate = clone $this->currentDate;
-                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
-                } while ($tempDate->format('j') != $currentDayOfMonth);
-                $this->currentDate = $tempDate;
-            }
-            return;
-        }
-
-        while(true) {
-
-            $occurrences = $this->getMonthlyOccurrences();
-
-            foreach($occurrences as $occurrence) {
-
-                // The first occurrence thats higher than the current
-                // day of the month wins.
-                if ($occurrence > $currentDayOfMonth) {
-                    break 2;
-                }
-
-            }
-
-            // If we made it all the way here, it means there were no
-            // valid occurrences, and we need to advance to the next
-            // month.
-            $this->currentDate->modify('first day of this month');
-            $this->currentDate->modify('+ ' . $this->interval . ' months');
-
-            // This goes to 0 because we need to start counting at hte
-            // beginning.
-            $currentDayOfMonth = 0;
-
-        }
-
-        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
-
-    }
-
-    /**
-     * Does the processing for advancing the iterator for yearly frequency.
-     *
-     * @return void
-     */
-    protected function nextYearly() {
-
-        if (!$this->byMonth) {
-            $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 {
-
-            // no byDay or byMonthDay, so we can just loop through the
-            // months.
-            do {
-
-                $currentMonth++;
-                if ($currentMonth>12) {
-                    $currentYear+=$this->interval;
-                    $currentMonth = 1;
-                }
-            } while (!in_array($currentMonth, $this->byMonth));
-            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
-            return;
-
-        }
-
-    }
-
-    /**
-     * Returns all the occurrences for a monthly frequency with a 'byDay' or
-     * 'byMonthDay' expansion for the current month.
-     *
-     * The returned list is an array of integers with the day of month (1-31).
-     *
-     * @return array
-     */
-    protected function getMonthlyOccurrences() {
-
-        $startDate = clone $this->currentDate;
-
-        $byDayResults = array();
-
-        // Our strategy is to simply go through the byDays, advance the date to
-        // that point and add it to the results.
-        if ($this->byDay) foreach($this->byDay as $day) {
-
-            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
-
-            // Dayname will be something like 'wednesday'. Now we need to find
-            // all wednesdays in this month.
-            $dayHits = array();
-
-            $checkDate = clone $startDate;
-            $checkDate->modify('first day of this month');
-            $checkDate->modify($dayName);
-
-            do {
-                $dayHits[] = $checkDate->format('j');
-                $checkDate->modify('next ' . $dayName);
-            } while ($checkDate->format('n') === $startDate->format('n'));
-
-            // So now we have 'all wednesdays' for month. It is however
-            // possible that the user only really wanted the 1st, 2nd or last
-            // wednesday.
-            if (strlen($day)>2) {
-                $offset = (int)substr($day,0,-2);
-
-                if ($offset>0) {
-                    // It is possible that the day does not exist, such as a
-                    // 5th or 6th wednesday of the month.
-                    if (isset($dayHits[$offset-1])) {
-                        $byDayResults[] = $dayHits[$offset-1];
-                    }
-                } else {
-
-                    // if it was negative we count from the end of the array
-                    $byDayResults[] = $dayHits[count($dayHits) + $offset];
-                }
-            } else {
-                // There was no counter (first, second, last wednesdays), so we
-                // just need to add the all to the list).
-                $byDayResults = array_merge($byDayResults, $dayHits);
-
-            }
-
-        }
-
-        $byMonthDayResults = array();
-        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
-
-            // Removing values that are out of range for this month
-            if ($monthDay > $startDate->format('t') ||
-                $monthDay < 0-$startDate->format('t')) {
-                    continue;
-            }
-            if ($monthDay>0) {
-                $byMonthDayResults[] = $monthDay;
-            } else {
-                // Negative values
-                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
-            }
-        }
-
-        // If there was just byDay or just byMonthDay, they just specify our
-        // (almost) final list. If both were provided, then byDay limits the
-        // list.
-        if ($this->byMonthDay && $this->byDay) {
-            $result = array_intersect($byMonthDayResults, $byDayResults);
-        } elseif ($this->byMonthDay) {
-            $result = $byMonthDayResults;
-        } else {
-            $result = $byDayResults;
-        }
-        $result = array_unique($result);
-        sort($result, SORT_NUMERIC);
-
-        // The last thing that needs checking is the BYSETPOS. If it's set, it
-        // means only certain items in the set survive the filter.
-        if (!$this->bySetPos) {
-            return $result;
-        }
-
-        $filteredResult = array();
-        foreach($this->bySetPos as $setPos) {
-
-            if ($setPos<0) {
-                $setPos = count($result)-($setPos+1);
-            }
-            if (isset($result[$setPos-1])) {
-                $filteredResult[] = $result[$setPos-1];
-            }
-        }
-
-        sort($filteredResult, SORT_NUMERIC);
-        return $filteredResult;
-
-    }
-
-
-}
-
diff --git a/dav/SabreDAV/lib/Sabre/VObject/Version.php b/dav/SabreDAV/lib/Sabre/VObject/Version.php
deleted file mode 100644 (file)
index 2617c7b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-/**
- * This class contains the version number for the VObject package
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_Version {
-
-    /**
-     * Full version number
-     */
-    const VERSION = '1.3.3';
-
-    /**
-     * Stability : alpha, beta, stable
-     */
-    const STABILITY = 'stable';
-
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php b/dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php
deleted file mode 100644 (file)
index 5e1cc5d..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-
-/**
- * Time zone name translation
- *
- * This file translates well-known time zone names into "Olson database" time zone names.
- *
- * @package Sabre
- * @subpackage VObject 
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Frank Edelhaeuser (fedel@users.sourceforge.net) 
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_VObject_WindowsTimezoneMap {
-
-    protected static $map = array(
-
-        // from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
-        // snapshot taken on 2012/01/16
-
-        // windows
-        'AUS Central Standard Time'=>'Australia/Darwin',
-        'AUS Eastern Standard Time'=>'Australia/Sydney',
-        'Afghanistan Standard Time'=>'Asia/Kabul',
-        'Alaskan Standard Time'=>'America/Anchorage',
-        'Arab Standard Time'=>'Asia/Riyadh',
-        'Arabian Standard Time'=>'Asia/Dubai',
-        'Arabic Standard Time'=>'Asia/Baghdad',
-        'Argentina Standard Time'=>'America/Buenos_Aires',
-        'Armenian Standard Time'=>'Asia/Yerevan',
-        'Atlantic Standard Time'=>'America/Halifax',
-        'Azerbaijan Standard Time'=>'Asia/Baku',
-        'Azores Standard Time'=>'Atlantic/Azores',
-        'Bangladesh Standard Time'=>'Asia/Dhaka',
-        'Canada Central Standard Time'=>'America/Regina',
-        'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
-        'Caucasus Standard Time'=>'Asia/Yerevan',
-        'Cen. Australia Standard Time'=>'Australia/Adelaide',
-        'Central America Standard Time'=>'America/Guatemala',
-        'Central Asia Standard Time'=>'Asia/Almaty',
-        'Central Brazilian Standard Time'=>'America/Cuiaba',
-        'Central Europe Standard Time'=>'Europe/Budapest',
-        'Central European Standard Time'=>'Europe/Warsaw',
-        'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
-        'Central Standard Time'=>'America/Chicago',
-        'Central Standard Time (Mexico)'=>'America/Mexico_City',
-        'China Standard Time'=>'Asia/Shanghai',
-        'Dateline Standard Time'=>'Etc/GMT+12',
-        'E. Africa Standard Time'=>'Africa/Nairobi',
-        'E. Australia Standard Time'=>'Australia/Brisbane',
-        'E. Europe Standard Time'=>'Europe/Minsk',
-        'E. South America Standard Time'=>'America/Sao_Paulo',
-        'Eastern Standard Time'=>'America/New_York',
-        'Egypt Standard Time'=>'Africa/Cairo',
-        'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
-        'FLE Standard Time'=>'Europe/Kiev',
-        'Fiji Standard Time'=>'Pacific/Fiji',
-        'GMT Standard Time'=>'Europe/London',
-        'GTB Standard Time'=>'Europe/Istanbul',
-        'Georgian Standard Time'=>'Asia/Tbilisi',
-        'Greenland Standard Time'=>'America/Godthab',
-        'Greenwich Standard Time'=>'Atlantic/Reykjavik',
-        'Hawaiian Standard Time'=>'Pacific/Honolulu',
-        'India Standard Time'=>'Asia/Calcutta',
-        'Iran Standard Time'=>'Asia/Tehran',
-        'Israel Standard Time'=>'Asia/Jerusalem',
-        'Jordan Standard Time'=>'Asia/Amman',
-        'Kamchatka Standard Time'=>'Asia/Kamchatka',
-        'Korea Standard Time'=>'Asia/Seoul',
-        'Magadan Standard Time'=>'Asia/Magadan',
-        'Mauritius Standard Time'=>'Indian/Mauritius',
-        'Mexico Standard Time'=>'America/Mexico_City',
-        'Mexico Standard Time 2'=>'America/Chihuahua',
-        'Mid-Atlantic Standard Time'=>'Etc/GMT+2',
-        'Middle East Standard Time'=>'Asia/Beirut',
-        'Montevideo Standard Time'=>'America/Montevideo',
-        'Morocco Standard Time'=>'Africa/Casablanca',
-        'Mountain Standard Time'=>'America/Denver',
-        'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
-        'Myanmar Standard Time'=>'Asia/Rangoon',
-        'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
-        'Namibia Standard Time'=>'Africa/Windhoek',
-        'Nepal Standard Time'=>'Asia/Katmandu',
-        'New Zealand Standard Time'=>'Pacific/Auckland',
-        'Newfoundland Standard Time'=>'America/St_Johns',
-        'North Asia East Standard Time'=>'Asia/Irkutsk',
-        'North Asia Standard Time'=>'Asia/Krasnoyarsk',
-        'Pacific SA Standard Time'=>'America/Santiago',
-        'Pacific Standard Time'=>'America/Los_Angeles',
-        'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
-        'Pakistan Standard Time'=>'Asia/Karachi',
-        'Paraguay Standard Time'=>'America/Asuncion',
-        'Romance Standard Time'=>'Europe/Paris',
-        'Russian Standard Time'=>'Europe/Moscow',
-        'SA Eastern Standard Time'=>'America/Cayenne',
-        'SA Pacific Standard Time'=>'America/Bogota',
-        'SA Western Standard Time'=>'America/La_Paz',
-        'SE Asia Standard Time'=>'Asia/Bangkok',
-        'Samoa Standard Time'=>'Pacific/Apia',
-        'Singapore Standard Time'=>'Asia/Singapore',
-        'South Africa Standard Time'=>'Africa/Johannesburg',
-        'Sri Lanka Standard Time'=>'Asia/Colombo',
-        'Syria Standard Time'=>'Asia/Damascus',
-        'Taipei Standard Time'=>'Asia/Taipei',
-        'Tasmania Standard Time'=>'Australia/Hobart',
-        'Tokyo Standard Time'=>'Asia/Tokyo',
-        'Tonga Standard Time'=>'Pacific/Tongatapu',
-        'US Eastern Standard Time'=>'America/Indianapolis',
-        'US Mountain Standard Time'=>'America/Phoenix',
-        'UTC'=>'Etc/GMT',
-        'UTC+12'=>'Etc/GMT-12',
-        'UTC-02'=>'Etc/GMT+2',
-        'UTC-11'=>'Etc/GMT+11',
-        'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
-        'Venezuela Standard Time'=>'America/Caracas',
-        'Vladivostok Standard Time'=>'Asia/Vladivostok',
-        'W. Australia Standard Time'=>'Australia/Perth',
-        'W. Central Africa Standard Time'=>'Africa/Lagos',
-        'W. Europe Standard Time'=>'Europe/Berlin',
-        'West Asia Standard Time'=>'Asia/Tashkent',
-        'West Pacific Standard Time'=>'Pacific/Port_Moresby',
-        'Yakutsk Standard Time'=>'Asia/Yakutsk',
-    );
-
-    static public function lookup($tzid) {
-        return isset(self::$map[$tzid]) ? self::$map[$tzid] : null;
-    }
-}
diff --git a/dav/SabreDAV/lib/Sabre/VObject/includes.php b/dav/SabreDAV/lib/Sabre/VObject/includes.php
deleted file mode 100644 (file)
index e7f57a0..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-/**
- * Sabre_VObject includes file
- *
- * Including this file will automatically include all files from the VObject
- * package.
- *
- * This often allows faster loadtimes, as autoload-speed is often quite slow.
- *
- * @package Sabre
- * @subpackage VObject
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-
-// Begin includes
-include __DIR__ . '/DateTimeParser.php';
-include __DIR__ . '/ElementList.php';
-include __DIR__ . '/FreeBusyGenerator.php';
-include __DIR__ . '/Node.php';
-include __DIR__ . '/Parameter.php';
-include __DIR__ . '/ParseException.php';
-include __DIR__ . '/Reader.php';
-include __DIR__ . '/RecurrenceIterator.php';
-include __DIR__ . '/Version.php';
-include __DIR__ . '/WindowsTimezoneMap.php';
-include __DIR__ . '/Element.php';
-include __DIR__ . '/Property.php';
-include __DIR__ . '/Component.php';
-include __DIR__ . '/Property/DateTime.php';
-include __DIR__ . '/Property/MultiDateTime.php';
-include __DIR__ . '/Component/VAlarm.php';
-include __DIR__ . '/Component/VCalendar.php';
-include __DIR__ . '/Component/VEvent.php';
-include __DIR__ . '/Component/VJournal.php';
-include __DIR__ . '/Component/VTodo.php';
-// End includes
index 511dd9fd2a61f1d027886ae2df4102f1a5f72c53..d9a5a7a0f00cb99342060c59491e47668df96b6c 100644 (file)
@@ -1,14 +1,16 @@
 <?php
 
-class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract {
+class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_NotificationSupport {
 
     private $calendarData;
     private $calendars;
+    private $notifications;
 
-    function __construct(array $calendars, array $calendarData) {
+    function __construct(array $calendars, array $calendarData, array $notifications = array()) {
 
         $this->calendars = $calendars;
         $this->calendarData = $calendarData;
+        $this->notifications = $notifications;
 
     }
 
@@ -58,7 +60,15 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract {
      */
     function createCalendar($principalUri,$calendarUri,array $properties) {
 
-        throw new Exception('Not implemented');
+        $id = Sabre_DAV_UUIDUtil::getUUID();
+        $this->calendars[] = array_merge(array(
+            'id' => $id,
+            'principaluri' => $principalUri,
+            'uri' => $calendarUri,
+            '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')),
+        ), $properties);
+
+        return $id;
 
     }
 
@@ -112,7 +122,11 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract {
      */
     public function deleteCalendar($calendarId) {
 
-        throw new Exception('Not implemented');
+        foreach($this->calendars as $k=>$calendar) {
+            if ($calendar['id'] === $calendarId) {
+                unset($this->calendars[$k]);
+            }
+        }
 
     }
 
@@ -227,4 +241,37 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract {
 
     }
 
+    /**
+     * Returns a list of notifications for a given principal url.
+     *
+     * The returned array should only consist of implementations of
+     * Sabre_CalDAV_Notifications_INotificationType.
+     *
+     * @param string $principalUri
+     * @return array
+     */
+    public function getNotificationsForPrincipal($principalUri) {
+
+        if (isset($this->notifications[$principalUri])) {
+            return $this->notifications[$principalUri];
+        }
+        return array();
+
+    }
+
+    /**
+     * This deletes a specific notifcation.
+     *
+     * This may be called by a client once it deems a notification handled.
+     *
+     * @param string $principalUri
+     * @param Sabre_CalDAV_Notifications_INotificationType $notification
+     * @return void
+     */
+    public function deleteNotification($principalUri, Sabre_CalDAV_Notifications_INotificationType $notification) {
+
+        throw new Sabre_DAV_Exception_NotImplemented('This doesn\'t work!');
+
+    }
+
 }
index 3677198c66de766f33ae7ca2193bb959abfaaa84..1ee3b9a6fdfabc46dcb1268f232aaa63d7df9c76 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 class Sabre_CalDAV_CalendarQueryVAlarmTest extends PHPUnit_Framework_TestCase {
 
     /**
@@ -8,16 +10,16 @@ class Sabre_CalDAV_CalendarQueryVAlarmTest extends PHPUnit_Framework_TestCase {
      */
     function testValarm() {
 
-        $vevent = Sabre_VObject_Component::create('VEVENT');
+        $vevent = VObject\Component::create('VEVENT');
         $vevent->RRULE = 'FREQ=MONTHLY';
         $vevent->DTSTART = '20120101T120000Z';
         $vevent->UID = 'bla';
 
-        $valarm = Sabre_VObject_Component::create('VALARM');
+        $valarm = VObject\Component::create('VALARM');
         $valarm->TRIGGER = '-P15D';
         $vevent->add($valarm);
 
-        $vcalendar = Sabre_VObject_Component::create('VCALENDAR');
+        $vcalendar = VObject\Component::create('VCALENDAR');
         $vcalendar->add($vevent);
 
         $filter = array(
@@ -52,16 +54,16 @@ class Sabre_CalDAV_CalendarQueryVAlarmTest extends PHPUnit_Framework_TestCase {
 
 
         // A limited recurrence rule, should return false
-        $vevent = Sabre_VObject_Component::create('VEVENT');
+        $vevent = VObject\Component::create('VEVENT');
         $vevent->RRULE = 'FREQ=MONTHLY;COUNT=1';
         $vevent->DTSTART = '20120101T120000Z';
         $vevent->UID = 'bla';
 
-        $valarm = Sabre_VObject_Component::create('VALARM');
+        $valarm = VObject\Component::create('VALARM');
         $valarm->TRIGGER = '-P15D';
         $vevent->add($valarm);
 
-        $vcalendar = Sabre_VObject_Component::create('VCALENDAR');
+        $vcalendar = VObject\Component::create('VCALENDAR');
         $vcalendar->add($vevent);
 
         $this->assertFalse($validator->validate($vcalendar, $filter));
@@ -69,15 +71,15 @@ class Sabre_CalDAV_CalendarQueryVAlarmTest extends PHPUnit_Framework_TestCase {
 
     function testAlarmWayBefore() {
 
-        $vevent = Sabre_VObject_Component::create('VEVENT');
+        $vevent = VObject\Component::create('VEVENT');
         $vevent->DTSTART = '20120101T120000Z';
         $vevent->UID = 'bla';
 
-        $valarm = Sabre_VObject_Component::create('VALARM');
+        $valarm = VObject\Component::create('VALARM');
         $valarm->TRIGGER = '-P2W1D';
         $vevent->add($valarm);
 
-        $vcalendar = Sabre_VObject_Component::create('VCALENDAR');
+        $vcalendar = VObject\Component::create('VCALENDAR');
         $vcalendar->add($vevent);
 
         $filter = array(
index b75c398ab232880e9acd1f92812aae31fe76e41a..dede4764bae4d9486d91c103ac1026b15821573d 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 class Sabre_CalDAV_CalendarQueryValidatorTest extends PHPUnit_Framework_TestCase {
 
     /**
@@ -19,7 +21,7 @@ class Sabre_CalDAV_CalendarQueryValidatorTest extends PHPUnit_Framework_TestCase
             'time-range' => null,
         );
 
-        $vObject = Sabre_VObject_Reader::read($icalObject);
+        $vObject = VObject\Reader::read($icalObject);
 
         switch($outcome) {
             case 0 :
@@ -31,7 +33,7 @@ class Sabre_CalDAV_CalendarQueryValidatorTest extends PHPUnit_Framework_TestCase
             case -1 :
                 try {
                     $validator->validate($vObject, $filters);
-                } catch (Sabre_DAV_Exception $e) {
+                } catch (Exception $e) {
                     // Success
                 }
                 break;
@@ -343,6 +345,14 @@ DURATION:PT1H
 RRULE:FREQ=YEARLY
 END:VEVENT
 END:VCALENDAR
+yow;
+        $blob33 = <<<yow
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20120628
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
 yow;
 
         $filter1 = array(
@@ -604,8 +614,16 @@ yow;
             'time-range' => null,
         );
 
-        // Time-range with RRULE
-
+        $filter38 = array(
+            'name' => 'VEVENT',
+            'comp-filters' => array(),
+            'prop-filters' => array(),
+            'is-not-defined' => false,
+            'time-range' => array(
+                'start' => new DateTime('2012-07-01 00:00:00', new DateTimeZone('UTC')),
+                'end' => new DateTime('2012-08-01 00:00:00', new DateTimeZone('UTC')),
+            )
+        );
 
         return array(
             // Component check
@@ -741,6 +759,9 @@ yow;
             array($blob31, $filter20, 1),
             array($blob32, $filter20, 0),
 
+            // Bug reported on mailing list, related to all-day events.
+            array($blob33, $filter38, 1),
+
         );
 
     }
index 984212aa2322b943906301c6eb43566ebaf11ea3..444dc496b6b685205ed1d4489e8e4661357c6cc7 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittests is created to find out why recurring events have wrong DTSTART value
  *
@@ -83,13 +85,13 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         // check if DTSTARTs and DTENDs are correct
         foreach ($vObject->VEVENT as $vevent) {
-            /** @var $vevent Sabre_VObject_Component_VEvent */
+            /** @var $vevent Sabre\VObject\Component_VEvent */
             foreach ($vevent->children as $child) {
-                /** @var $child Sabre_VObject_Property */
+                /** @var $child Sabre\VObject\Property */
 
                 if ($child->name == 'DTSTART') {
                     // DTSTART has to be one of three valid values
index a2c2fc275ebefdb98affffa2c16c6652b11dce89..a183082b5f8b9941bb347446687960ab61b38c09 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittests is created to find out why recurring events have wrong DTSTART value
  *
@@ -75,15 +77,15 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         $this->assertEquals(2, count($vObject->VEVENT));
 
         // check if DTSTARTs and DTENDs are correct
         foreach ($vObject->VEVENT as $vevent) {
-            /** @var $vevent Sabre_VObject_Component_VEvent */
+            /** @var $vevent Sabre\VObject\Component\VEvent */
             foreach ($vevent->children as $child) {
-                /** @var $child Sabre_VObject_Property */
+                /** @var $child Sabre\VObject\Property */
 
                 if ($child->name == 'DTSTART') {
                     // DTSTART has to be one of two valid values
index 55d06b231cebaddddcb7814fe413795fa9278dfa..087c384fa28465345af0a48d0b914b7138aba0a6 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittests is created to find out why certain events show up twice.
  *
@@ -85,7 +87,7 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         // We only expect 3 events
         $this->assertEquals(3, count($vObject->VEVENT),'We got 6 events instead of 3. Output: ' . $body);
index b8a97ca1dea31df64a9ee6b40374f2d9ca8b3134..d9a6d586bca11fafb2c389a08a9c292b78195ebd 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittest is created to check if queries for time-range include the start timestamp or not
  *
@@ -82,7 +84,7 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         // We expect 1 event
         $this->assertEquals(1, count($vObject->VEVENT), 'We got 0 events instead of 1. Output: ' . $body);
index 137b7d3ca0bb3a570979885a03479f2f94833966..c569da8f71d19e1eb2f17f59b1faefd979452624 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 require_once 'Sabre/CalDAV/TestUtil.php';
 require_once 'Sabre/DAV/Auth/MockBackend.php';
 require_once 'Sabre/HTTP/ResponseMock.php';
@@ -49,12 +51,62 @@ class Sabre_CalDAV_ICSExportPluginTest extends PHPUnit_Framework_TestCase {
             'Content-Type' => 'text/calendar',
         ), $s->httpResponse->headers);
 
-        $obj = Sabre_VObject_Reader::read($s->httpResponse->body);
+        $obj = VObject\Reader::read($s->httpResponse->body);
+
+        $this->assertEquals(5,count($obj->children()));
+        $this->assertEquals(1,count($obj->VERSION));
+        $this->assertEquals(1,count($obj->CALSCALE));
+        $this->assertEquals(1,count($obj->PRODID));
+        $this->assertTrue(strpos((string)$obj->PRODID, Sabre_DAV_Version::VERSION)!==false);
+        $this->assertEquals(1,count($obj->VTIMEZONE));
+        $this->assertEquals(1,count($obj->VEVENT));
+
+    }
+    function testBeforeMethodNoVersion() {
+
+        if (!SABRE_HASSQLITE) $this->markTestSkipped('SQLite driver is not available');
+        $cbackend = Sabre_CalDAV_TestUtil::getBackend();
+        $pbackend = new Sabre_DAVACL_MockPrincipalBackend();
+
+        $props = array(
+            'uri'=>'UUID-123467',
+            'principaluri' => 'admin',
+            'id' => 1,
+        );
+        $tree = array(
+            new Sabre_CalDAV_Calendar($pbackend,$cbackend,$props),
+        );
+
+        $p = new Sabre_CalDAV_ICSExportPlugin();
+
+        $s = new Sabre_DAV_Server($tree);
+
+        $s->addPlugin($p);
+        $s->addPlugin(new Sabre_CalDAV_Plugin());
+
+        $h = new Sabre_HTTP_Request(array(
+            'QUERY_STRING' => 'export',
+        ));
+
+        $s->httpRequest = $h;
+        $s->httpResponse = new Sabre_HTTP_ResponseMock();
+
+        Sabre_DAV_Server::$exposeVersion = false;
+        $this->assertFalse($p->beforeMethod('GET','UUID-123467?export'));
+        Sabre_DAV_Server::$exposeVersion = true; 
+
+        $this->assertEquals('HTTP/1.1 200 OK',$s->httpResponse->status);
+        $this->assertEquals(array(
+            'Content-Type' => 'text/calendar',
+        ), $s->httpResponse->headers);
+
+        $obj = VObject\Reader::read($s->httpResponse->body);
 
         $this->assertEquals(5,count($obj->children()));
         $this->assertEquals(1,count($obj->VERSION));
         $this->assertEquals(1,count($obj->CALSCALE));
         $this->assertEquals(1,count($obj->PRODID));
+        $this->assertFalse(strpos((string)$obj->PRODID, Sabre_DAV_Version::VERSION)!==false);
         $this->assertEquals(1,count($obj->VTIMEZONE));
         $this->assertEquals(1,count($obj->VEVENT));
 
@@ -161,7 +213,7 @@ class Sabre_CalDAV_ICSExportPluginTest extends PHPUnit_Framework_TestCase {
             'Content-Type' => 'text/calendar',
         ), $s->httpResponse->headers);
 
-        $obj = Sabre_VObject_Reader::read($s->httpResponse->body);
+        $obj = VObject\Reader::read($s->httpResponse->body);
 
         $this->assertEquals(5,count($obj->children()));
         $this->assertEquals(1,count($obj->VERSION));
index 3a61663ec6c16852e530691392b646dbd248111c..11d2f21cead73005b8b0e4606d849089e4458de7 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 class Sabre_CalDAV_Issue166Test extends PHPUnit_Framework_TestCase {
 
     function testFlaw() {
@@ -51,7 +53,7 @@ HI;
             'is-not-defined' => false,
             'time-range' => null,
         );
-        $input = Sabre_VObject_Reader::read($input);
+        $input = VObject\Reader::read($input);
         $this->assertTrue($validator->validate($input,$filters));
 
     }
index 024b2557919a1ea99618a9ecb66c70d0fc2960bc..90e13fd842863e9319dfc28f2c477a99c62483b5 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 class Sabre_CalDAV_Issue172Test extends PHPUnit_Framework_TestCase {
 
     // DateTimeZone() native name: America/Los_Angeles (GMT-8 in January)
@@ -30,7 +32,7 @@ HI;
             ),
             'prop-filters' => array(),
         );
-        $input = Sabre_VObject_Reader::read($input);
+        $input = VObject\Reader::read($input);
         $this->assertTrue($validator->validate($input,$filters));
     }
 
@@ -77,7 +79,7 @@ HI;
             ),
             'prop-filters' => array(),
         );
-        $input = Sabre_VObject_Reader::read($input);
+        $input = VObject\Reader::read($input);
         $this->assertTrue($validator->validate($input,$filters));
     }
 
@@ -125,7 +127,7 @@ HI;
             ),
             'prop-filters' => array(),
         );
-        $input = Sabre_VObject_Reader::read($input);
+        $input = VObject\Reader::read($input);
         $this->assertTrue($validator->validate($input,$filters));
     }
 }
index e9eaecab8c603cd04727d4c1bb03cdb8dc55eb44..4cb8eec9ac46ae6b033d677a148d233eec7f20c1 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittest is created to find out why an overwritten DAILY event has wrong DTSTART, DTEND, SUMMARY and RECURRENCEID
  *
@@ -88,7 +90,7 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         $this->assertEquals(2, count($vObject->VEVENT));
 
@@ -113,10 +115,10 @@ END:VCALENDAR
             $matching = false;
 
             foreach ($vObject->VEVENT as $vevent) {
-                /** @var $vevent Sabre_VObject_Component_VEvent */
+                /** @var $vevent Sabre\VObject\Component\VEvent */
 
                 foreach ($vevent->children as $child) {
-                    /** @var $child Sabre_VObject_Property */
+                    /** @var $child Sabre\VObject\Property */
 
                     if (isset($expectedEvent[$child->name])) {
                         if ($expectedEvent[$child->name] != $child->value) {
index 20098252e0b794a9053abdd42ed894cc7eabb818..6e9e497ca84a955267da76c81ec316419b1225ae 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Sabre\VObject;
+
 /**
  * This unittest is created to check if a VALARM TRIGGER of PT0S is supported
  *
@@ -86,7 +88,7 @@ END:VCALENDAR
         );
         $body = str_replace('&#13;','',$body);
 
-        $vObject = Sabre_VObject_Reader::read($body);
+        $vObject = VObject\Reader::read($body);
 
         $this->assertEquals(1, count($vObject->VEVENT));
 
diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php b/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php
new file mode 100644 (file)
index 0000000..f4f8753
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * This unittest is created to check for an endless loop in Sabre_CalDAV_CalendarQueryValidator
+ *
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Issue220Test extends Sabre_DAVServerTest {
+
+    protected $setupCalDAV = true;
+
+    protected $caldavCalendars = array(
+        array(
+            'id' => 1,
+            'name' => 'Calendar',
+            'principaluri' => 'principals/user1',
+            'uri' => 'calendar1',
+        )
+    );
+
+    protected $caldavCalendarObjects = array(
+        1 => array(
+            'event.ics' => array(
+                'calendardata' => 'BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20120601T180000
+SUMMARY:Brot backen
+RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO
+TRANSP:OPAQUE
+DURATION:PT20M
+LAST-MODIFIED:20120601T064634Z
+CREATED:20120601T064634Z
+DTSTAMP:20120601T064634Z
+UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd
+BEGIN:VALARM
+TRIGGER;VALUE=DURATION:-PT5M
+ACTION:DISPLAY
+DESCRIPTION:Default Event Notification
+X-WR-ALARMUID:cd952c1b-b3d6-41fb-b0a6-ec3a1a5bdd58
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20120606T180000
+SUMMARY:Brot backen
+TRANSP:OPAQUE
+STATUS:CANCELLED
+DTEND;TZID=Europe/Berlin:20120606T182000
+LAST-MODIFIED:20120605T094310Z
+SEQUENCE:1
+RECURRENCE-ID:20120606T160000Z
+UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd
+END:VEVENT
+END:VCALENDAR
+',
+            ),
+        ),
+    );
+
+    function testIssue220() {
+
+        $request = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'REPORT',
+            'HTTP_CONTENT_TYPE' => 'application/xml',
+            'REQUEST_URI' => '/calendars/user1/calendar1',
+            'HTTP_DEPTH' => '1',
+        ));
+
+        $request->setBody('<?xml version="1.0" encoding="utf-8" ?>
+<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+    <D:prop>
+        <C:calendar-data/>
+        <D:getetag/>
+    </D:prop>
+    <C:filter>
+        <C:comp-filter name="VCALENDAR">
+            <C:comp-filter name="VEVENT">
+                <C:comp-filter name="VALARM">
+                    <C:time-range start="20120607T161646Z" end="20120612T161646Z"/>
+                </C:comp-filter>
+            </C:comp-filter>
+        </C:comp-filter>
+    </C:filter>
+</C:calendar-query>');
+
+        $response = $this->request($request);
+
+        $this->assertFalse(strpos($response->body, '<s:exception>PHPUnit_Framework_Error_Warning</s:exception>'), 'Error Warning occurred: ' . $response->body);
+        $this->assertFalse(strpos($response->body, 'Invalid argument supplied for foreach()'), 'Invalid argument supplied for foreach(): ' . $response->body);
+
+        $this->assertEquals('HTTP/1.1 207 Multi-Status', $response->status);
+    }
+}
diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Issue228Test.php b/dav/SabreDAV/tests/Sabre/CalDAV/Issue228Test.php
new file mode 100644 (file)
index 0000000..f251ab1
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * This unittest is created to check if the time-range filter is working correctly with all-day-events
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Issue228Test extends Sabre_DAVServerTest {
+
+    protected $setupCalDAV = true;
+
+    protected $caldavCalendars = array(
+        array(
+            'id' => 1,
+            'name' => 'Calendar',
+            'principaluri' => 'principals/user1',
+            'uri' => 'calendar1',
+        )
+    );
+
+    protected $caldavCalendarObjects = array(
+        1 => array(
+            'event.ics' => array(
+                'calendardata' => 'BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:20120730T113415CEST-6804EGphkd@xxxxxx.de
+DTSTAMP:20120730T093415Z
+DTSTART;VALUE=DATE:20120729
+DTEND;VALUE=DATE:20120730
+SUMMARY:sunday event
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+',
+            ),
+        ),
+    );
+
+    function testIssue228() {
+
+        $request = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'REPORT',
+            'HTTP_CONTENT_TYPE' => 'application/xml',
+            'REQUEST_URI' => '/calendars/user1/calendar1',
+            'HTTP_DEPTH' => '1',
+        ));
+
+        $request->setBody('<?xml version="1.0" encoding="utf-8" ?>
+<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+  <D:prop>
+    <C:calendar-data>
+  <C:expand start="20120730T095609Z"
+            end="20120813T095609Z"/>
+</C:calendar-data>
+    <D:getetag/>
+  </D:prop>
+  <C:filter>
+    <C:comp-filter name="VCALENDAR">
+      <C:comp-filter name="VEVENT">
+        <C:time-range start="20120730T095609Z" end="20120813T095609Z"/>
+      </C:comp-filter>
+    </C:comp-filter>
+  </C:filter>
+</C:calendar-query>');
+
+        $response = $this->request($request);
+
+        // We must check if absolutely nothing was returned from this query.
+        $this->assertFalse(strpos($response->body, 'BEGIN:VCALENDAR'));
+
+    }
+}
diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php
new file mode 100644 (file)
index 0000000..1d396d6
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+class Sabre_CalDAV_Notifications_CollectionTest extends \PHPUnit_Framework_TestCase {
+
+    function testGetChildren() {
+
+        $principalUri = 'principals/user1';
+
+        $systemStatus = new Sabre_CalDAV_Notifications_Notification_SystemStatus(1);
+
+        $caldavBackend = new Sabre_CalDAV_Backend_Mock(array(),array(), array(
+            'principals/user1' => array(
+                $systemStatus
+            )
+        )); 
+
+
+        $col = new Sabre_CalDAV_Notifications_Collection($caldavBackend, $principalUri);
+        $this->assertEquals('notifications', $col->getName());
+
+        $this->assertEquals(array(
+            new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus)
+        ), $col->getChildren()); 
+
+    }
+
+}
diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php
new file mode 100644 (file)
index 0000000..dba636e
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+class Sabre_CalDAV_Notifications_NodeTest extends \PHPUnit_Framework_TestCase {
+
+    function testGetId() {
+
+        $principalUri = 'principals/user1';
+
+        $systemStatus = new Sabre_CalDAV_Notifications_Notification_SystemStatus(1);
+
+        $caldavBackend = new Sabre_CalDAV_Backend_Mock(array(),array(), array(
+            'principals/user1' => array(
+                $systemStatus
+            )
+        )); 
+
+
+        $node = new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus);
+        $this->assertEquals($systemStatus->getId(), $node->getName());
+
+    }
+
+}
diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php
new file mode 100644 (file)
index 0000000..ddaf5ce
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+class Sabre_CalDAV_Notifications_Notification extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider dataProvider
+     */
+    function testSerializers($notification, $expected1, $expected2) {
+
+        $this->assertEquals('foo', $notification->getId());
+
+
+        $dom = new DOMDocument('1.0','UTF-8');
+        $elem = $dom->createElement('cs:root');
+        $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
+        $dom->appendChild($elem);
+        $notification->serialize(new Sabre_DAV_Server(), $elem);
+        $this->assertEquals($expected1, $dom->saveXML());
+
+        $dom = new DOMDocument('1.0','UTF-8');
+        $elem = $dom->createElement('cs:root');
+        $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
+        $dom->appendChild($elem);
+        $notification->serializeBody(new Sabre_DAV_Server(), $elem);
+        $this->assertEquals($expected2, $dom->saveXML());
+
+
+    }
+
+    function dataProvider() {
+
+        return array(
+
+            array(
+                new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo'),
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="high"/></cs:root>' . "\n",
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="high"/></cs:root>' . "\n",
+            ),
+
+            array(
+                new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_MEDIUM,'bar'),
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="medium"/></cs:root>' . "\n",
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="medium"><cs:description>bar</cs:description></cs:systemstatus></cs:root>' . "\n",
+            ),
+
+            array(
+                new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_LOW,null,'http://example.org/'),
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="low"/></cs:root>' . "\n",
+                '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="low"><d:href>http://example.org/</d:href></cs:systemstatus></cs:root>' . "\n",
+            )
+        );
+
+    }
+
+}
index 4d45c8ae0deadddd2bde44001fcc208a7b745ac2..6c618cac056e517133e84eb629d646c63bf1caaf 100644 (file)
@@ -191,7 +191,17 @@ class Sabre_CalDAV_OutboxPostTest extends Sabre_DAVServerTest {
 
         $req->setBody(implode("\r\n",$body));
 
-        $this->assertHTTPStatus(501, $req);
+
+        $response = $this->request($req);
+        $this->assertEquals('HTTP/1.1 200 OK', $response->status);
+        $this->assertEquals(array(
+            'Content-Type' => 'application/xml',
+        ), $response->headers);
+
+        // Lazily checking the body for a few expected values.
+        $this->assertTrue(strpos($response->body, '5.2;')!==false);
+        $this->assertTrue(strpos($response->body,'user2@example.org')!==false);
+
 
     }
 
@@ -218,7 +228,66 @@ class Sabre_CalDAV_OutboxPostTest extends Sabre_DAVServerTest {
         $handler = new Sabre_CalDAV_Schedule_IMip_Mock('server@example.org');
 
         $this->caldavPlugin->setIMIPhandler($handler);
-        $this->assertHTTPStatus(200, $req);
+
+        $response = $this->request($req);
+        $this->assertEquals('HTTP/1.1 200 OK', $response->status);
+        $this->assertEquals(array(
+            'Content-Type' => 'application/xml',
+        ), $response->headers);
+
+        // Lazily checking the body for a few expected values.
+        $this->assertTrue(strpos($response->body, '2.0;')!==false);
+        $this->assertTrue(strpos($response->body,'user2@example.org')!==false);
+
+        $this->assertEquals(array(
+            array(
+                'to' => 'user2@example.org',
+                'subject' => 'Invitation for: An invitation',
+                'body' => implode("\r\n", $body) . "\r\n",
+                'headers' => array(
+                    'Reply-To: user1.sabredav@sabredav.org',
+                    'From: server@example.org',
+                    'Content-Type: text/calendar; method=REQUEST; charset=utf-8',
+                    'X-Sabre-Version: ' . Sabre_DAV_Version::VERSION . '-' . Sabre_DAV_Version::STABILITY,
+                ),
+           )
+        ), $handler->getSentEmails());
+
+    }
+
+    function testSuccessRequestUpperCased() {
+
+        $req = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD'  => 'POST',
+            'REQUEST_URI'     => '/calendars/user1/outbox',
+            'HTTP_ORIGINATOR' => 'MAILTO:user1.sabredav@sabredav.org',
+            'HTTP_RECIPIENT'  => 'MAILTO:user2@example.org',
+        ));
+
+        $body = array(
+            'BEGIN:VCALENDAR',
+            'METHOD:REQUEST',
+            'BEGIN:VEVENT',
+            'SUMMARY:An invitation',
+            'END:VEVENT',
+            'END:VCALENDAR',
+        );
+
+        $req->setBody(implode("\r\n",$body));
+
+        $handler = new Sabre_CalDAV_Schedule_IMip_Mock('server@example.org');
+
+        $this->caldavPlugin->setIMIPhandler($handler);
+
+        $response = $this->request($req);
+        $this->assertEquals('HTTP/1.1 200 OK', $response->status);
+        $this->assertEquals(array(
+            'Content-Type' => 'application/xml',
+        ), $response->headers);
+
+        // Lazily checking the body for a few expected values.
+        $this->assertTrue(strpos($response->body, '2.0;')!==false);
+        $this->assertTrue(strpos($response->body,'user2@example.org')!==false);
 
         $this->assertEquals(array(
             array(
index eb097c9e8cfd70843c1628463dfc652c76bcbfbe..6ff57285d99f205f4b0f415a55e4eba3f3139fbf 100644 (file)
@@ -23,8 +23,34 @@ class Sabre_CalDAV_PluginTest extends PHPUnit_Framework_TestCase {
 
     function setup() {
 
-        if (!SABRE_HASSQLITE) $this->markTestSkipped('No PDO SQLite support');
-        $this->caldavBackend = Sabre_CalDAV_TestUtil::getBackend();
+        $this->caldavBackend = new Sabre_CalDAV_Backend_Mock(array(
+            array(
+                'id' => 1,
+                'uri' => 'UUID-123467',
+                'principaluri' => 'principals/user1',
+                '{DAV:}displayname' => 'user1 calendar',
+                '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description',
+                '{http://apple.com/ns/ical/}calendar-order' => '1',
+                '{http://apple.com/ns/ical/}calendar-color' => '#FF0000',
+                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')),
+            ),
+            array(
+                'id' => 2,
+                'uri' => 'UUID-123468',
+                'principaluri' => 'principals/user1',
+                '{DAV:}displayname' => 'user1 calendar2',
+                '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description',
+                '{http://apple.com/ns/ical/}calendar-order' => '1',
+                '{http://apple.com/ns/ical/}calendar-color' => '#FF0000',
+                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')),
+            )
+        ), array(
+            1 => array(
+                'UUID-2345' => array(
+                    'calendardata' => Sabre_CalDAV_TestUtil::getTestCalendarData(),
+                )
+            )
+        ));
         $principalBackend = new Sabre_DAVACL_MockPrincipalBackend();
         $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-read',array('principals/user1'));
         $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-write',array('principals/user1'));
@@ -398,6 +424,7 @@ END:VCALENDAR';
             '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',
             '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}calendar-proxy-read-for',
             '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}calendar-proxy-write-for',
+            '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notification-URL',
         ));
 
         $this->assertArrayHasKey(0,$props);
@@ -414,6 +441,12 @@ END:VCALENDAR';
         $this->assertTrue($prop instanceof Sabre_DAV_Property_Href);
         $this->assertEquals('calendars/user1/outbox',$prop->getHref());
 
+        $this->assertArrayHasKey('{'.Sabre_CalDAV_Plugin::NS_CALENDARSERVER .'}notification-URL',$props[0][200]);
+        $prop = $props[0][200]['{'.Sabre_CalDAV_Plugin::NS_CALENDARSERVER .'}notification-URL'];
+        $this->assertTrue($prop instanceof Sabre_DAV_Property_Href);
+        $this->assertEquals('calendars/user1/notifications/',$prop->getHref());
+
+
         $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',$props[0][200]);
         $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'];
         $this->assertTrue($prop instanceof Sabre_DAV_Property_HrefList);
@@ -429,6 +462,7 @@ END:VCALENDAR';
         $this->assertInstanceOf('Sabre_DAV_Property_HrefList', $prop);
         $this->assertEquals(array('principals/admin'), $prop->getHrefs());
 
+
     }
 
     function testSupportedReportSetPropertyNonCalendar() {
@@ -502,9 +536,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -564,9 +598,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -629,9 +663,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -688,9 +722,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -755,7 +789,7 @@ END:VCALENDAR';
             '<d:prop>' .
             '  <c:calendar-data>' .
             '     <c:expand start="20000101T000000Z" end="20101231T235959Z" />' .
-            '  </c:calendar-data>' . 
+            '  </c:calendar-data>' .
             '  <d:getetag />' .
             '</d:prop>' .
             '<c:filter>' .
@@ -777,9 +811,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -837,9 +871,9 @@ END:VCALENDAR';
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body);
 
-        $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body));
+        $xml = simplexml_load_string($this->response->body);
 
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav');
 
         $check = array(
@@ -991,4 +1025,60 @@ END:VCALENDAR';
         $this->assertEquals('HTTP/1.1 400 Bad request',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body);
 
     }
+
+    function testNotificationProperties() {
+
+        $request = array(
+            '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype',
+        );
+        $result = array();
+        $notification = new Sabre_CalDAV_Notifications_Node(
+            $this->caldavBackend,
+            new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo')
+        );
+        $this->plugin->beforeGetProperties('foo', $notification, $request, $result);
+
+        $this->assertEquals(
+            array(
+                200 => array(
+                    '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype' => $notification->getNotificationType()
+                )
+            ), $result);
+
+    }
+
+    function testNotificationGet() {
+
+        $notification = new Sabre_CalDAV_Notifications_Node(
+            $this->caldavBackend,
+            new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo')
+        );
+
+        $server = new Sabre_DAV_Server(array($notification));
+        $caldav = new Sabre_CalDAV_Plugin();
+
+        $httpResponse = new Sabre_HTTP_ResponseMock();
+        $server->httpResponse = $httpResponse;
+
+        $server->addPlugin($caldav);
+
+        $caldav->beforeMethod('GET','foo');
+
+        $this->assertEquals('HTTP/1.1 200 OK', $httpResponse->status);
+        $this->assertEquals(array(
+            'Content-Type' => 'application/xml',
+        ), $httpResponse->headers);
+
+        $expected = 
+'<?xml version="1.0" encoding="UTF-8"?>
+<cs:notification xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
+  <cs:systemstatus type="high"/>
+</cs:notification>
+';
+
+        $this->assertEquals($expected, $httpResponse->body);
+
+
+    }
+
 }
index f2a5e5563c38405dea7e62c9ab41492d0970250c..42e8aebb2c46bb2a3e007dc8b86ab55acda20468 100644 (file)
@@ -22,6 +22,13 @@ class Sabre_CalDAV_ValidateICalTest extends PHPUnit_Framework_TestCase {
                 'id' => 'calendar1',
                 'principaluri' => 'principals/admin',
                 'uri' => 'calendar1',
+                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet( array('VEVENT','VTODO','VJOURNAL') ),
+            ),
+            array(
+                'id' => 'calendar2',
+                'principaluri' => 'principals/admin',
+                'uri' => 'calendar2',
+                '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet( array('VTODO','VJOURNAL') ),
             )
         );
 
@@ -207,4 +214,33 @@ class Sabre_CalDAV_ValidateICalTest extends PHPUnit_Framework_TestCase {
         $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1','blabla.ics'));
 
     }
+
+    function testCreateFileInvalidComponent() {
+
+        $request = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'PUT',
+            'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics',
+        ));
+        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
+
+        $response = $this->request($request);
+
+        $this->assertEquals('HTTP/1.1 403 Forbidden', $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
+
+    }
+
+    function testUpdateFileInvalidComponent() {
+
+        $this->calBackend->createCalendarObject('calendar2','blabla.ics','foo');
+        $request = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'PUT',
+            'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics',
+        ));
+        $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
+
+        $response = $this->request($request);
+
+        $this->assertEquals('HTTP/1.1 403 Forbidden', $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
+
+    }
 }
index 80a5d081ca663a5b0e1a6ab859e2727e57a967c6..a4c8e801517cc7886092546cfc5f489501f3141e 100644 (file)
@@ -65,20 +65,35 @@ class Sabre_CardDAV_ValidateVCardTest extends PHPUnit_Framework_TestCase {
             'REQUEST_METHOD' => 'PUT',
             'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf',
         ));
-        $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n");
+        $request->setBody("BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n");
 
         $response = $this->request($request);
 
         $this->assertEquals('HTTP/1.1 201 Created', $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
         $expected = array(
             'uri'          => 'blabla.vcf',
-            'carddata' => "BEGIN:VCARD\r\nEND:VCARD\r\n",
+            'carddata' => "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n",
         );
 
         $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1','blabla.vcf'));
 
     }
 
+    function testCreateFileNoUID() {
+
+        $request = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'PUT',
+            'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf',
+        ));
+        $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n");
+
+        $response = $this->request($request);
+
+        $this->assertEquals('HTTP/1.1 400 Bad request', $response->status, 'Incorrect status returned! Full response body: ' . $response->body);
+
+    }
+
+
     function testCreateFileVCalendar() {
 
         $request = new Sabre_HTTP_Request(array(
@@ -114,7 +129,7 @@ class Sabre_CardDAV_ValidateVCardTest extends PHPUnit_Framework_TestCase {
             'REQUEST_METHOD' => 'PUT',
             'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf',
         ));
-        $body = "BEGIN:VCARD\r\nEND:VCARD\r\n";
+        $body = "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n";
         $request->setBody($body);
 
         $response = $this->request($request);
index 758a492bfcbe87afe059608e9f66e6bfc918b014..0ce0eb3a05ec9ebda33053fa099dd48dbce4737d 100644 (file)
@@ -92,9 +92,9 @@ class Sabre_DAV_Locks_PluginTest extends Sabre_DAV_AbstractServer {
 
         $this->assertEquals('HTTP/1.1 200 OK',$this->response->status,'Got an incorrect status back. Response body: ' . $this->response->body);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         $elements = array(
             '/d:prop',
index ef8c1a0933c72bc6373556ded3a3edf6eece7c2e..c420f22dff3e394d954d276cf4e857ed7f90c5d2 100644 (file)
@@ -60,7 +60,7 @@ class Sabre_DAV_Property_HrefListTest extends PHPUnit_Framework_TestCase {
     function testUnserialize() {
 
         $xml = '<?xml version="1.0"?>
-<d:anything xmlns:d="urn:DAV"><d:href>/bla/foo</d:href><d:href>/bla/bar</d:href></d:anything>
+<d:anything xmlns:d="DAV:"><d:href>/bla/foo</d:href><d:href>/bla/bar</d:href></d:anything>
 ';
 
         $dom = new DOMDocument();
@@ -74,7 +74,7 @@ class Sabre_DAV_Property_HrefListTest extends PHPUnit_Framework_TestCase {
     function testUnserializeIncompatible() {
 
         $xml = '<?xml version="1.0"?>
-<d:anything xmlns:d="urn:DAV"><d:href2>/bla/foo</d:href2></d:anything>
+<d:anything xmlns:d="DAV:"><d:href2>/bla/foo</d:href2></d:anything>
 ';
 
         $dom = new DOMDocument();
index 8dcec751ec595912d17854d46b925f46a65f8489..12f0a5943e99225a7323c857fdb0226644974e64 100644 (file)
@@ -60,7 +60,7 @@ class Sabre_DAV_Property_HrefTest extends PHPUnit_Framework_TestCase {
     function testUnserialize() {
 
         $xml = '<?xml version="1.0"?>
-<d:anything xmlns:d="urn:DAV"><d:href>/bla/path</d:href></d:anything>
+<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything>
 ';
 
         $dom = new DOMDocument();
@@ -74,7 +74,7 @@ class Sabre_DAV_Property_HrefTest extends PHPUnit_Framework_TestCase {
     function testUnserializeIncompatible() {
 
         $xml = '<?xml version="1.0"?>
-<d:anything xmlns:d="urn:DAV"><d:href2>/bla/path</d:href2></d:anything>
+<d:anything xmlns:d="DAV:"><d:href2>/bla/path</d:href2></d:anything>
 ';
 
         $dom = new DOMDocument();
index 27d5825e60807909e0ee041935caa0747412d71d..086d59a9c9891b8796723f72489cff2b89d8c377 100644 (file)
@@ -37,9 +37,9 @@ class Sabre_DAV_Property_SupportedReportSetTest extends Sabre_DAV_AbstractServer
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We expected a multi-status response. Full response body: ' . $this->response->body);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop');
         $this->assertEquals(1,count($data),'We expected 1 \'d:prop\' element');
@@ -74,9 +74,9 @@ class Sabre_DAV_Property_SupportedReportSetTest extends Sabre_DAV_AbstractServer
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We expected a multi-status response. Full response body: ' . $this->response->body);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('x','http://www.rooftopsolutions.nl/testnamespace');
 
         $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop');
index a5e88f3a8cfabec3c3f12f3ac5eef657b0396ee3..7208fed2b8d1fd855cb923c92e04cb6079b21bbe 100644 (file)
@@ -6,6 +6,8 @@ class Sabre_DAV_ServerEventsTest extends Sabre_DAV_AbstractServer {
 
     private $tempPath;
 
+    private $exception;
+
     function testAfterBind() {
 
         $this->server->subscribeEvent('afterBind',array($this,'afterBindHandler'));
@@ -47,5 +49,25 @@ class Sabre_DAV_ServerEventsTest extends Sabre_DAV_AbstractServer {
 
     }
 
+    function testException() {
+
+        $this->server->subscribeEvent('exception', array($this, 'exceptionHandler'));
+
+        $req = new Sabre_HTTP_Request(array(
+            'REQUEST_METHOD' => 'GET',
+            'REQUEST_URI' => '/not/exisitng',
+        ));
+        $this->server->httpRequest = $req;
+        $this->server->exec();
+
+        $this->assertInstanceOf('Sabre_DAV_Exception_NotFound', $this->exception);
+
+    }
+
+    function exceptionHandler(Exception $exception) {
+
+        $this->exception = $exception;
+
+    }
 
 }
index 0a181dd70dd8656f830fb65f063f75a221439df9..a9abc1814e7f31aaf43ccfd68261b7a0fff8dea7 100644 (file)
@@ -192,7 +192,6 @@ class Sabre_DAV_ServerMKCOLTest extends Sabre_DAV_AbstractServer {
 
     }
 
-
     /**
      * @depends testMKCOLIncorrectResourceType2
      */
@@ -224,6 +223,38 @@ class Sabre_DAV_ServerMKCOLTest extends Sabre_DAV_AbstractServer {
 
     }
 
+    /**
+     * @depends testMKCOLIncorrectResourceType2
+     */
+    function testMKCOLWhiteSpaceResourceType() {
+
+        $serverVars = array(
+            'REQUEST_URI'    => '/testcol',
+            'REQUEST_METHOD' => 'MKCOL',
+            'HTTP_CONTENT_TYPE' => 'application/xml',
+        );
+
+        $request = new Sabre_HTTP_Request($serverVars);
+        $request->setBody('<?xml version="1.0"?>
+<mkcol xmlns="DAV:">
+  <set>
+    <prop>
+        <resourcetype>
+            <collection />
+        </resourcetype>
+    </prop>
+  </set>
+</mkcol>');
+        $this->server->httpRequest = ($request);
+        $this->server->exec();
+
+        $this->assertEquals(array(
+            'Content-Length' => '0',
+        ),$this->response->headers);
+
+        $this->assertEquals('HTTP/1.1 201 Created',$this->response->status,'Wrong statuscode received. Full response body: ' .$this->response->body);
+
+    }
 
     /**
      * @depends testMKCOLIncorrectResourceType2
index 484b038d3204c0a66155d1f92e94894db6886187..63a204cf93dbae81b91012b8d04edd232fdbda78 100644 (file)
@@ -58,9 +58,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
             $this->response->headers
          );
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         list($data) = $xml->xpath('/d:multistatus/d:response/d:href');
         $this->assertEquals('/',(string)$data,'href element should have been /');
@@ -81,9 +81,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
 
         $this->sendRequest($xml);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry');
         $this->assertEquals(2,count($data),'We expected two \'d:lockentry\' tags');
@@ -115,9 +115,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
 
         $this->sendRequest($xml);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery');
         $this->assertEquals(1,count($data),'We expected a \'d:lockdiscovery\' tag');
@@ -134,9 +134,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
 </d:propfind>';
 
         $this->sendRequest($xml);
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $pathTests = array(
             '/d:multistatus',
             '/d:multistatus/d:response',
@@ -312,9 +312,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
 
         $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We got the wrong status. Full XML response: ' . $this->response->body);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('bla','http://www.rooftopsolutions.nl/testnamespace');
 
         $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop');
@@ -345,9 +345,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer {
 
         $this->sendRequest($xml);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
         $xml->registerXPathNamespace('bla','http://www.rooftopsolutions.nl/testnamespace');
 
         $xpath='//bla:someprop';
index 4ac0b1ae0117efcab1055448f140351b538fd425..acd64380f7b7071ed91fd4817382b9a5efbed7b9 100644 (file)
@@ -233,9 +233,9 @@ class Sabre_DAV_TemporaryFileFilterTest extends Sabre_DAV_AbstractServer {
             'Content-Type' => 'application/xml; charset=utf-8',
         ),$this->response->headers);
 
-        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body);
+        $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body);
         $xml = simplexml_load_string($body);
-        $xml->registerXPathNamespace('d','urn:DAV');
+        $xml->registerXPathNamespace('d','DAV:');
 
         list($data) = $xml->xpath('/d:multistatus/d:response/d:href');
         $this->assertEquals('/._testput.txt',(string)$data,'href element should have been /._testput.txt');
index cf310ec89e7dc3d8ba56fc5043749e742d35f3b5..67d5bbeb12f27ed71c328e87719e67be61e2c80d 100644 (file)
@@ -37,6 +37,8 @@ class Sabre_DAV_Tree_FilesystemTest extends PHPUnit_Framework_TestCase {
         $fs = new Sabre_DAV_Tree_Filesystem(SABRE_TEMPDIR);
         $node = $fs->getNodeForPath('dir');
         $this->assertTrue($node instanceof Sabre_DAV_FS_Directory);
+        $this->assertEquals('dir', $node->getName());
+        $this->assertInternalType('array', $node->getChildren());
 
     }
 
index d70fe9136bdaf51ccd9b57f48cf79fe7dbc4e697..f7fcbb681636525b48193bf8eea6d243cb1839a5 100644 (file)
@@ -29,7 +29,7 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase {
     function testToClarkNotationDAVNamespace() {
 
         $dom = new DOMDocument();
-        $dom->loadXML('<?xml version="1.0"?><s:test1 xmlns:s="urn:DAV">Testdoc</s:test1>');
+        $dom->loadXML('<?xml version="1.0"?><s:test1 xmlns:s="DAV:">Testdoc</s:test1>');
 
         $this->assertEquals(
             '{DAV:}test1',
@@ -41,7 +41,7 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase {
     function testToClarkNotationNoElem() {
 
         $dom = new DOMDocument();
-        $dom->loadXML('<?xml version="1.0"?><s:test1 xmlns:s="urn:DAV">Testdoc</s:test1>');
+        $dom->loadXML('<?xml version="1.0"?><s:test1 xmlns:s="DAV:">Testdoc</s:test1>');
 
         $this->assertNull(
             Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild->firstChild)
@@ -49,59 +49,6 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase {
 
     }
 
-    function testConvertDAVNamespace() {
-
-        $xml='<?xml version="1.0"?><document xmlns="DAV:">blablabla</document>';
-        $this->assertEquals(
-            '<?xml version="1.0"?><document xmlns="urn:DAV">blablabla</document>',
-            Sabre_DAV_XMLUtil::convertDAVNamespace($xml)
-        );
-
-    }
-
-    function testConvertDAVNamespace2() {
-
-        $xml='<?xml version="1.0"?><s:document xmlns:s="DAV:">blablabla</s:document>';
-        $this->assertEquals(
-            '<?xml version="1.0"?><s:document xmlns:s="urn:DAV">blablabla</s:document>',
-            Sabre_DAV_XMLUtil::convertDAVNamespace($xml)
-        );
-
-    }
-
-    function testConvertDAVNamespace3() {
-
-        $xml='<?xml version="1.0"?><s:document xmlns="http://bla" xmlns:s="DAV:" xmlns:z="http://othernamespace">blablabla</s:document>';
-        $this->assertEquals(
-            '<?xml version="1.0"?><s:document xmlns="http://bla" xmlns:s="urn:DAV" xmlns:z="http://othernamespace">blablabla</s:document>',
-            Sabre_DAV_XMLUtil::convertDAVNamespace($xml)
-        );
-
-    }
-
-    function testConvertDAVNamespace4() {
-
-        $xml='<?xml version="1.0"?><document xmlns=\'DAV:\'>blablabla</document>';
-        $this->assertEquals(
-            '<?xml version="1.0"?><document xmlns=\'urn:DAV\'>blablabla</document>',
-            Sabre_DAV_XMLUtil::convertDAVNamespace($xml)
-        );
-
-    }
-
-    function testConvertDAVNamespaceMixedQuotes() {
-
-        $xml='<?xml version="1.0"?><document xmlns=\'DAV:" xmlns="Another attribute\'>blablabla</document>';
-        $this->assertEquals(
-            $xml,
-            Sabre_DAV_XMLUtil::convertDAVNamespace($xml)
-        );
-
-    }
-
-    /**
-     * @depends testConvertDAVNamespace
-     */
     function testLoadDOMDocument() {
 
         $xml='<?xml version="1.0"?><document></document>';
@@ -121,7 +68,6 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase {
     }
 
     /**
-     * @depends testConvertDAVNamespace
      * @expectedException Sabre_DAV_Exception_BadRequest
      */
     function testLoadDOMDocumentInvalid() {
index b5dd5144554210eebe0408b2c7a8266dd9a1f2c8..1bebcf85e461fddfc3a36e996c6cd12c03dec362 100644 (file)
@@ -60,6 +60,25 @@ class Sabre_HTTP_BasicAuthTest extends PHPUnit_Framework_TestCase {
 
     }
 
+    function testGetUserPassWithColon() {
+
+        $server = array(
+            'HTTP_AUTHORIZATION' => 'Basic ' . base64_encode('admin:1234:5678'),
+        );
+
+        $request = new Sabre_HTTP_Request($server);
+        $this->basicAuth->setHTTPRequest($request);
+
+        $userPass = $this->basicAuth->getUserPass();
+
+        $this->assertEquals(
+            array('admin','1234:5678'),
+            $userPass,
+            'We did not get the username and password we expected'
+        );
+
+    }
+
     function testGetUserPassApacheEdgeCase() {
 
         $server = array(
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VAlarmTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VAlarmTest.php
deleted file mode 100644 (file)
index 5229de7..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-
-class Sabre_VObject_Component_VAlarmTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @dataProvider timeRangeTestData
-     */
-    public function testInTimeRange(Sabre_VObject_Component_VAlarm $valarm,$start,$end,$outcome) {
-
-        $this->assertEquals($outcome, $valarm->isInTimeRange($start, $end));
-
-    }
-
-    public function timeRangeTestData() {
-
-        $tests = array();
-
-        // Hard date and time        
-        $valarm1 = Sabre_VObject_Component::create('VALARM');
-        $valarm1->TRIGGER = '20120312T130000Z';
-        $valarm1->TRIGGER['VALUE'] = 'DATE-TIME';
-
-        $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
-        $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
-
-        // Relation to start time of event
-        $valarm2 = Sabre_VObject_Component::create('VALARM');
-        $valarm2->TRIGGER = '-P1D';
-        $valarm2->TRIGGER['VALUE'] = 'DURATION';
-
-        $vevent2 = Sabre_VObject_Component::create('VEVENT');
-        $vevent2->DTSTART = '20120313T130000Z';
-        $vevent2->add($valarm2);
-
-        $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
-        $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
-
-        // Relation to end time of event
-        $valarm3 = Sabre_VObject_Component::create('VALARM');
-        $valarm3->TRIGGER = '-P1D';
-        $valarm3->TRIGGER['VALUE'] = 'DURATION';
-        $valarm3->TRIGGER['RELATED']= 'END';
-
-        $vevent3 = Sabre_VObject_Component::create('VEVENT');
-        $vevent3->DTSTART = '20120301T130000Z';
-        $vevent3->DTEND = '20120401T130000Z';
-        $vevent3->add($valarm3);
-
-        $tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
-        $tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
-
-        // Relation to end time of todo 
-        $valarm4 = Sabre_VObject_Component::create('VALARM');
-        $valarm4->TRIGGER = '-P1D';
-        $valarm4->TRIGGER['VALUE'] = 'DURATION';
-        $valarm4->TRIGGER['RELATED']= 'END';
-
-        $vtodo4 = Sabre_VObject_Component::create('VTODO');
-        $vtodo4->DTSTART = '20120301T130000Z';
-        $vtodo4->DUE = '20120401T130000Z';
-        $vtodo4->add($valarm4);
-
-        $tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
-        $tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
-
-        // Relation to start time of event + repeat
-        $valarm5 = Sabre_VObject_Component::create('VALARM');
-        $valarm5->TRIGGER = '-P1D';
-        $valarm5->TRIGGER['VALUE'] = 'DURATION';
-        $valarm5->REPEAT = 10;
-        $valarm5->DURATION = 'P1D';
-
-        $vevent5 = Sabre_VObject_Component::create('VEVENT');
-        $vevent5->DTSTART = '20120301T130000Z';
-        $vevent5->add($valarm5);
-
-        $tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true);
-
-        // Relation to start time of event + duration, but no repeat
-        $valarm6 = Sabre_VObject_Component::create('VALARM');
-        $valarm6->TRIGGER = '-P1D';
-        $valarm6->TRIGGER['VALUE'] = 'DURATION';
-        $valarm6->DURATION = 'P1D';
-
-        $vevent6 = Sabre_VObject_Component::create('VEVENT');
-        $vevent6->DTSTART = '20120313T130000Z';
-        $vevent6->add($valarm6);
-
-        $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
-        $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
-
-
-        // Relation to end time of event (DURATION instead of DTEND)
-        $valarm7 = Sabre_VObject_Component::create('VALARM');
-        $valarm7->TRIGGER = '-P1D';
-        $valarm7->TRIGGER['VALUE'] = 'DURATION';
-        $valarm7->TRIGGER['RELATED']= 'END';
-
-        $vevent7 = Sabre_VObject_Component::create('VEVENT');
-        $vevent7->DTSTART = '20120301T130000Z';
-        $vevent7->DURATION = 'P30D';
-        $vevent7->add($valarm7);
-
-        $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
-        $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
-
-        // Relation to end time of event (No DTEND or DURATION)
-        $valarm7 = Sabre_VObject_Component::create('VALARM');
-        $valarm7->TRIGGER = '-P1D';
-        $valarm7->TRIGGER['VALUE'] = 'DURATION';
-        $valarm7->TRIGGER['RELATED']= 'END';
-
-        $vevent7 = Sabre_VObject_Component::create('VEVENT');
-        $vevent7->DTSTART = '20120301T130000Z';
-        $vevent7->add($valarm7);
-
-        $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true);
-        $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false);
-
-
-        return $tests;
-    }
-
-    /**
-     * @expectedException Sabre_DAV_Exception
-     */
-    public function testInTimeRangeInvalidComponent() {
-
-        $valarm = Sabre_VObject_Component::create('VALARM');
-        $valarm->TRIGGER = '-P1D';
-        $valarm->TRIGGER['RELATED'] = 'END';
-
-        $vjournal = Sabre_VObject_Component::create('VJOURNAL');
-        $vjournal->add($valarm);
-
-        $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
-
-    }
-
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VCalendarTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VCalendarTest.php
deleted file mode 100644 (file)
index b1b503b..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php
-
-class Sabre_VObject_Component_VCalendarTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @dataProvider expandData
-     */
-    public function testExpand($input, $output) {
-
-        $vcal = Sabre_VObject_Reader::read($input);
-        $vcal->expand(
-            new DateTime('2011-12-01'),
-            new DateTime('2011-12-31')
-        );
-
-        // This will normalize the output
-        $output = Sabre_VObject_Reader::read($output)->serialize();
-
-        $this->assertEquals($output, $vcal->serialize());
-
-    }
-
-    public function expandData() {
-
-        $tests = array();
-
-        // No data
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-END:VCALENDAR
-';
-
-        $output = $input;
-        $tests[] = array($input,$output);
-
-
-        // Simple events
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla
-SUMMARY:InExpand
-DTSTART;VALUE=DATE:20111202
-END:VEVENT
-BEGIN:VEVENT
-UID:bla2
-SUMMARY:NotInExpand
-DTSTART;VALUE=DATE:20120101
-END:VEVENT
-END:VCALENDAR
-';
-
-        $output = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla
-SUMMARY:InExpand
-DTSTART;VALUE=DATE:20111202
-END:VEVENT
-END:VCALENDAR
-';
-
-        $tests[] = array($input, $output);
-
-        // Removing timezone info
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VTIMEZONE
-TZID:Europe/Paris
-END:VTIMEZONE
-BEGIN:VEVENT
-UID:bla4
-SUMMARY:RemoveTZ info
-DTSTART;TZID=Europe/Paris:20111203T130102
-END:VEVENT
-END:VCALENDAR
-';
-
-        $output = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla4
-SUMMARY:RemoveTZ info
-DTSTART;VALUE=DATE-TIME:20111203T120102Z
-END:VEVENT
-END:VCALENDAR
-';
-
-        $tests[] = array($input, $output);
-
-        // Recurrence rule
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART:20111125T120000Z
-DTEND:20111125T130000Z
-RRULE:FREQ=WEEKLY
-END:VEVENT
-END:VCALENDAR
-';
-
-        $output = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART;VALUE=DATE-TIME:20111202T120000Z
-DTEND;VALUE=DATE-TIME:20111202T130000Z
-RECURRENCE-ID:20111202T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART;VALUE=DATE-TIME:20111209T120000Z
-DTEND;VALUE=DATE-TIME:20111209T130000Z
-RECURRENCE-ID:20111209T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART;VALUE=DATE-TIME:20111216T120000Z
-DTEND;VALUE=DATE-TIME:20111216T130000Z
-RECURRENCE-ID:20111216T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART;VALUE=DATE-TIME:20111223T120000Z
-DTEND;VALUE=DATE-TIME:20111223T130000Z
-RECURRENCE-ID:20111223T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule
-DTSTART;VALUE=DATE-TIME:20111230T120000Z
-DTEND;VALUE=DATE-TIME:20111230T130000Z
-RECURRENCE-ID:20111230T120000Z
-END:VEVENT
-END:VCALENDAR
-';
-
-        // Recurrence rule + override
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule2
-DTSTART:20111125T120000Z
-DTEND:20111125T130000Z
-RRULE:FREQ=WEEKLY
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-RECURRENCE-ID:20111209T120000Z
-DTSTART:20111209T140000Z
-DTEND:20111209T150000Z
-SUMMARY:Override!
-END:VEVENT
-END:VCALENDAR
-';
-
-        $output = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule2
-DTSTART;VALUE=DATE-TIME:20111202T120000Z
-DTEND;VALUE=DATE-TIME:20111202T130000Z
-RECURRENCE-ID:20111202T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-RECURRENCE-ID:20111209T120000Z
-DTSTART:20111209T140000Z
-DTEND:20111209T150000Z
-SUMMARY:Override!
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule2
-DTSTART;VALUE=DATE-TIME:20111216T120000Z
-DTEND;VALUE=DATE-TIME:20111216T130000Z
-RECURRENCE-ID:20111216T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule2
-DTSTART;VALUE=DATE-TIME:20111223T120000Z
-DTEND;VALUE=DATE-TIME:20111223T130000Z
-RECURRENCE-ID:20111223T120000Z
-END:VEVENT
-BEGIN:VEVENT
-UID:bla6
-SUMMARY:Testing RRule2
-DTSTART;VALUE=DATE-TIME:20111230T120000Z
-DTEND;VALUE=DATE-TIME:20111230T130000Z
-RECURRENCE-ID:20111230T120000Z
-END:VEVENT
-END:VCALENDAR
-';
-
-        $tests[] = array($input, $output);
-        return $tests;
-
-    }
-
-    /**
-     * @expectedException LogicException
-     */
-    public function testBrokenEventExpand() {
-
-        $input = 'BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-BEGIN:VEVENT
-RRULE:FREQ=WEEKLY
-DTSTART;VALUE=DATE:20111202
-END:VEVENT
-END:VCALENDAR
-';
-        $vcal = Sabre_VObject_Reader::read($input);
-        $vcal->expand(
-            new DateTime('2011-12-01'),
-            new DateTime('2011-12-31')
-        );
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php
deleted file mode 100644 (file)
index a5a855f..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-class Sabre_VObject_Component_VEventTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @dataProvider timeRangeTestData
-     */
-    public function testInTimeRange(Sabre_VObject_Component_VEvent $vevent,$start,$end,$outcome) {
-
-        $this->assertEquals($outcome, $vevent->isInTimeRange($start, $end));
-
-    }
-
-    public function timeRangeTestData() {
-
-        $tests = array();
-
-        $vevent = new Sabre_VObject_Component_VEvent('VEVENT');
-        $vevent->DTSTART = '20111223T120000Z';
-        $tests[] = array($vevent, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vevent2 = clone $vevent;
-        $vevent2->DTEND = '20111225T120000Z';
-        $tests[] = array($vevent2, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent2, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vevent3 = clone $vevent;
-        $vevent3->DURATION = 'P1D';
-        $tests[] = array($vevent3, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent3, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vevent4 = clone $vevent;
-        $vevent4->DTSTART = '20111225';
-        $vevent4->DTSTART['VALUE'] = 'DATE';
-        $tests[] = array($vevent4, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent4, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-        // Event with no end date should be treated as lasting the entire day.
-        $tests[] = array($vevent4, new DateTime('2011-12-25 16:00:00'), new DateTime('2011-12-25 17:00:00'), true);
-
-
-        $vevent5 = clone $vevent;
-        $vevent5->DURATION = 'P1D';
-        $vevent5->RRULE = 'FREQ=YEARLY';
-        $tests[] = array($vevent5, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent5, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-        $tests[] = array($vevent5, new DateTime('2013-12-01'), new DateTime('2013-12-31'), true);
-
-        $vevent6 = clone $vevent;
-        $vevent6->DTSTART = '20111225';
-        $vevent6->DTSTART['VALUE'] = 'DATE';
-        $vevent6->DTEND   = '20111225';
-        $vevent6->DTEND['VALUE'] = 'DATE';
-        $tests[] = array($vevent6, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vevent6, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-        // Event with no end date should be treated as lasting the entire day.
-        $tests[] = array($vevent6, new DateTime('2011-12-25 16:00:00'), new DateTime('2011-12-25 17:00:00'), true);
-
-        return $tests;
-
-    }
-
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VJournalTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VJournalTest.php
deleted file mode 100644 (file)
index 04fcc9d..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-class Sabre_VObject_Component_VJournalTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @dataProvider timeRangeTestData
-     */
-    public function testInTimeRange(Sabre_VObject_Component_VJournal $vtodo,$start,$end,$outcome) {
-
-        $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
-
-    }
-
-    public function timeRangeTestData() {
-
-        $tests = array();
-
-        $vjournal = Sabre_VObject_Component::create('VJOURNAL');
-        $vjournal->DTSTART = '20111223T120000Z';
-        $tests[] = array($vjournal, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vjournal, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vjournal2 = Sabre_VObject_Component::create('VJOURNAL');
-        $vjournal2->DTSTART = '20111223';
-        $vjournal2->DTSTART['VALUE'] = 'DATE';
-        $tests[] = array($vjournal2, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vjournal2, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vjournal3 = Sabre_VObject_Component::create('VJOURNAL');
-        $tests[] = array($vjournal3, new DateTime('2011-01-01'), new DateTime('2012-01-01'), false);
-        $tests[] = array($vjournal3, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        return $tests;
-    }
-
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VTodoTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VTodoTest.php
deleted file mode 100644 (file)
index a8bd619..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-class Sabre_VObject_Component_VTodoTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * @dataProvider timeRangeTestData
-     */
-    public function testInTimeRange(Sabre_VObject_Component_VTodo $vtodo,$start,$end,$outcome) {
-
-        $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
-
-    }
-
-    public function timeRangeTestData() {
-
-        $tests = array();
-
-        $vtodo = Sabre_VObject_Component::create('VTODO');
-        $vtodo->DTSTART = '20111223T120000Z';
-        $tests[] = array($vtodo, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo2 = clone $vtodo;
-        $vtodo2->DURATION = 'P1D';
-        $tests[] = array($vtodo2, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo2, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo3 = clone $vtodo;
-        $vtodo3->DUE = '20111225';
-        $tests[] = array($vtodo3, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo3, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo4 = Sabre_VObject_Component::create('VTODO');
-        $vtodo4->DUE = '20111225';
-        $tests[] = array($vtodo4, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo4, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo5 = Sabre_VObject_Component::create('VTODO');
-        $vtodo5->COMPLETED = '20111225';
-        $tests[] = array($vtodo5, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo5, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo6 = Sabre_VObject_Component::create('VTODO');
-        $vtodo6->CREATED = '20111225';
-        $tests[] = array($vtodo6, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo6, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo7 = Sabre_VObject_Component::create('VTODO');
-        $vtodo7->CREATED = '20111225';
-        $vtodo7->COMPLETED = '20111226';
-        $tests[] = array($vtodo7, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo7, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false);
-
-        $vtodo7 = Sabre_VObject_Component::create('VTODO');
-        $tests[] = array($vtodo7, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true);
-        $tests[] = array($vtodo7, new DateTime('2011-01-01'), new DateTime('2011-11-01'), true);
-
-        return $tests;
-
-    }
-
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php b/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php
deleted file mode 100644 (file)
index 71b33c0..0000000
+++ /dev/null
@@ -1,361 +0,0 @@
-<?php
-
-class Sabre_VObject_ComponentTest extends PHPUnit_Framework_TestCase {
-
-    function testIterate() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $sub = new Sabre_VObject_Component('VEVENT');
-        $comp->children[] = $sub;
-
-        $sub = new Sabre_VObject_Component('VTODO');
-        $comp->children[] = $sub;
-
-        $count = 0;
-        foreach($comp->children() as $key=>$subcomponent) {
-
-           $count++;
-           $this->assertInstanceOf('Sabre_VObject_Component',$subcomponent);
-
-        }
-        $this->assertEquals(2,$count);
-        $this->assertEquals(1,$key);
-
-    }
-
-    function testMagicGet() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $sub = new Sabre_VObject_Component('VEVENT');
-        $comp->children[] = $sub;
-
-        $sub = new Sabre_VObject_Component('VTODO');
-        $comp->children[] = $sub;
-
-        $event = $comp->vevent;
-        $this->assertInstanceOf('Sabre_VObject_Component', $event);
-        $this->assertEquals('VEVENT', $event->name);
-
-        $this->assertInternalType('null', $comp->vjournal);
-
-    }
-
-    function testMagicGetGroups() {
-
-        $comp = new Sabre_VObject_Component('VCARD');
-
-        $sub = new Sabre_VObject_Property('GROUP1.EMAIL','1@1.com');
-        $comp->children[] = $sub;
-
-        $sub = new Sabre_VObject_Property('GROUP2.EMAIL','2@2.com');
-        $comp->children[] = $sub;
-
-        $sub = new Sabre_VObject_Property('EMAIL','3@3.com');
-        $comp->children[] = $sub;
-
-        $emails = $comp->email;
-        $this->assertEquals(3, count($emails));
-
-        $email1 = $comp->{"group1.email"};
-        $this->assertEquals('EMAIL', $email1[0]->name);
-        $this->assertEquals('GROUP1', $email1[0]->group);
-
-        $email3 = $comp->{".email"};
-        $this->assertEquals('EMAIL', $email3[0]->name);
-        $this->assertEquals(null, $email3[0]->group);
-
-    }
-
-    function testMagicIsset() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $sub = new Sabre_VObject_Component('VEVENT');
-        $comp->children[] = $sub;
-
-        $sub = new Sabre_VObject_Component('VTODO');
-        $comp->children[] = $sub;
-
-        $this->assertTrue(isset($comp->vevent));
-        $this->assertTrue(isset($comp->vtodo));
-        $this->assertFalse(isset($comp->vjournal));
-
-    }
-
-    function testMagicSetScalar() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->myProp = 'myValue';
-
-        $this->assertInstanceOf('Sabre_VObject_Property',$comp->MYPROP);
-        $this->assertEquals('myValue',$comp->MYPROP->value);
-
-
-    }
-
-    function testMagicSetScalarTwice() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->myProp = 'myValue';
-        $comp->myProp = 'myValue';
-
-        $this->assertEquals(1,count($comp->children));
-        $this->assertInstanceOf('Sabre_VObject_Property',$comp->MYPROP);
-        $this->assertEquals('myValue',$comp->MYPROP->value);
-
-    }
-
-    function testMagicSetComponent() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        // Note that 'myProp' is ignored here.
-        $comp->myProp = new Sabre_VObject_Component('VEVENT');
-
-        $this->assertEquals(1, count($comp->children));
-
-        $this->assertEquals('VEVENT',$comp->VEVENT->name);
-
-    }
-
-    function testMagicSetTwice() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $comp->VEVENT = new Sabre_VObject_Component('VEVENT');
-        $comp->VEVENT = new Sabre_VObject_Component('VEVENT');
-
-        $this->assertEquals(1, count($comp->children));
-
-        $this->assertEquals('VEVENT',$comp->VEVENT->name);
-
-    }
-
-    function testArrayAccessGet() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $event = new Sabre_VObject_Component('VEVENT');
-        $event->summary = 'Event 1';
-
-        $comp->add($event);
-
-        $event2 = clone $event;
-        $event2->summary = 'Event 2';
-
-        $comp->add($event2);
-
-        $this->assertEquals(2,count($comp->children()));
-        $this->assertTrue($comp->vevent[1] instanceof Sabre_VObject_Component);
-        $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary);
-
-    }
-
-    function testArrayAccessExists() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $event = new Sabre_VObject_Component('VEVENT');
-        $event->summary = 'Event 1';
-
-        $comp->add($event);
-
-        $event2 = clone $event;
-        $event2->summary = 'Event 2';
-
-        $comp->add($event2);
-
-        $this->assertTrue(isset($comp->vevent[0]));
-        $this->assertTrue(isset($comp->vevent[1]));
-
-    }
-
-    /**
-     * @expectedException LogicException
-     */
-    function testArrayAccessSet() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp['hey'] = 'hi there';
-
-    }
-    /**
-     * @expectedException LogicException
-     */
-    function testArrayAccessUnset() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        unset($comp[0]);
-
-    }
-
-    function testAddScalar() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $comp->add('myprop','value');
-
-        $this->assertEquals(1, count($comp->children));
-
-        $this->assertTrue($comp->children[0] instanceof Sabre_VObject_Property);
-        $this->assertEquals('MYPROP',$comp->children[0]->name);
-        $this->assertEquals('value',$comp->children[0]->value);
-
-    }
-
-    function testAddComponent() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $comp->add(new Sabre_VObject_Component('VEVENT'));
-
-        $this->assertEquals(1, count($comp->children));
-
-        $this->assertEquals('VEVENT',$comp->VEVENT->name);
-
-    }
-
-    function testAddComponentTwice() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        $comp->add(new Sabre_VObject_Component('VEVENT'));
-        $comp->add(new Sabre_VObject_Component('VEVENT'));
-
-        $this->assertEquals(2, count($comp->children));
-
-        $this->assertEquals('VEVENT',$comp->VEVENT->name);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->add(new Sabre_VObject_Component('VEVENT'),'hello');
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail2() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->add(array());
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail3() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->add('hello',array());
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testMagicSetInvalid() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        // Note that 'myProp' is ignored here.
-        $comp->myProp = new StdClass();
-
-        $this->assertEquals(1, count($comp->children));
-
-        $this->assertEquals('VEVENT',$comp->VEVENT->name);
-
-    }
-
-    function testMagicUnset() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->add(new Sabre_VObject_Component('VEVENT'));
-
-        unset($comp->vevent);
-
-        $this->assertEquals(array(), $comp->children);
-
-    }
-
-
-    function testCount() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $this->assertEquals(1,$comp->count());
-
-    }
-
-    function testChildren() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        // Note that 'myProp' is ignored here.
-        $comp->children = array(
-            new Sabre_VObject_Component('VEVENT'),
-            new Sabre_VObject_Component('VTODO')
-        );
-
-        $r = $comp->children();
-        $this->assertTrue($r instanceof Sabre_VObject_ElementList);
-        $this->assertEquals(2,count($r));
-    }
-
-    function testGetComponents() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-
-        // Note that 'myProp' is ignored here.
-        $comp->children = array(
-            new Sabre_VObject_Property('FOO','BAR'),
-            new Sabre_VObject_Component('VTODO')
-        );
-
-        $r = $comp->getComponents();
-        $this->assertInternalType('array', $r);
-        $this->assertEquals(1, count($r));
-        $this->assertEquals('VTODO', $r[0]->name);
-    }
-
-    function testSerialize() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
-
-    }
-
-    function testSerializeChildren() {
-
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->children = array(
-            new Sabre_VObject_Component('VEVENT'),
-            new Sabre_VObject_Component('VTODO')
-        );
-        $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $comp->serialize());
-
-    }
-
-    function testSerializeOrder() {
-        
-        $comp = new Sabre_VObject_Component('VCALENDAR');
-        $comp->add(new Sabre_VObject_Component('VEVENT'));
-        $comp->add('PROP1','BLABLA');
-        $comp->add('VERSION','2.0');
-        $comp->add(new Sabre_VObject_Component('VTIMEZONE'));
-
-        $str = $comp->serialize();
-
-        $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/DateTimeParserTest.php b/dav/SabreDAV/tests/Sabre/VObject/DateTimeParserTest.php
deleted file mode 100644 (file)
index 83e4c42..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-require_once 'Sabre/CalDAV/TestUtil.php';
-
-class Sabre_VObject_DateTimeParserTest extends PHPUnit_Framework_TestCase {
-
-    function testParseICalendarDuration() {
-
-        $this->assertEquals('+1 weeks', Sabre_VObject_DateTimeParser::parseDuration('P1W',true));
-        $this->assertEquals('+5 days',  Sabre_VObject_DateTimeParser::parseDuration('P5D',true));
-        $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', Sabre_VObject_DateTimeParser::parseDuration('P5DT3H50M12S',true));
-        $this->assertEquals('-1 weeks 50 minutes', Sabre_VObject_DateTimeParser::parseDuration('-P1WT50M',true));
-        $this->assertEquals('+50 days 3 hours 2 seconds', Sabre_VObject_DateTimeParser::parseDuration('+P50DT3H2S',true));
-        $this->assertEquals(new DateInterval('PT0S'), Sabre_VObject_DateTimeParser::parseDuration('PT0S'));
-
-    }
-
-    function testParseICalendarDurationDateInterval() {
-
-        $expected = new DateInterval('P7D');
-        $this->assertEquals($expected, Sabre_VObject_DateTimeParser::parseDuration('P1W'));
-        $this->assertEquals($expected, Sabre_VObject_DateTimeParser::parse('P1W'));
-
-        $expected = new DateInterval('PT3M');
-        $expected->invert = true;
-        $this->assertEquals($expected, Sabre_VObject_DateTimeParser::parseDuration('-PT3M'));
-
-    }
-
-    /**
-     * @expectedException Sabre_DAV_Exception_BadRequest
-     */
-    function testParseICalendarDurationFail() {
-
-        Sabre_VObject_DateTimeParser::parseDuration('P1X',true);
-
-    }
-
-    function testParseICalendarDateTime() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDateTime('20100316T141405');
-
-        $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
-
-        $this->assertEquals($compare, $dateTime);
-
-    }
-
-    /**
-     * @depends testParseICalendarDateTime
-     * @expectedException Sabre_DAV_Exception_BadRequest
-     */
-    function testParseICalendarDateTimeBadFormat() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDateTime('20100316T141405 ');
-
-    }
-
-    /**
-     * @depends testParseICalendarDateTime
-     */
-    function testParseICalendarDateTimeUTC() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDateTime('20100316T141405Z');
-
-        $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
-        $this->assertEquals($compare, $dateTime);
-
-    }
-
-    /**
-     * @depends testParseICalendarDateTime
-     */
-    function testParseICalendarDateTimeUTC2() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDateTime('20101211T160000Z');
-
-        $compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC'));
-        $this->assertEquals($compare, $dateTime);
-
-    }
-
-    /**
-     * @depends testParseICalendarDateTime
-     */
-    function testParseICalendarDateTimeCustomTimeZone() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
-
-        $compare = new DateTime('2010-03-16 13:14:05',new DateTimeZone('UTC'));
-        $this->assertEquals($compare, $dateTime);
-
-    }
-
-    function testParseICalendarDate() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDate('20100316');
-
-        $expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC'));
-
-        $this->assertEquals($expected, $dateTime);
-
-        $dateTime = Sabre_VObject_DateTimeParser::parse('20100316');
-        $this->assertEquals($expected, $dateTime);
-
-    }
-
-    /**
-     * @depends testParseICalendarDate
-     * @expectedException Sabre_DAV_Exception_BadRequest
-     */
-    function testParseICalendarDateBadFormat() {
-
-        $dateTime = Sabre_VObject_DateTimeParser::parseDate('20100316T141405');
-
-    }
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/ElementListTest.php b/dav/SabreDAV/tests/Sabre/VObject/ElementListTest.php
deleted file mode 100644 (file)
index 5bc8b43..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-class Sabre_VObject_ElementListTest extends PHPUnit_Framework_TestCase {
-
-    function testIterate() {
-
-        $sub = new Sabre_VObject_Component('VEVENT');
-
-        $elems = array(
-            $sub,
-            clone $sub,
-            clone $sub
-        );
-
-        $elemList = new Sabre_VObject_ElementList($elems);
-
-        $count = 0;
-        foreach($elemList as $key=>$subcomponent) {
-
-           $count++;
-           $this->assertInstanceOf('Sabre_VObject_Component',$subcomponent);
-
-        }
-        $this->assertEquals(3,$count);
-        $this->assertEquals(2,$key);
-
-    }
-
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/EmClientTest.php b/dav/SabreDAV/tests/Sabre/VObject/EmClientTest.php
deleted file mode 100644 (file)
index 6933023..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-class Sabre_VObject_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 = Sabre_VObject_Reader::read($str);
-        $dt = $vObject->VEVENT->DTSTART->getDateTime();
-        $this->assertEquals(new DateTime('2011-10-08 19:30:00', new DateTimeZone('America/Chicago')), $dt);
-
-    }
-
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/FreeBusyGeneratorTest.php b/dav/SabreDAV/tests/Sabre/VObject/FreeBusyGeneratorTest.php
deleted file mode 100644 (file)
index d84f5a4..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-<?php
-
-class Sabre_VObject_FreeBusyGeneratorTest extends PHPUnit_Framework_TestCase {
-
-    function getInput() {
-
-        // shows up
-$blob1 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110101T120000Z
-DTEND:20110101T130000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // opaque, shows up
-$blob2 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-TRANSP:OPAQUE
-DTSTART:20110101T130000Z
-DTEND:20110101T140000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // transparent, hidden
-$blob3 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-TRANSP:TRANSPARENT
-DTSTART:20110101T140000Z
-DTEND:20110101T150000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // cancelled, hidden
-$blob4 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-STATUS:CANCELLED
-DTSTART:20110101T160000Z
-DTEND:20110101T170000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // tentative, shows up
-$blob5 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-STATUS:TENTATIVE
-DTSTART:20110101T180000Z
-DTEND:20110101T190000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // outside of time-range, hidden
-$blob6 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110101T090000Z
-DTEND:20110101T100000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // outside of time-range, hidden
-$blob7 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110104T090000Z
-DTEND:20110104T100000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // using duration, shows up
-$blob8 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110101T190000Z
-DURATION:PT1H
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-    // Day-long event, shows up
-$blob9 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART;TYPE=DATE:20110102
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-// No duration, does not show up
-$blob10 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110101T200000Z
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-// encoded as object, shows up
-$blob11 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20110101T210000Z
-DURATION:PT1H
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-// Freebusy. Some parts show up
-$blob12 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VFREEBUSY
-FREEBUSY:20110103T010000Z/20110103T020000Z
-FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z
-FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z
-FREEBUSY:20120101T000000Z/20120101T010000Z
-FREEBUSY:20110103T050000Z/PT1H
-END:VFREEBUSY
-END:VCALENDAR
-ICS;
-
-// Yearly recurrence rule, shows up
-$blob13 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20100101T220000Z
-DTEND:20100101T230000Z
-RRULE:FREQ=YEARLY
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-// Yearly recurrence rule + duration, shows up
-$blob14 = <<<ICS
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-DTSTART:20100101T230000Z
-DURATION:PT1H
-RRULE:FREQ=YEARLY
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-
-        return array(
-            $blob1,
-            $blob2,
-            $blob3,
-            $blob4,
-            $blob5,
-            $blob6,
-            $blob7,
-            $blob8,
-            $blob9,
-            $blob10,
-            Sabre_VObject_Reader::read($blob11),
-            $blob12,
-            $blob13,
-            $blob14,
-        );
-
-    }
-
-    function testGenerator() {
-
-        $gen = new Sabre_VObject_FreeBusyGenerator();
-        $gen->setObjects($this->getInput());
-        $gen->setTimeRange(
-            new DateTime('20110101T110000Z'),
-            new DateTime('20110103T110000Z')
-        );
-
-        $result = $gen->getResult();
-
-        $expected = array(
-            '20110101T120000Z/20110101T130000Z',
-            '20110101T130000Z/20110101T140000Z',
-            '20110101T180000Z/20110101T190000Z',
-            '20110101T190000Z/20110101T200000Z',
-            '20110102T000000Z/20110103T000000Z',
-            '20110101T210000Z/20110101T220000Z',
-
-            '20110103T010000Z/20110103T020000Z',
-            '20110103T030000Z/20110103T040000Z',
-            '20110103T040000Z/20110103T050000Z',
-            '20110103T050000Z/20110103T060000Z',
-
-            '20110101T220000Z/20110101T230000Z',
-            '20110101T230000Z/20110102T000000Z',
-        );
-
-        foreach($result->VFREEBUSY->FREEBUSY as $fb) {
-
-            $this->assertContains((string)$fb, $expected);
-
-            $k = array_search((string)$fb, $expected);
-            unset($expected[$k]);
-
-        }
-        if (count($expected)>0) {
-            $this->fail('There were elements in the expected array that were not found in the output: ' . "\n"  . print_r($expected,true) . "\n" . $result->serialize());
-
-        }
-
-    }
-
-    function testGeneratorBaseObject() {
-
-        $obj = new Sabre_VObject_Component('VCALENDAR');
-        $obj->METHOD = 'PUBLISH';
-
-        $gen = new Sabre_VObject_FreeBusyGenerator();
-        $gen->setObjects(array());
-        $gen->setBaseObject($obj);
-
-        $result = $gen->getResult();
-
-        $this->assertEquals('PUBLISH', $result->METHOD->value);
-
-    }
-    function testGeneratorNoVersion() {
-
-        $v = Sabre_DAV_Server::$exposeVersion;
-        Sabre_DAV_Server::$exposeVersion = false;
-
-        $gen = new Sabre_VObject_FreeBusyGenerator();
-        $gen->setObjects(array());
-
-        $result = $gen->getResult();
-        Sabre_DAV_Server::$exposeVersion = $v;
-
-        $this->assertFalse(strpos($result->PRODID->value, Sabre_VObject_Version::VERSION));
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testInvalidArg() {
-
-        $gen = new Sabre_VObject_FreeBusyGenerator();
-        $gen->setObjects(array(new StdClass()));
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Issue153Test.php b/dav/SabreDAV/tests/Sabre/VObject/Issue153Test.php
deleted file mode 100644 (file)
index 16557c5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-class Sabre_VObject_Issue153Test extends PHPUnit_Framework_TestCase {
-
-    function testRead() {
-
-        $obj = Sabre_VObject_Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf'));
-        $this->assertEquals('Test Benutzer', (string)$obj->fn);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php b/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php
deleted file mode 100644 (file)
index a004eb0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-class Sabre_VObject_Issue154Test extends PHPUnit_Framework_TestCase {
-
-    function testStuff() {
-
-        $vcard = new Sabre_VObject_Component('VCARD');
-        $vcard->VERSION = '3.0';
-        $vcard->UID = 'foo-bar';
-        $vcard->PHOTO = base64_encode('random_stuff');
-        $vcard->PHOTO->add('BASE64',null);
-
-        $result = $vcard->serialize();
-        $expected = array(
-            "BEGIN:VCARD",
-            "VERSION:3.0",
-            "PHOTO;BASE64:" . base64_encode('random_stuff'),
-            "UID:foo-bar",
-            "END:VCARD",
-            "",
-        );
-
-        $this->assertEquals(implode("\r\n", $expected), $result);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/ParameterTest.php b/dav/SabreDAV/tests/Sabre/VObject/ParameterTest.php
deleted file mode 100644 (file)
index ea30997..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-class Sabre_VObject_ParameterTest extends PHPUnit_Framework_TestCase {
-
-    function testSetup() {
-
-        $param = new Sabre_VObject_Parameter('name','value');
-        $this->assertEquals('NAME',$param->name);
-        $this->assertEquals('value',$param->value);
-
-    }
-
-    function testCastToString() {
-
-        $param = new Sabre_VObject_Parameter('name','value');
-        $this->assertEquals('value',$param->__toString());
-        $this->assertEquals('value',(string)$param);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Property/DateTimeTest.php b/dav/SabreDAV/tests/Sabre/VObject/Property/DateTimeTest.php
deleted file mode 100644 (file)
index d6a9830..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-<?php
-
-class Sabre_VObject_Property_DateTimeTest extends PHPUnit_Framework_TestCase {
-
-    function testSetDateTime() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt);
-
-        $this->assertEquals('19850704T013000', $elem->value);
-        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeLOCAL() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt, Sabre_VObject_Property_DateTime::LOCAL);
-
-        $this->assertEquals('19850704T013000', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeUTC() {
-
-        $tz = new DateTimeZone('GMT');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt, Sabre_VObject_Property_DateTime::UTC);
-
-        $this->assertEquals('19850704T013000Z', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeLOCALTZ() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt, Sabre_VObject_Property_DateTime::LOCALTZ);
-
-        $this->assertEquals('19850704T013000', $elem->value);
-        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeDATE() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt, Sabre_VObject_Property_DateTime::DATE);
-
-        $this->assertEquals('19850704', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE', (string)$elem['VALUE']);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testSetDateTimeInvalid() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt, 7);
-
-    }
-
-    function testGetDateTimeCached() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $elem->setDateTime($dt);
-
-        $this->assertEquals($elem->getDateTime(), $dt);
-
-    }
-
-    function testGetDateTimeDateNULL() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dt = $elem->getDateTime();
-
-        $this->assertNull($dt);
-        $this->assertNull($elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateDATE() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704');
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals(Sabre_VObject_Property_DateTime::DATE, $elem->getDateType());
-
-    }
-
-
-    function testGetDateTimeDateLOCAL() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCAL, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateUTC() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000Z');
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('UTC', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::UTC, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateLOCALTZ() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $elem['TZID'] = 'Europe/Amsterdam';
-
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCALTZ, $elem->getDateType());
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testGetDateTimeDateInvalid() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','bla');
-        $dt = $elem->getDateTime();
-
-    }
-
-    function testGetDateTimeWeirdTZ() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
-
-
-        $event = new Sabre_VObject_Component('VEVENT');
-        $event->add($elem);
-
-        $timezone = new Sabre_VObject_Component('VTIMEZONE');
-        $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
-        $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam';
-
-        $calendar = new Sabre_VObject_Component('VCALENDAR');
-        $calendar->add($event);
-        $calendar->add($timezone);
-
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCALTZ, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeBadTimeZone() {
-
-        $default = date_default_timezone_get();
-        date_default_timezone_set('Canada/Eastern');
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $elem['TZID'] = 'Moon';
-
-
-        $event = new Sabre_VObject_Component('VEVENT');
-        $event->add($elem);
-
-        $timezone = new Sabre_VObject_Component('VTIMEZONE');
-        $timezone->TZID = 'Moon';
-        $timezone->{'X-LIC-LOCATION'} = 'Moon';
-
-        $calendar = new Sabre_VObject_Component('VCALENDAR');
-        $calendar->add($event);
-        $calendar->add($timezone);
-
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCALTZ, $elem->getDateType());
-        date_default_timezone_set($default);
-
-    }
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/Property/MultiDateTimeTest.php b/dav/SabreDAV/tests/Sabre/VObject/Property/MultiDateTimeTest.php
deleted file mode 100644 (file)
index 4d70ed3..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-<?php
-
-class Sabre_VObject_Property_MultiDateTimeTest extends PHPUnit_Framework_TestCase {
-
-    function testSetDateTime() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt1 = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt2 = new DateTime('1986-07-04 01:30:00', $tz);
-        $dt1->setTimeZone($tz);
-        $dt2->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2));
-
-        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
-        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeLOCAL() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt1 = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt2 = new DateTime('1986-07-04 01:30:00', $tz);
-        $dt1->setTimeZone($tz);
-        $dt2->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2), Sabre_VObject_Property_DateTime::LOCAL);
-
-        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeUTC() {
-
-        $tz = new DateTimeZone('GMT');
-        $dt1 = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt2 = new DateTime('1986-07-04 01:30:00', $tz);
-        $dt1->setTimeZone($tz);
-        $dt2->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2), Sabre_VObject_Property_DateTime::UTC);
-
-        $this->assertEquals('19850704T013000Z,19860704T013000Z', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeLOCALTZ() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt1 = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt2 = new DateTime('1986-07-04 01:30:00', $tz);
-        $dt1->setTimeZone($tz);
-        $dt2->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2), Sabre_VObject_Property_DateTime::LOCALTZ);
-
-        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
-        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
-        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
-
-    }
-
-    function testSetDateTimeDATE() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt1 = new datetime('1985-07-04 01:30:00', $tz);
-        $dt2 = new datetime('1986-07-04 01:30:00', $tz);
-        $dt1->settimezone($tz);
-        $dt2->settimezone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2), Sabre_VObject_Property_DateTime::DATE);
-
-        $this->assertEquals('19850704,19860704', $elem->value);
-        $this->assertNull($elem['TZID']);
-        $this->assertEquals('DATE', (string)$elem['VALUE']);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testSetDateTimeInvalid() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt = new DateTime('1985-07-04 01:30:00', $tz);
-        $dt->setTimeZone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt), 7);
-
-    }
-
-    function testGetDateTimeCached() {
-
-        $tz = new DateTimeZone('Europe/Amsterdam');
-        $dt1 = new datetime('1985-07-04 01:30:00', $tz);
-        $dt2 = new datetime('1986-07-04 01:30:00', $tz);
-        $dt1->settimezone($tz);
-        $dt2->settimezone($tz);
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $elem->setDateTimes(array($dt1,$dt2));
-
-        $this->assertEquals($elem->getDateTimes(), array($dt1,$dt2));
-
-    }
-
-    function testGetDateTimeDateNULL() {
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART');
-        $dt = $elem->getDateTimes();
-
-        $this->assertNull($dt);
-        $this->assertNull($elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateDATE() {
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART','19850704,19860704');
-        $dt = $elem->getDateTimes();
-
-        $this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
-        $this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
-        $this->assertEquals(Sabre_VObject_Property_DateTime::DATE, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateDATEReverse() {
-
-        $elem = new Sabre_VObject_Property_MultiDateTime('DTSTART','19850704,19860704');
-
-        $this->assertEquals(Sabre_VObject_Property_DateTime::DATE, $elem->getDateType());
-
-        $dt = $elem->getDateTimes();
-        $this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
-        $this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
-
-    }
-
-
-    function testGetDateTimeDateLOCAL() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCAL, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateUTC() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000Z');
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('UTC', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::UTC, $elem->getDateType());
-
-    }
-
-    function testGetDateTimeDateLOCALTZ() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','19850704T013000');
-        $elem['TZID'] = 'Europe/Amsterdam';
-
-        $dt = $elem->getDateTime();
-
-        $this->assertInstanceOf('DateTime', $dt);
-        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
-        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
-        $this->assertEquals(Sabre_VObject_Property_DateTime::LOCALTZ, $elem->getDateType());
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testGetDateTimeDateInvalid() {
-
-        $elem = new Sabre_VObject_Property_DateTime('DTSTART','bla');
-        $dt = $elem->getDateTime();
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/PropertyTest.php b/dav/SabreDAV/tests/Sabre/VObject/PropertyTest.php
deleted file mode 100644 (file)
index 40fb146..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-<?php
-
-class Sabre_VObject_PropertyTest extends PHPUnit_Framework_TestCase {
-
-    public function testToString() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $this->assertEquals('PROPNAME', $property->name);
-        $this->assertEquals('propvalue', $property->value);
-        $this->assertEquals('propvalue', $property->__toString());
-        $this->assertEquals('propvalue', (string)$property);
-
-    }
-
-    public function testParameterExists() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $this->assertTrue(isset($property['PARAMNAME']));
-        $this->assertTrue(isset($property['paramname']));
-        $this->assertFalse(isset($property['foo']));
-
-    }
-
-    public function testParameterGet() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $this->assertInstanceOf('Sabre_VObject_Parameter',$property['paramname']);
-
-    }
-
-    public function testParameterNotExists() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $this->assertInternalType('null',$property['foo']);
-
-    }
-
-    public function testParameterMultiple() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $this->assertInstanceOf('Sabre_VObject_Parameter',$property['paramname']);
-        $this->assertEquals(2,count($property['paramname']));
-
-    }
-
-    public function testSetParameterAsString() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property['paramname'] = 'paramvalue';
-
-        $this->assertEquals(1,count($property->parameters));
-        $this->assertInstanceOf('Sabre_VObject_Parameter', $property->parameters[0]);
-        $this->assertEquals('PARAMNAME',$property->parameters[0]->name);
-        $this->assertEquals('paramvalue',$property->parameters[0]->value);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    public function testSetParameterAsStringNoKey() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property[] = 'paramvalue';
-
-    }
-
-    public function testSetParameterObject() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $param = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $property[] = $param;
-
-        $this->assertEquals(1,count($property->parameters));
-        $this->assertEquals($param, $property->parameters[0]);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    public function testSetParameterObjectWithKey() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $param = new Sabre_VObject_Parameter('paramname','paramvalue');
-
-        $property['key'] = $param;
-
-    }
-
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    public function testSetParameterObjectRandomObject() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property[] = new StdClass();
-
-    }
-
-    public function testUnsetParameter() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $param = new Sabre_VObject_Parameter('paramname','paramvalue');
-        $property->parameters[] = $param;
-
-        unset($property['PARAMNAME']);
-        $this->assertEquals(0,count($property->parameters));
-
-    }
-
-    public function testParamCount() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $param = new Sabre_VObject_Parameter('paramname','paramvalue');
-        $property->parameters[] = $param;
-        $property->parameters[] = clone $param;
-
-        $this->assertEquals(2,count($property->parameters));
-
-    }
-
-    public function testSerialize() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-
-        $this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize());
-
-    }
-
-    public function testSerializeParam() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname','paramvalue');
-        $property->parameters[] = new Sabre_VObject_Parameter('paramname2','paramvalue2');
-
-        $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize());
-
-    }
-
-    public function testSerializeNewLine() {
-
-        $property = new Sabre_VObject_Property('propname',"line1\nline2");
-
-        $this->assertEquals("PROPNAME:line1\\nline2\r\n",$property->serialize());
-
-    }
-
-    public function testSerializeLongLine() {
-
-        $value = str_repeat('!',200);
-        $property = new Sabre_VObject_Property('propname',$value);
-
-        $expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n";
-
-        $this->assertEquals($expected,$property->serialize());
-
-    }
-
-    public function testSerializeUTF8LineFold() {
-
-        $value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a
-        $property = new Sabre_VObject_Property('propname', $value);
-        $expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n";
-        $this->assertEquals($expected, $property->serialize());
-
-    }
-
-    public function testGetIterator() {
-
-        $it = new Sabre_VObject_ElementList(array());
-        $property = new Sabre_VObject_Property('propname','propvalue', $it);
-        $this->assertEquals($it,$property->getIterator());
-
-    }
-
-
-    public function testGetIteratorDefault() {
-
-        $property = new Sabre_VObject_Property('propname','propvalue');
-        $it = $property->getIterator();
-        $this->assertTrue($it instanceof Sabre_VObject_ElementList);
-        $this->assertEquals(1,count($it));
-
-    }
-
-    function testAddScalar() {
-
-        $property = new Sabre_VObject_Property('EMAIL');
-
-        $property->add('myparam','value');
-
-        $this->assertEquals(1, count($property->parameters));
-
-        $this->assertTrue($property->parameters[0] instanceof Sabre_VObject_Parameter);
-        $this->assertEquals('MYPARAM',$property->parameters[0]->name);
-        $this->assertEquals('value',$property->parameters[0]->value);
-
-    }
-
-    function testAddParameter() {
-
-        $prop = new Sabre_VObject_Property('EMAIL');
-
-        $prop->add(new Sabre_VObject_Parameter('MYPARAM','value'));
-
-        $this->assertEquals(1, count($prop->parameters));
-        $this->assertEquals('MYPARAM',$prop['myparam']->name);
-
-    }
-
-    function testAddParameterTwice() {
-
-        $prop = new Sabre_VObject_Property('EMAIL');
-
-        $prop->add(new Sabre_VObject_Parameter('MYPARAM', 'value1'));
-        $prop->add(new Sabre_VObject_Parameter('MYPARAM', 'value2'));
-
-        $this->assertEquals(2, count($prop->parameters));
-
-        $this->assertEquals('MYPARAM',$prop['MYPARAM']->name);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail() {
-
-        $prop = new Sabre_VObject_Property('EMAIL');
-        $prop->add(new Sabre_VObject_Parameter('MPARAM'),'hello');
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail2() {
-
-        $property = new Sabre_VObject_Property('EMAIL','value');
-        $property->add(array());
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testAddArgFail3() {
-
-        $property = new Sabre_VObject_Property('EMAIL','value');
-        $property->add('hello',array());
-
-    }
-
-    function testClone() {
-
-        $property = new Sabre_VObject_Property('EMAIL','value');
-        $property['FOO'] = 'BAR';
-
-        $property2 = clone $property;
-        
-        $property['FOO'] = 'BAZ';
-        $this->assertEquals('BAR', (string)$property2['FOO']);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/ReaderTest.php b/dav/SabreDAV/tests/Sabre/VObject/ReaderTest.php
deleted file mode 100644 (file)
index a87651c..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-
-class Sabre_VObject_ReaderTest extends PHPUnit_Framework_TestCase {
-
-    function testReadComponent() {
-
-        $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
-
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(0, count($result->children));
-
-    }
-
-    function testReadComponentUnixNewLine() {
-
-        $data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
-
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(0, count($result->children));
-
-    }
-
-    function testReadComponentMacNewLine() {
-
-        $data = "BEGIN:VCALENDAR\rEND:VCALENDAR";
-
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(0, count($result->children));
-
-    }
-
-    function testReadComponentLineFold() {
-
-        $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR";
-
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(0, count($result->children));
-
-    }
-
-    /**
-     * @expectedException Sabre_VObject_ParseException
-     */
-    function testReadCorruptComponent() {
-
-        $data = "BEGIN:VCALENDAR\r\nEND:FOO";
-
-        $result = Sabre_VObject_Reader::read($data);
-
-    }
-
-    function testReadProperty() {
-
-        $data = "PROPNAME:propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-
-    }
-
-    function testReadPropertyWithNewLine() {
-
-        $data = 'PROPNAME:Line1\\nLine2\\NLine3\\\\Not the 4th line!';
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->value);
-
-    }
-
-    function testReadMappedProperty() {
-
-        $data = "DTSTART:20110529";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property_DateTime', $result);
-        $this->assertEquals('DTSTART', $result->name);
-        $this->assertEquals('20110529', $result->value);
-
-    }
-
-    function testReadMappedPropertyGrouped() {
-
-        $data = "foo.DTSTART:20110529";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property_DateTime', $result);
-        $this->assertEquals('DTSTART', $result->name);
-        $this->assertEquals('20110529', $result->value);
-
-    }
-
-
-    /**
-     * @expectedException Sabre_VObject_ParseException
-     */
-    function testReadBrokenLine() {
-
-        $data = "PROPNAME;propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-    }
-
-    function testReadPropertyInComponent() {
-
-        $data = array(
-            "BEGIN:VCALENDAR",
-            "PROPNAME:propValue",
-            "END:VCALENDAR"
-        );
-
-        $result = Sabre_VObject_Reader::read(implode("\r\n",$data));
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(1, count($result->children));
-        $this->assertInstanceOf('Sabre_VObject_Property', $result->children[0]);
-        $this->assertEquals('PROPNAME', $result->children[0]->name);
-        $this->assertEquals('propValue', $result->children[0]->value);
-
-
-    }
-    function testReadNestedComponent() {
-
-        $data = array(
-            "BEGIN:VCALENDAR",
-            "BEGIN:VTIMEZONE",
-            "BEGIN:DAYLIGHT",
-            "END:DAYLIGHT",
-            "END:VTIMEZONE",
-            "END:VCALENDAR"
-        );
-
-        $result = Sabre_VObject_Reader::read(implode("\r\n",$data));
-
-        $this->assertInstanceOf('Sabre_VObject_Component', $result);
-        $this->assertEquals('VCALENDAR', $result->name);
-        $this->assertEquals(1, count($result->children));
-        $this->assertInstanceOf('Sabre_VObject_Component', $result->children[0]);
-        $this->assertEquals('VTIMEZONE', $result->children[0]->name);
-        $this->assertEquals(1, count($result->children[0]->children));
-        $this->assertInstanceOf('Sabre_VObject_Component', $result->children[0]->children[0]);
-        $this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name);
-
-
-    }
-
-    function testReadPropertyParameter() {
-
-        $data = "PROPNAME;PARAMNAME=paramvalue:propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('paramvalue', $result->parameters[0]->value);
-
-    }
-
-    function testReadPropertyNoValue() {
-
-        $data = "PROPNAME;PARAMNAME:propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('', $result->parameters[0]->value);
-
-    }
-
-    function testReadPropertyParameterExtraColon() {
-
-        $data = "PROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue:anotherrandomstring', $result->value);
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('paramvalue', $result->parameters[0]->value);
-
-    }
-
-    function testReadProperty2Parameters() {
-
-        $data = "PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-        $this->assertEquals(2, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('paramvalue', $result->parameters[0]->value);
-        $this->assertEquals('PARAMNAME2', $result->parameters[1]->name);
-        $this->assertEquals('paramvalue2', $result->parameters[1]->value);
-
-    }
-
-    function testReadPropertyParameterQuoted() {
-
-        $data = "PROPNAME;PARAMNAME=\"paramvalue\":propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('paramvalue', $result->parameters[0]->value);
-
-    }
-    function testReadPropertyParameterNewLines() {
-
-        $data = "PROPNAME;PARAMNAME=paramvalue1\\nvalue2\\\\nvalue3:propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals("paramvalue1\nvalue2\\nvalue3", $result->parameters[0]->value);
-
-    }
-
-    function testReadPropertyParameterQuotedColon() {
-
-        $data = "PROPNAME;PARAMNAME=\"param:value\":propValue";
-        $result = Sabre_VObject_Reader::read($data);
-
-        $this->assertInstanceOf('Sabre_VObject_Property', $result);
-        $this->assertEquals('PROPNAME', $result->name);
-        $this->assertEquals('propValue', $result->value);
-        $this->assertEquals(1, count($result->parameters));
-        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
-        $this->assertEquals('param:value', $result->parameters[0]->value);
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php b/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php
deleted file mode 100644 (file)
index 75972c2..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-class Sabre_VObject_RecurrenceIteratorFifthTuesdayProblemTest extends PHPUnit_Framework_TestCase {
-
-    function testGetDTEnd() {
-
-        $ics = <<<ICS
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.4//EN
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-TRANSP:OPAQUE
-DTEND;TZID=America/New_York:20070925T170000
-UID:uuid
-DTSTAMP:19700101T000000Z
-LOCATION:
-DESCRIPTION:
-STATUS:CONFIRMED
-SEQUENCE:18
-SUMMARY:Stuff
-DTSTART;TZID=America/New_York:20070925T160000
-CREATED:20071004T144642Z
-RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU
-END:VEVENT
-END:VCALENDAR
-ICS;
-
-        $vObject = Sabre_VObject_Reader::read($ics);
-        $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$vObject->VEVENT->UID);
-
-        while($it->valid()) {
-            $it->next();
-        }
-
-        // If we got here, it means we were successful. The bug that was in teh 
-        // system before would fail on the 5th tuesday of the month, if the 5th 
-        // tuesday did not exist.
-       
-    }
-
-}
-
-?>
diff --git a/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php b/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php
deleted file mode 100644 (file)
index 1506250..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-class Sabre_VObject_RecurrenceIteratorInfiniteLoopProblemTest extends PHPUnit_Framework_TestCase {
-
-    /**
-     * This bug came from a Fruux customer. This would result in a never-ending
-     * request.
-     */
-    function testFastForwardTooFar() {
-
-        $ev = Sabre_VObject_Component::create('VEVENT');
-        $ev->DTSTART = '20090420T180000Z';
-        $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1';
-
-        $this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00')));
-
-    }
-
-    /**
-     * Different bug, also likely an infinite loop.
-     */
-    function testYearlyByMonthLoop() {
-
-        $ev = Sabre_VObject_Component::create('VEVENT');
-        $ev->UID = 'uuid';
-        $ev->DTSTART = '20120101T154500';
-        $ev->DTSTART['TZID'] = 'Europe/Berlin';
-        $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
-        $ev->DTEND = '20120101T164500';
-        $ev->DTEND['TZID'] = 'Europe/Berlin';
-
-        // This recurrence rule by itself is a yearly rule that should happen
-        // every february.
-        //
-        // The BYDAY part expands this to every day of the month, but the
-        // BYSETPOS limits this to only the 1st day of the month. Very crazy
-        // way to specify this, and could have certainly been a lot easier.
-        $cal = Sabre_VObject_Component::create('VCALENDAR');
-        $cal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($cal,'uuid');
-        $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
-
-        $collect = array();
-
-        while($it->valid()) {
-            $collect[] = $it->getDTSTART();
-            if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
-                break;
-            }
-            $it->next();
-
-        }
-
-        $this->assertEquals(
-            array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))),
-            $collect
-        );
-
-    }
-
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php b/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php
deleted file mode 100644 (file)
index d1ef2da..0000000
+++ /dev/null
@@ -1,1087 +0,0 @@
-<?php
-
-class Sabre_VObject_RecurrenceIteratorTest extends PHPUnit_Framework_TestCase {
-
-    function testValues() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals(array(10), $it->byHour);
-        $this->assertEquals(array(5), $it->byMinute);
-        $this->assertEquals(array(16), $it->bySecond);
-        $this->assertEquals(array(32), $it->byWeekNo);
-        $this->assertEquals(array(100,200), $it->byYearDay);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     * @depends testValues
-     */
-    function testInvalidFreq() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testVCalendarNoUID() {
-
-        $vcal = new Sabre_VObject_Component('VCALENDAR');
-        $it = new Sabre_VObject_RecurrenceIterator($vcal);
-
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    function testVCalendarInvalidUID() {
-
-        $vcal = new Sabre_VObject_Component('VCALENDAR');
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,'foo');
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testDaily() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,$ev->uid);
-
-        $this->assertEquals('daily', $it->frequency);
-        $this->assertEquals(3, $it->interval);
-        $this->assertEquals(new DateTime('2011-10-25', new DateTimeZone('UTC')), $it->until);
-
-        // Max is to prevent overflow
-        $max = 12;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-10-07', $tz),
-                new DateTime('2011-10-10', $tz),
-                new DateTime('2011-10-13', $tz),
-                new DateTime('2011-10-16', $tz),
-                new DateTime('2011-10-19', $tz),
-                new DateTime('2011-10-22', $tz),
-                new DateTime('2011-10-25', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testNoRRULE() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,$ev->uid);
-
-        $this->assertEquals('daily', $it->frequency);
-        $this->assertEquals(1, $it->interval);
-
-        // Max is to prevent overflow
-        $max = 12;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-10-07', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testDailyByDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('daily', $it->frequency);
-        $this->assertEquals(2, $it->interval);
-        $this->assertEquals(array('TU','WE','FR'), $it->byDay);
-
-        // Grabbing the next 12 items
-        $max = 12;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-10-07', $tz),
-                new DateTime('2011-10-11', $tz),
-                new DateTime('2011-10-19', $tz),
-                new DateTime('2011-10-21', $tz),
-                new DateTime('2011-10-25', $tz),
-                new DateTime('2011-11-02', $tz),
-                new DateTime('2011-11-04', $tz),
-                new DateTime('2011-11-08', $tz),
-                new DateTime('2011-11-16', $tz),
-                new DateTime('2011-11-18', $tz),
-                new DateTime('2011-11-22', $tz),
-                new DateTime('2011-11-30', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testWeekly() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('weekly', $it->frequency);
-        $this->assertEquals(2, $it->interval);
-        $this->assertEquals(10, $it->count);
-
-        // Max is to prevent overflow
-        $max = 12;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-10-07', $tz),
-                new DateTime('2011-10-21', $tz),
-                new DateTime('2011-11-04', $tz),
-                new DateTime('2011-11-18', $tz),
-                new DateTime('2011-12-02', $tz),
-                new DateTime('2011-12-16', $tz),
-                new DateTime('2011-12-30', $tz),
-                new DateTime('2012-01-13', $tz),
-                new DateTime('2012-01-27', $tz),
-                new DateTime('2012-02-10', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testWeeklyByDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-10-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('weekly', $it->frequency);
-        $this->assertEquals(2, $it->interval);
-        $this->assertEquals(array('TU','WE','FR'), $it->byDay);
-        $this->assertEquals('SU', $it->weekStart);
-
-        // Grabbing the next 12 items
-        $max = 12;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-10-07', $tz),
-                new DateTime('2011-10-18', $tz),
-                new DateTime('2011-10-19', $tz),
-                new DateTime('2011-10-21', $tz),
-                new DateTime('2011-11-01', $tz),
-                new DateTime('2011-11-02', $tz),
-                new DateTime('2011-11-04', $tz),
-                new DateTime('2011-11-15', $tz),
-                new DateTime('2011-11-16', $tz),
-                new DateTime('2011-11-18', $tz),
-                new DateTime('2011-11-29', $tz),
-                new DateTime('2011-11-30', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthly() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-12-05'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(3, $it->interval);
-        $this->assertEquals(5, $it->count);
-
-        $max = 14;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-12-05', $tz),
-                new DateTime('2012-03-05', $tz),
-                new DateTime('2012-06-05', $tz),
-                new DateTime('2012-09-05', $tz),
-                new DateTime('2012-12-05', $tz),
-            ),
-            $result
-        );
-
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthlyEndOfMonth() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-12-31'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(2, $it->interval);
-        $this->assertEquals(12, $it->count);
-
-        $max = 14;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-12-31', $tz),
-                new DateTime('2012-08-31', $tz),
-                new DateTime('2012-10-31', $tz),
-                new DateTime('2012-12-31', $tz),
-                new DateTime('2013-08-31', $tz),
-                new DateTime('2013-10-31', $tz),
-                new DateTime('2013-12-31', $tz),
-                new DateTime('2014-08-31', $tz),
-                new DateTime('2014-10-31', $tz),
-                new DateTime('2014-12-31', $tz),
-                new DateTime('2015-08-31', $tz),
-                new DateTime('2015-10-31', $tz),
-            ),
-            $result
-        );
-
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthlyByMonthDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-01-01'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(5, $it->interval);
-        $this->assertEquals(9, $it->count);
-        $this->assertEquals(array(1, 31, -7), $it->byMonthDay);
-
-        $max = 14;
-        $result = array();
-        foreach($it as $item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-01-01', $tz),
-                new DateTime('2011-01-25', $tz),
-                new DateTime('2011-01-31', $tz),
-                new DateTime('2011-06-01', $tz),
-                new DateTime('2011-06-24', $tz),
-                new DateTime('2011-11-01', $tz),
-                new DateTime('2011-11-24', $tz),
-                new DateTime('2012-04-01', $tz),
-                new DateTime('2012-04-24', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthlyByDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-01-03'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(2, $it->interval);
-        $this->assertEquals(16, $it->count);
-        $this->assertEquals(array('MO','-2TU','+1WE','3TH'), $it->byDay);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-01-03', $tz),
-                new DateTime('2011-01-05', $tz),
-                new DateTime('2011-01-10', $tz),
-                new DateTime('2011-01-17', $tz),
-                new DateTime('2011-01-18', $tz),
-                new DateTime('2011-01-20', $tz),
-                new DateTime('2011-01-24', $tz),
-                new DateTime('2011-01-31', $tz),
-                new DateTime('2011-03-02', $tz),
-                new DateTime('2011-03-07', $tz),
-                new DateTime('2011-03-14', $tz),
-                new DateTime('2011-03-17', $tz),
-                new DateTime('2011-03-21', $tz),
-                new DateTime('2011-03-22', $tz),
-                new DateTime('2011-03-28', $tz),
-                new DateTime('2011-05-02', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthlyByDayByMonthDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-08-01'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(1, $it->interval);
-        $this->assertEquals(10, $it->count);
-        $this->assertEquals(array('MO'), $it->byDay);
-        $this->assertEquals(array(1), $it->byMonthDay);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-08-01', $tz),
-                new DateTime('2012-10-01', $tz),
-                new DateTime('2013-04-01', $tz),
-                new DateTime('2013-07-01', $tz),
-                new DateTime('2014-09-01', $tz),
-                new DateTime('2014-12-01', $tz),
-                new DateTime('2015-06-01', $tz),
-                new DateTime('2016-02-01', $tz),
-                new DateTime('2016-08-01', $tz),
-                new DateTime('2017-05-01', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testMonthlyByDayBySetPos() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-01-03'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('monthly', $it->frequency);
-        $this->assertEquals(1, $it->interval);
-        $this->assertEquals(10, $it->count);
-        $this->assertEquals(array('MO','TU','WE','TH','FR'), $it->byDay);
-        $this->assertEquals(array(1,-1), $it->bySetPos);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-01-03', $tz),
-                new DateTime('2011-01-31', $tz),
-                new DateTime('2011-02-01', $tz),
-                new DateTime('2011-02-28', $tz),
-                new DateTime('2011-03-01', $tz),
-                new DateTime('2011-03-31', $tz),
-                new DateTime('2011-04-01', $tz),
-                new DateTime('2011-04-29', $tz),
-                new DateTime('2011-05-02', $tz),
-                new DateTime('2011-05-31', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testYearly() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-01-01'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('yearly', $it->frequency);
-        $this->assertEquals(3, $it->interval);
-        $this->assertEquals(10, $it->count);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-01-01', $tz),
-                new DateTime('2014-01-01', $tz),
-                new DateTime('2017-01-01', $tz),
-                new DateTime('2020-01-01', $tz),
-                new DateTime('2023-01-01', $tz),
-                new DateTime('2026-01-01', $tz),
-                new DateTime('2029-01-01', $tz),
-                new DateTime('2032-01-01', $tz),
-                new DateTime('2035-01-01', $tz),
-                new DateTime('2038-01-01', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testYearlyByMonth() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-04-07'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('yearly', $it->frequency);
-        $this->assertEquals(4, $it->interval);
-        $this->assertEquals(8, $it->count);
-        $this->assertEquals(array(4,10), $it->byMonth);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-04-07', $tz),
-                new DateTime('2011-10-07', $tz),
-                new DateTime('2015-04-07', $tz),
-                new DateTime('2015-10-07', $tz),
-                new DateTime('2019-04-07', $tz),
-                new DateTime('2019-10-07', $tz),
-                new DateTime('2023-04-07', $tz),
-                new DateTime('2023-10-07', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testYearlyByMonthByDay() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-04-04'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('yearly', $it->frequency);
-        $this->assertEquals(5, $it->interval);
-        $this->assertEquals(8, $it->count);
-        $this->assertEquals(array(4,10), $it->byMonth);
-        $this->assertEquals(array('1MO','-1SU'), $it->byDay);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-04-04', $tz),
-                new DateTime('2011-04-24', $tz),
-                new DateTime('2011-10-03', $tz),
-                new DateTime('2011-10-30', $tz),
-                new DateTime('2016-04-04', $tz),
-                new DateTime('2016-04-24', $tz),
-                new DateTime('2016-10-03', $tz),
-                new DateTime('2016-10-30', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testFastForward() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-        $dtStart->setDateTime(new DateTime('2011-04-04'),Sabre_VObject_Property_DateTime::UTC);
-
-        $ev->add($dtStart);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        // The idea is that we're fast-forwarding too far in the future, so
-        // there will be no results left.
-        $it->fastForward(new DateTime('2020-05-05'));
-
-        $max = 20;
-        $result = array();
-        while($item = $it->current()) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-            $it->next();
-
-        }
-
-        $tz = new DateTimeZone('UTC');
-        $this->assertEquals(array(), $result);
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testComplexExclusions() {
-
-        $ev = new Sabre_VObject_Component('VEVENT');
-        $ev->UID = 'bla';
-        $ev->RRULE = 'FREQ=YEARLY;COUNT=10';
-        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
-
-        $tz = new DateTimeZone('Canada/Eastern');
-        $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz),Sabre_VObject_Property_DateTime::LOCALTZ);
-
-        $exDate1 = new Sabre_VObject_Property_MultiDateTime('EXDATE');
-        $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz)), Sabre_VObject_Property_DateTime::LOCALTZ);
-        $exDate2 = new Sabre_VObject_Property_MultiDateTime('EXDATE');
-        $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz)), Sabre_VObject_Property_DateTime::LOCALTZ);
-
-        $ev->add($dtStart);
-        $ev->add($exDate1);
-        $ev->add($exDate2);
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-        $vcal->add($ev);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid);
-
-        $this->assertEquals('yearly', $it->frequency);
-        $this->assertEquals(1, $it->interval);
-        $this->assertEquals(10, $it->count);
-
-        $max = 20;
-        $result = array();
-        foreach($it as $k=>$item) {
-
-            $result[] = $item;
-            $max--;
-
-            if (!$max) break;
-
-        }
-
-        $this->assertEquals(
-            array(
-                new DateTime('2011-01-01 13:50:20', $tz),
-                new DateTime('2013-01-01 13:50:20', $tz),
-                new DateTime('2015-01-01 13:50:20', $tz),
-                new DateTime('2017-01-01 13:50:20', $tz),
-                new DateTime('2018-01-01 13:50:20', $tz),
-                new DateTime('2019-01-01 13:50:20', $tz),
-                new DateTime('2020-01-01 13:50:20', $tz),
-            ),
-            $result
-        );
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testOverridenEvent() {
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-
-        $ev1 = Sabre_VObject_Component::create('VEVENT');
-        $ev1->UID = 'overridden';
-        $ev1->RRULE = 'FREQ=DAILY;COUNT=10';
-        $ev1->DTSTART = '20120107T120000Z';
-        $ev1->SUMMARY = 'baseEvent';
-
-        $vcal->add($ev1);
-
-        // ev2 overrides an event, and puts it on 2pm instead.
-        $ev2 = Sabre_VObject_Component::create('VEVENT');
-        $ev2->UID = 'overridden';
-        $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
-        $ev2->DTSTART = '20120110T140000Z';
-        $ev2->SUMMARY = 'Event 2';
-
-        $vcal->add($ev2);
-
-        // ev3 overrides an event, and puts it 2 days and 2 hours later 
-        $ev3 = Sabre_VObject_Component::create('VEVENT');
-        $ev3->UID = 'overridden';
-        $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
-        $ev3->DTSTART = '20120115T140000Z';
-        $ev3->SUMMARY = 'Event 3';
-
-        $vcal->add($ev3);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,'overridden');
-
-        $dates = array();
-        $summaries = array();
-        while($it->valid()) {
-
-            $dates[] = $it->getDTStart();
-            $summaries[] = (string)$it->getEventObject()->SUMMARY;
-            $it->next();
-
-        }
-
-        $tz = new DateTimeZone('GMT');
-        $this->assertEquals(array(
-            new DateTime('2012-01-07 12:00:00',$tz),
-            new DateTime('2012-01-08 12:00:00',$tz),
-            new DateTime('2012-01-09 12:00:00',$tz),
-            new DateTime('2012-01-10 14:00:00',$tz),
-            new DateTime('2012-01-11 12:00:00',$tz),
-            new DateTime('2012-01-12 12:00:00',$tz),
-            new DateTime('2012-01-14 12:00:00',$tz),
-            new DateTime('2012-01-15 12:00:00',$tz),
-            new DateTime('2012-01-15 14:00:00',$tz),
-            new DateTime('2012-01-16 12:00:00',$tz),
-        ), $dates);
-
-        $this->assertEquals(array(
-            'baseEvent',
-            'baseEvent',
-            'baseEvent',
-            'Event 2',
-            'baseEvent',
-            'baseEvent',
-            'baseEvent',
-            'baseEvent',
-            'Event 3',
-            'baseEvent',
-        ), $summaries);
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testOverridenEvent2() {
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-
-        $ev1 = Sabre_VObject_Component::create('VEVENT');
-        $ev1->UID = 'overridden';
-        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
-        $ev1->DTSTART = '20120112T120000Z';
-        $ev1->SUMMARY = 'baseEvent';
-
-        $vcal->add($ev1);
-
-        // ev2 overrides an event, and puts it 6 days earlier instead.
-        $ev2 = Sabre_VObject_Component::create('VEVENT');
-        $ev2->UID = 'overridden';
-        $ev2->{'RECURRENCE-ID'} = '20120119T120000Z';
-        $ev2->DTSTART = '20120113T120000Z';
-        $ev2->SUMMARY = 'Override!';
-
-        $vcal->add($ev2);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,'overridden');
-
-        $dates = array();
-        $summaries = array();
-        while($it->valid()) {
-
-            $dates[] = $it->getDTStart();
-            $summaries[] = (string)$it->getEventObject()->SUMMARY;
-            $it->next();
-
-        }
-
-        $tz = new DateTimeZone('GMT');
-        $this->assertEquals(array(
-            new DateTime('2012-01-12 12:00:00',$tz),
-            new DateTime('2012-01-13 12:00:00',$tz),
-            new DateTime('2012-01-26 12:00:00',$tz),
-
-        ), $dates);
-
-        $this->assertEquals(array(
-            'baseEvent',
-            'Override!',
-            'baseEvent',
-        ), $summaries);
-
-    }
-
-    /**
-     * @depends testValues
-     */
-    function testOverridenEventNoValuesExpected() {
-
-        $vcal = Sabre_VObject_Component::create('VCALENDAR');
-
-        $ev1 = Sabre_VObject_Component::create('VEVENT');
-        $ev1->UID = 'overridden';
-        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
-        $ev1->DTSTART = '20120124T120000Z';
-        $ev1->SUMMARY = 'baseEvent';
-
-        $vcal->add($ev1);
-
-        // ev2 overrides an event, and puts it 6 days earlier instead.
-        $ev2 = Sabre_VObject_Component::create('VEVENT');
-        $ev2->UID = 'overridden';
-        $ev2->{'RECURRENCE-ID'} = '20120131T120000Z';
-        $ev2->DTSTART = '20120125T120000Z';
-        $ev2->SUMMARY = 'Override!';
-
-        $vcal->add($ev2);
-
-        $it = new Sabre_VObject_RecurrenceIterator($vcal,'overridden');
-
-        $dates = array();
-        $summaries = array();
-
-        // The reported problem was specifically related to the VCALENDAR 
-        // expansion. In this parcitular case, we had to forward to the 28th of 
-        // january.
-        $it->fastForward(new DateTime('2012-01-28 23:00:00'));
-
-        // We stop the loop when it hits the 6th of februari. Normally this 
-        // iterator would hit 24, 25 (overriden from 31) and 7 feb but because 
-        // we 'filter' from the 28th till the 6th, we should get 0 results.
-        while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) {
-
-            $dates[] = $it->getDTStart();
-            $summaries[] = (string)$it->getEventObject()->SUMMARY;
-            $it->next();
-
-        }
-
-        $this->assertEquals(array(), $dates);
-        $this->assertEquals(array(), $summaries);
-
-    }
-}
-
diff --git a/dav/SabreDAV/tests/Sabre/VObject/VersionTest.php b/dav/SabreDAV/tests/Sabre/VObject/VersionTest.php
deleted file mode 100644 (file)
index ea2a4b2..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-class Sabre_VObject_VersionTest extends PHPUnit_Framework_TestCase {
-
-    function testString() {
-
-        $v = Sabre_VObject_Version::VERSION;
-        $this->assertEquals(-1, version_compare('0.9.0',$v));
-
-        $s = Sabre_VObject_Version::STABILITY;
-        $this->assertTrue($s == 'alpha' || $s == 'beta' || $s =='stable');
-
-    }
-
-}
diff --git a/dav/SabreDAV/tests/Sabre/VObject/issue153.vcf b/dav/SabreDAV/tests/Sabre/VObject/issue153.vcf
deleted file mode 100644 (file)
index 5fb0fa2..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-BEGIN:VCARD\r
-VERSION:3.0\r
-N:Benutzer;Test;;;\r
-FN:Test Benutzer\r
-PHOTO;BASE64:\r
-  /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
-  AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
-  AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
-  Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
-  CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
-  AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
-  kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
-  aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
-  1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
-  CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
-  YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
-  goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
-  5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
-  F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
-  7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
-  BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
-  t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau
-  m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H
-  a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii
-  KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ
-  BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW
-  u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn
-  bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4
-  g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci
-  QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh
-  UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9
-  CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc
-  u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku
-  Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP
-  j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP
-  OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro
-  /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU
-  LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy
-  9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl
-  G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW
-  QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb
-  94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD
-  5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+
-  dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV
-  4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0
-  sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW
-  rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K
-  rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk
-  HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD
-  xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC
-  yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY
-  itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN
-  AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh
-  dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V
-  DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A
-  RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun
-  8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg
-  QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt
-  pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS
-  nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu
-  lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V
-  5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF
-  tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3
-  Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs
-  uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+
-  1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx
-  sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r
-  VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP
-  X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY
-  2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm
-  P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi
-  yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N
-  t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk
-  OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4
-  V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish
-  yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46
-  ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW
-  KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX
-  e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO
-  lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY
-  MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21
-  MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy
-  WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d
-  6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ
-  HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs
-  HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw
-  ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa
-  KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9
-  iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8
-  Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5
-  z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33
-  yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4
-  NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/
-  BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3
-  evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP
-  4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8
-  nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+
-  RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi
-  JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0
-  xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA
-  GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS
-  P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw
-  WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+
-  6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6
-  1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf
-  rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c
-  VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z
-  nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m
-  PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3
-  En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4
-  wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7
-  3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP
-  7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3
-  wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G
-  00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE
-  rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg
-  B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA
-  6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw
-  cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb
-  juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r
-  PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t
-  7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr
-  nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD
-  aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq
-  /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg
-  C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA
-  iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F
-  h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb
-  d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC
-  UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk
-  XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR
-  79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF
-  jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA
-  MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA
-  Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA
-  +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W
-  qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE
-  DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM
-  jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR
-  jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI
-  do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze
-  MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S
-  KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn
-  cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ
-  JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz
-  R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR
-  kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd
-  0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb
-  zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/
-  Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf
-  Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa
-  AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht
-  X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp
-  UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO
-  3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK
-  QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH
-  HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/
-  McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka
-  6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi
-  Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy
-  MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u
-  1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up
-  YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH
-  0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB
-  159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA
-  7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG
-  0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm
-  gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS
-  24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l
-  GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd
-  g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34
-  x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9
-  8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I
-  NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ
-  GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe
-  DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey
-  jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN
-  VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP
-  uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU
-  6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9
-  jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt
-  XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0
-  /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr
-  qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM
-  4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM
-  XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw
-  NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx
-  2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X
-  2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU
-  65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn
-  h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+
-  OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd
-  xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh
-  aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw
-  o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH
-  1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP
-  O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb
-  lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ
-  dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy
-  7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi
-  anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2
-  Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y
-  ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ
-  LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8
-  g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld
-  x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar
-  u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV
-  RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe
-  3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz
-  xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg
-  eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ
-  fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6
-  XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2
-  ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF
-  c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K
-  iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU
-  CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c
-  54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc
-  ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c
-  OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4
-  AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8
-  zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn
-  Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4
-  eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9
-  cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW
-  KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21
-  1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi
-  qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ
-  q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N
-  ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG
-  CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e
-  lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt
-  MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6
-  qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh
-  h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv
-  S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL
-  KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w
-  dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z
-  mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb
-  AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww
-  eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC
-  L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm
-  xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C
-  KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG
-  OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY
-  gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7
-  qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP
-  mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA
-  zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR
-  mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg
-  pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF
-  +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu
-  mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND
-  bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V
-  2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE
-  9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9
-  QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4
-  QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki
-  RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP
-  xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW
-  ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA
-  bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml
-  jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk
-  1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub
-  c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr
-  co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI
-  gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI
-  iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG
-  WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw
-  tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG
-  7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC
-  SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1
-  R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b
-  AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG
-  31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx
-  obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy
-  Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA
-  GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr
-  csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg
-  0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx
-  bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1
-  oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71
-  LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j
-  TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP
-  HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX
-  bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x
-  0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl
-  PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC
-  s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT
-  LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc
-  FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09
-  9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW
-  56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw
-  2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH
-  wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj
-  pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I
-  /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW
-  UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5
-  vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ
-  bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm
-  AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5
-  7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW
-  DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX
-  TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p
-  wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws
-  HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6
-  VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt
-  6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH
-  X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ
-  7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8
-  QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P
-  BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG
-  R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6
-  zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe
-  poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD
-  4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D
-  N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG
-  XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t
-  yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK
-  yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb
-  qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44
-  5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX
-  +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA
-  5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC
-  CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye
-  3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w
-  EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg
-  CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68
-  d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE
-  bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC
-  UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH
-  qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF
-  pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H
-  G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX
-  cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/
-  AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw
-  aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG
-  W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa
-  fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw
-  vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p
-  V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma
-  IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw
-  EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G
-  9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2
-  Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6
-  ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+
-  U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH
-  14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr
-  bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt
-  0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw
-  zbVbk4/OrNpefLsnyyg5UUAf/9k=\r
-END:VCARD\r
index 8dcae248b6f7680d1cbf92a6054216e8267a4afd..c3be7366cd74fbf86f39599b721c20cbbef27b24 100644 (file)
@@ -4,9 +4,9 @@ define('SABRE_MYSQLDSN','mysql:host=127.0.0.1;dbname=sabredav');
 define('SABRE_MYSQLUSER','root');
 define('SABRE_MYSQLPASS','');
 
-set_include_path(dirname(__FILE__) . PATH_SEPARATOR . dirname(__FILE__) . '/../lib/' . PATH_SEPARATOR . get_include_path());
+set_include_path(__DIR__ . '/../lib/' . PATH_SEPARATOR . __DIR__ . PATH_SEPARATOR . get_include_path());
 
-include 'Sabre/autoload.php';
+include __DIR__ . '/../vendor/autoload.php';
 include 'Sabre/DAVServerTest.php';
 
 date_default_timezone_set('GMT');
diff --git a/dav/calendar.friendica.fnk.php b/dav/calendar.friendica.fnk.php
deleted file mode 100644 (file)
index af4a017..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-
-$a = get_app();
-$uri = parse_url($a->get_baseurl());
-$path = "/";
-if (strlen($uri["path"]) > 1) {
-       $path = $uri["path"] . "/";
-}
-
-define("CALDAV_SQL_DB", "");
-define("CALDAV_SQL_PREFIX", "dav_");
-define("CALDAV_URL_PREFIX", $path . "dav/");
-
-define("CALDAV_NAMESPACE_PRIVATE", 1);
-define("CALDAV_NAMESPACE_FRIENDICA_NATIVE", 2);
-
-define("CALDAV_FRIENDICA_MINE", 1);
-define("CALDAV_FRIENDICA_CONTACTS", 2);
-
-define("CARDDAV_NAMESPACE_COMMUNITYCONTACTS", 1);
-define("CARDDAV_NAMESPACE_PHONECONTACTS", 2);
-
-define("CALDAV_DB_VERSION", 1);
-
-/**
- * @return int
- */
-function getCurMicrotime () {
-        list($usec, $sec) = explode(" ", microtime());
-        return sprintf("%14.0f", $sec * 10000 + $usec * 10000);
-} // function getCurMicrotime
-
-/**
- *
- */
-function debug_time() {
-        $cur = getCurMicrotime();
-        if ($GLOBALS["debug_time_last"] > 0) {
-                echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "<br>\n";
-        }
-        $GLOBALS["debug_time_last"] = $cur;
-}
-
-
-/**
- * @param string $username
- * @return int|null
- */
-function dav_compat_username2id($username = "")
-{
-       $x = q("SELECT `uid` FROM user WHERE nickname='%s' AND account_removed = 0 AND account_expired = 0", dbesc($username));
-       if (count($x) == 1) return $x[0]["uid"];
-       return null;
-}
-
-/**
- * @param int $id
- * @return string
- */
-function dav_compat_id2username($id = 0)
-{
-       $x = q("SELECT `nickname` FROM user WHERE uid = %i AND account_removed = 0 AND account_expired = 0", IntVal($id));
-       if (count($x) == 1) return $x[0]["nickname"];
-       return "";
-}
-
-/**
- * @return int
- */
-function dav_compat_get_curr_user_id() {
-       $a = get_app();
-       return IntVal($a->user["uid"]);
-}
-
-
-/**
- * @param string $principalUri
- * @return int|null
- */
-function dav_compat_principal2uid($principalUri = "")
-{
-       if (strlen($principalUri) == 0) return null;
-       if ($principalUri[0] == "/") $principalUri = substr($principalUri, 1);
-       if (strpos($principalUri, "principals/users/") !== 0) return null;
-       $username = substr($principalUri, strlen("principals/users/"));
-       return dav_compat_username2id($username);
-}
-
-
-/**
- * @param string $name
- * @return null|string
- */
-function dav_compat_getRequestVar($name = "") {
-       if (x($_REQUEST, $name)) return $_REQUEST[$name];
-       else return null;
-}
-
-/**
- * @param $text
- * @return null|string
- */
-function dav_compat_parse_text_serverside($text)
-{
-       return dav_compat_getRequestVar($text);
-}
-
-/**
- * @param string $uri
- */
-function dav_compat_redirect($uri = "") {
-       goaway($uri);
-}
-
-/**
- * @param int $user_id
- * @param int $namespace
- * @param int $namespace_id
- * @return AnimexxCalSource
- * @throws Exception
- */
-function wdcal_calendar_factory($user_id, $namespace, $namespace_id)
-{
-       switch ($namespace) {
-               case CALDAV_NAMESPACE_PRIVATE:
-                       return new AnimexxCalSourcePrivate($user_id, $namespace_id);
-               case CALDAV_NAMESPACE_FRIENDICA_NATIVE:
-                       return new FriendicaCalSourceEvents($user_id, $namespace_id);
-       }
-       throw new Exception("Calendar Namespace not found");
-}
-
-
-/**
- */
-function wdcal_create_std_calendars()
-{
-       $a = get_app();
-       if (!local_user()) return;
-
-       $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_PRIVATE);
-       if (count($cals) == 0) {
-               $maxid = q("SELECT MAX(`namespace_id`) maxid FROM %s%scalendars WHERE `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE);
-               if (!$maxid) {
-                       notification("Something went wrong when trying to create your calendar.");
-                       goaway("/");
-                       killme();
-               }
-               $nextid = IntVal($maxid[0]["maxid"]) + 1;
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $nextid, $a->user["uid"], dbesc(t("Private Calendar")), dbesc($a->timezone)
-               );
-       }
-
-       $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_FRIENDICA_NATIVE);
-       if (count($cals) < 2) {
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_MINE, $a->user["uid"], dbesc(t("Friendica Events: Mine")), dbesc($a->timezone)
-               );
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_CONTACTS, $a->user["uid"], dbesc(t("Friendica Events: Contacts")), dbesc($a->timezone)
-               );
-       }
-}
index 38975f8ed281a6f666554e114e289e87f26e8a6b..8b5aad4653608d7c36330be5bc9e2ea51cf815b2 100644 (file)
@@ -6,8 +6,8 @@
 <title>Really Simple Color Picker</title>
 
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
-<script language="javascript" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
-<script language="javascript" type="text/javascript" src="jquery.colorPicker.min.js"/></script>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+<script type="text/javascript" src="jquery.colorPicker.min.js"></script>
 
 <script type="text/javascript">
   //Run the code when document ready
index 410c60dd2a7ec1535b2fc0f51040fdf3e7202039..11f31f7de4e7f3792dec1def9348e12678a89319 100644 (file)
@@ -1,6 +1,17 @@
 <?php
 
 
+define("DAV_ACL_READ", "{DAV:}read");
+define("DAV_ACL_WRITE", "{DAV:}write");
+define("DAV_DISPLAYNAME", "{DAV:}displayname");
+define("DAV_CALENDARCOLOR", "{http://apple.com/ns/ical/}calendar-color");
+
+
+class DAVVersionMismatchException extends Exception
+{
+}
+
+
 class vcard_source_data_email
 {
        public $email, $type;
@@ -83,16 +94,13 @@ class vcard_source_data
        /** @var array|vcard_source_data_email[] $email */
        public $emails;
 
-       /** @var array|vcard_source_data_addresses[] $addresses */
+       /** @var array|vcard_source_data_address[] $addresses */
        public $addresses;
 
        /** @var vcard_source_data_photo */
        public $photo;
 }
 
-;
-
-
 /**
  * @param vcard_source_data $vcardsource
  * @return string
@@ -136,41 +144,6 @@ function vcard_source_compile($vcardsource)
 }
 
 
-/**
- * @param array $start
- * @param array $end
- * @param bool $allday
- * @return vevent
- */
-function dav_create_vevent($start, $end, $allday)
-{
-       if ($end["year"] < $start["year"] ||
-               ($end["year"] == $start["year"] && $end["month"] < $start["month"]) ||
-               ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] < $start["day"]) ||
-               ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] < $start["hour"]) ||
-               ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] < $start["minute"]) ||
-               ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] == $start["minute"] && $end["second"] < $start["second"])
-       ) {
-               $end = $start;
-       } // DTEND muss <= DTSTART
-
-       $vevent = new vevent();
-       if ($allday) {
-               $vevent->setDtstart($start["year"], $start["month"], $start["day"], FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
-               $end = IntVal(mktime(0, 0, 0, $end["month"], $end["day"], $end["year"]) + 3600 * 24);
-
-               // If a DST change occurs on the current day
-               $end += IntVal(date("Z", ($end - 3600 * 24)) - date("Z", $end));
-
-               $vevent->setDtend(date("Y", $end), date("m", $end), date("d", $end), FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
-       } else {
-               $vevent->setDtstart($start["year"], $start["month"], $start["day"], $start["hour"], $start["minute"], $start["second"], FALSE, array("VALUE"=> "DATE-TIME"));
-               $vevent->setDtend($end["year"], $end["month"], $end["day"], $end["hour"], $end["minute"], $end["second"], FALSE, array("VALUE"=> "DATE-TIME"));
-       }
-       return $vevent;
-}
-
-
 /**
  * @param int $phpDate (UTC)
  * @return string (Lokalzeit)
@@ -213,460 +186,190 @@ function wdcal_mySql2icalTime($myqlDate)
  */
 function icalendar_sanitize_string($str = "")
 {
-       $str = str_replace("\r\n", "\n", $str);
-       $str = str_replace("\n\r", "\n", $str);
-       $str = str_replace("\r", "\n", $str);
-       return $str;
+       return preg_replace("/[\\r\\n]+/siu", "\r\n", $str);
 }
 
 
 /**
- * @param DBClass_friendica_calendars $calendar
- * @param DBClass_friendica_calendarobjects $calendarobject
+ * @return Sabre_CalDAV_AnimexxCalendarRootNode
  */
-function renderCalDavEntry_data(&$calendar, &$calendarobject)
+function dav_createRootCalendarNode()
 {
-       $a = get_app();
-
-       $v = new vcalendar();
-       $v->setConfig('unique_id', $a->get_hostname());
-       $v->parse($calendarobject->calendardata);
-       $v->sort();
-
-       $eventArray = $v->selectComponents(2009, 1, 1, date("Y") + 2, 12, 30);
-
-       $start_min = $end_max = "";
-
-       $allday   = $summary = $vevent = $rrule = $color = $start = $end = null;
-       $location = $description = "";
-
-       foreach ($eventArray as $yearArray) {
-               foreach ($yearArray as $monthArray) {
-                       foreach ($monthArray as $day => $dailyEventsArray) {
-                               foreach ($dailyEventsArray as $vevent) {
-                                       /** @var $vevent vevent  */
-                                       $start  = "";
-                                       $rrule  = "NULL";
-                                       $allday = 0;
-
-                                       $dtstart = $vevent->getProperty('X-CURRENT-DTSTART');
-                                       if (is_array($dtstart)) {
-                                               $start = "'" . $dtstart[1] . "'";
-                                               if (strpos($dtstart[1], ":") === false) $allday = 1;
-                                       } else {
-                                               $dtstart = $vevent->getProperty('dtstart');
-                                               if (isset($dtstart["day"]) && $dtstart["day"] == $day) { // Mehrtägige Events nur einmal rein
-                                                       if (isset($dtstart["hour"])) $start = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " " . $dtstart["hour"] . ":" . $dtstart["minute"] . ":" . $dtstart["secont"] . "'";
-                                                       else {
-                                                               $start  = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " 00:00:00'";
-                                                               $allday = 1;
-                                                       }
-                                               }
-                                       }
-
-                                       $dtend = $vevent->getProperty('X-CURRENT-DTEND');
-                                       if (is_array($dtend)) {
-                                               $end = "'" . $dtend[1] . "'";
-                                               if (strpos($dtend[1], ":") === false) $allday = 1;
-                                       } else {
-                                               $dtend = $vevent->getProperty('dtend');
-                                               if (isset($dtend["hour"])) $end = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " " . $dtend["hour"] . ":" . $dtend["minute"] . ":" . $dtend["second"] . "'";
-                                               else {
-                                                       $end    = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " 00:00:00' - INTERVAL 1 SECOND";
-                                                       $allday = 1;
-                                               }
-                                       }
-                                       $summary     = $vevent->getProperty('summary');
-                                       $description = $vevent->getProperty('description');
-                                       $location    = $vevent->getProperty('location');
-                                       $rrule_prob  = $vevent->getProperty('rrule');
-                                       if ($rrule_prob != null) {
-                                               $rrule = $vevent->createRrule();
-                                               $rrule = "'" . dbesc($rrule) . "'";
-                                       }
-                                       $color_ = $vevent->getProperty("X-ANIMEXX-COLOR");
-                                       $color  = (is_array($color_) ? $color_[1] : "NULL");
-
-                                       if ($start_min == "" || preg_replace("/[^0-9]/", "", $start) < preg_replace("/[^0-9]/", "", $start_min)) $start_min = $start;
-                                       if ($end_max == "" || preg_replace("/[^0-9]/", "", $end) > preg_replace("/[^0-9]/", "", $start_min)) $end_max = $end;
-                               }
-                       }
-               }
-       }
-
-       if ($start_min != "") {
-
-               if ($allday && mb_strlen($end_max) == 12) {
-                       $x       = explode("-", str_replace("'", "", $end_max));
-                       $time    = mktime(0, 0, 0, IntVal($x[1]), IntVal($x[2]), IntVal($x[0]));
-                       $end_max = date("'Y-m-d H:i:s'", ($time - 1));
-               }
-
-               q("INSERT INTO %s%sjqcalendar (`uid`, `namespace`, `namespace_id`, `ical_uri`, `Subject`, `Location`, `Description`, `StartTime`, `EndTime`, `IsAllDayEvent`, `RecurringRule`, `Color`)
-                       VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', %s, %s, %d, '%s', '%s')",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                       IntVal($calendar->uid), IntVal($calendarobject->namespace), IntVal($calendarobject->namespace_id), dbesc($calendarobject->uri), dbesc($summary),
-                       dbesc($location), dbesc(str_replace("\\n", "\n", $description)), $start_min, $end_max, IntVal($allday), dbesc($rrule), dbesc($color)
-               );
-
-               foreach ($vevent->components as $comp) {
-                       /** @var $comp calendarComponent */
-                       $trigger   = $comp->getProperty("TRIGGER");
-                       $sql_field = ($trigger["relatedStart"] ? $start : $end);
-                       $sql_op    = ($trigger["before"] ? "DATE_SUB" : "DATE_ADD");
-                       $num       = "";
-                       $rel_type  = "";
-                       $rel_value = 0;
-                       if (isset($trigger["second"])) {
-                               $num       = IntVal($trigger["second"]) . " SECOND";
-                               $rel_type  = "second";
-                               $rel_value = IntVal($trigger["second"]);
-                       }
-                       if (isset($trigger["minute"])) {
-                               $num       = IntVal($trigger["minute"]) . " MINUTE";
-                               $rel_type  = "minute";
-                               $rel_value = IntVal($trigger["minute"]);
-                       }
-                       if (isset($trigger["hour"])) {
-                               $num       = IntVal($trigger["hour"]) . " HOUR";
-                               $rel_type  = "hour";
-                               $rel_value = IntVal($trigger["hour"]);
-                       }
-                       if (isset($trigger["day"])) {
-                               $num       = IntVal($trigger["day"]) . " DAY";
-                               $rel_type  = "day";
-                               $rel_value = IntVal($trigger["day"]);
-                       }
-                       if (isset($trigger["week"])) {
-                               $num       = IntVal($trigger["week"]) . " WEEK";
-                               $rel_type  = "week";
-                               $rel_value = IntVal($trigger["week"]);
-                       }
-                       if (isset($trigger["month"])) {
-                               $num       = IntVal($trigger["month"]) . " MONTH";
-                               $rel_type  = "month";
-                               $rel_value = IntVal($trigger["month"]);
-                       }
-                       if (isset($trigger["year"])) {
-                               $num       = IntVal($trigger["year"]) . " YEAR";
-                               $rel_type  = "year";
-                               $rel_value = IntVal($trigger["year"]);
-                       }
-                       if ($trigger["before"]) $rel_value *= -1;
-
-                       if ($rel_type != "") {
-                               $not_date = "$sql_op($sql_field, INTERVAL $num)";
-                               q("INSERT INTO %s%snotifications (`uid`, `ical_uri`, `rel_type`, `rel_value`, `alert_date`, `notified`) VALUES ('%s', '%s', '%s', '%s', %s, IF(%s < NOW(), 1, 0))",
-                                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                                       IntVal($calendar->uid), dbesc($calendarobject->uri), dbesc($rel_type), IntVal($rel_value), $not_date, $not_date);
-                       }
-               }
-       }
+       $backends = array(Sabre_CalDAV_Backend_Private::getInstance());
+       foreach ($GLOBALS["CALDAV_PRIVATE_SYSTEM_BACKENDS"] as $backendclass) $backends[] = $backendclass::getInstance();
+       return new Sabre_CalDAV_AnimexxCalendarRootNode(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), $backends);
 }
 
-
 /**
- *
+ * @return Sabre_CardDAV_AddressBookRootFriendica
  */
-function renderAllCalDavEntries()
+function dav_createRootContactsNode()
 {
-       q("DELETE FROM %s%sjqcalendar", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
-       q("DELETE FROM %s%snotifications", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
-       $calendars = q("SELECT * FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
-       $anz       = count($calendars);
-       $i         = 0;
-       foreach ($calendars as $calendar) {
-               $cal = new DBClass_friendica_calendars($calendar);
-               $i++;
-               if (($i % 100) == 0) echo "$i / $anz\n";
-               $calobjs = q("SELECT * FROM %s%scalendarobjects WHERE `namespace` = %d AND `namespace_id` = %d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["namespace"]), IntVal($calendar["namespace_id"]));
-               foreach ($calobjs as $calobj) {
-                       $obj = new DBClass_friendica_calendarobjects($calobj);
-                       renderCalDavEntry_data($cal, $obj);
-               }
-       }
-}
-
+       $backends = array(Sabre_CardDAV_Backend_Std::getInstance());
+       foreach ($GLOBALS["CARDDAV_PRIVATE_SYSTEM_BACKENDS"] as $backendclass) $backends[] = $backendclass::getInstance();
 
-/**
- * @param string $uri
- * @return bool
- */
-function renderCalDavEntry_uri($uri)
-{
-       q("DELETE FROM %s%sjqcalendar WHERE `ical_uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
-       q("DELETE FROM %s%snotifications WHERE `ical_uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
-
-       $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
-       if (count($calobj) == 0) return false;
-       $cal       = new DBClass_friendica_calendarobjects($calobj[0]);
-       $calendars = q("SELECT * FROM %s%scalendars WHERE `namespace`=%d AND `namespace_id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($cal->namespace), IntVal($cal->namespace_id));
-       $calendar  = new DBClass_friendica_calendars($calendars[0]);
-       renderCalDavEntry_data($calendar, $cal);
-       return true;
+       return new Sabre_CardDAV_AddressBookRootFriendica(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), $backends);
 }
 
 
 /**
- * @param $user_id
- * @return array|DBClass_friendica_calendars[]
+ * @param bool $force_authentication
+ * @param bool $needs_caldav
+ * @param bool $needs_carddav
+ * @return Sabre_DAV_Server
  */
-function dav_getMyCals($user_id)
+function dav_create_server($force_authentication = false, $needs_caldav = true, $needs_carddav = true)
 {
-       $d    = q("SELECT * FROM %s%scalendars WHERE `uid` = %d ORDER BY `calendarorder` ASC",
-               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($user_id), CALDAV_NAMESPACE_PRIVATE
+       $arr = array(
+               new Sabre_DAV_SimpleCollection('principals', array(
+                       new Sabre_CalDAV_Principal_Collection(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), "principals/users"),
+               )),
        );
-       $cals = array();
-       foreach ($d as $e) $cals[] = new DBClass_friendica_calendars($e);
-       return $cals;
+       if ($needs_caldav) $arr[] = dav_createRootCalendarNode();
+       if ($needs_carddav) $arr[] = dav_createRootContactsNode();
+
+
+       $tree = new Sabre_DAV_SimpleCollection('root', $arr);
+
+// The object tree needs in turn to be passed to the server class
+       $server = new Sabre_DAV_Server($tree);
+
+       if (CALDAV_URL_PREFIX != "") $server->setBaseUri(CALDAV_URL_PREFIX);
+
+       $authPlugin = new Sabre_DAV_Auth_Plugin(Sabre_DAV_Auth_Backend_Std::getInstance(), DAV_APPNAME);
+       $server->addPlugin($authPlugin);
+
+       if ($needs_caldav) {
+               $caldavPlugin = new Sabre_CalDAV_Plugin();
+               $server->addPlugin($caldavPlugin);
+       }
+       if ($needs_carddav) {
+               $carddavPlugin = new Sabre_CardDAV_Plugin();
+               $server->addPlugin($carddavPlugin);
+       }
+
+       if ($GLOBALS["CALDAV_ACL_PLUGIN_CLASS"] != "") {
+               $aclPlugin                      = new $GLOBALS["CALDAV_ACL_PLUGIN_CLASS"]();
+               $aclPlugin->defaultUsernamePath = "principals/users";
+               $server->addPlugin($aclPlugin);
+       } else {
+               $aclPlugin                      = new Sabre_DAVACL_Plugin();
+               $aclPlugin->defaultUsernamePath = "principals/users";
+               $server->addPlugin($aclPlugin);
+       }
+
+       if ($force_authentication) $server->broadcastEvent('beforeMethod', array("GET", "/")); // Make it authenticate
+
+       return $server;
 }
 
 
 /**
- * @param mixed $obj
- * @return string
+ * @param Sabre_DAV_Server $server
+ * @param string $with_privilege
+ * @return array|Sabre_CalDAV_Calendar[]
  */
-function wdcal_jsonp_encode($obj)
+function dav_get_current_user_calendars(&$server, $with_privilege = "")
 {
-       $str = json_encode($obj);
-       if (isset($_REQUEST["callback"])) {
-               $str = $_REQUEST["callback"] . "(" . $str . ")";
+       if ($with_privilege == "") $with_privilege = DAV_ACL_READ;
+
+       $a             = get_app();
+       $calendar_path = "/calendars/" . strtolower($a->user["nickname"]) . "/";
+
+       /** @var Sabre_CalDAV_AnimexxUserCalendars $tree  */
+       $tree = $server->tree->getNodeForPath($calendar_path);
+       /** @var array|Sabre_CalDAV_Calendar[] $calendars  */
+       $children = $tree->getChildren();
+
+       $calendars = array();
+       /** @var Sabre_DAVACL_Plugin $aclplugin  */
+       $aclplugin = $server->getPlugin("acl");
+       foreach ($children as $child) if (is_a($child, "Sabre_CalDAV_Calendar") || is_subclass_of($child, "Sabre_CalDAV_Calendar")) {
+               if ($with_privilege != "") {
+                       $caluri = $calendar_path . $child->getName();
+                       if ($aclplugin->checkPrivileges($caluri, $with_privilege, Sabre_DAVACL_Plugin::R_PARENT, false)) $calendars[] = $child;
+               } else {
+                       $calendars[] = $child;
+               }
        }
-       return $str;
+       return $calendars;
 }
 
 
 /**
- * @param string $day
- * @param int $weekstartday
- * @param int $num_days
- * @param string $type
- * @return array
+ * @param Sabre_DAV_Server $server
+ * @param Sabre_CalDAV_Calendar $calendar
+ * @param string $calendarobject_uri
+ * @param string $with_privilege
+ * @return null|Sabre\VObject\Component\VCalendar
  */
-function wdcal_get_list_range_params($day, $weekstartday, $num_days, $type)
+function dav_get_current_user_calendarobject(&$server, &$calendar, $calendarobject_uri, $with_privilege = "")
 {
-       $phpTime = IntVal($day);
-       switch ($type) {
-               case "month":
-                       $st = mktime(0, 0, 0, date("m", $phpTime), 1, date("Y", $phpTime));
-                       $et = mktime(0, 0, -1, date("m", $phpTime) + 1, 1, date("Y", $phpTime));
-                       break;
-               case "week":
-                       //suppose first day of a week is monday
-                       $monday = date("d", $phpTime) - date('N', $phpTime) + 1;
-                       //echo date('N', $phpTime);
-                       $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
-                       $et = mktime(0, 0, -1, date("m", $phpTime), $monday + 7, date("Y", $phpTime));
-                       break;
-               case "multi_days":
-                       //suppose first day of a week is monday
-                       $monday = date("d", $phpTime) - date('N', $phpTime) + $weekstartday;
-                       //echo date('N', $phpTime);
-                       $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
-                       $et = mktime(0, 0, -1, date("m", $phpTime), $monday + $num_days, date("Y", $phpTime));
-                       break;
-               case "day":
-                       $st = mktime(0, 0, 0, date("m", $phpTime), date("d", $phpTime), date("Y", $phpTime));
-                       $et = mktime(0, 0, -1, date("m", $phpTime), date("d", $phpTime) + 1, date("Y", $phpTime));
-                       break;
-               default:
-                       return array(0, 0);
-       }
-       return array($st, $et);
-}
+       $obj = $calendar->getChild($calendarobject_uri);
+
+       if ($with_privilege == "") $with_privilege = DAV_ACL_READ;
+
+       $a   = get_app();
+       $uri = "/calendars/" . strtolower($a->user["nickname"]) . "/" . $calendar->getName() . "/" . $calendarobject_uri;
 
+       /** @var Sabre_DAVACL_Plugin $aclplugin  */
+       $aclplugin = $server->getPlugin("acl");
+       if (!$aclplugin->checkPrivileges($uri, $with_privilege, Sabre_DAVACL_Plugin::R_PARENT, false)) return null;
 
+       $data    = $obj->get();
+       $vObject = Sabre\VObject\Reader::read($data);
 
+       return $vObject;
+}
 
 
 /**
- * @param string $uri
- * @param string $recurr_uri
- * @param int $uid
- * @param string $timezone
- * @param string $goaway_url
- * @return string
+ * @param Sabre_DAV_Server $server
+ * @param int $id
+ * @param string $with_privilege
+ * @return null|Sabre_CalDAV_Calendar
  */
-function wdcal_postEditPage($uri, $recurr_uri = "", $uid = 0, $timezone = "", $goaway_url = "")
+function dav_get_current_user_calendar_by_id(&$server, $id, $with_privilege = "")
 {
-       $uid = IntVal($uid);
-       $localization = wdcal_local::getInstanceByUser($uid);
+       $calendars = dav_get_current_user_calendars($server, $with_privilege);
 
-       if (isset($_REQUEST["allday"])) {
-               $start    = $localization->date_parseLocal($_REQUEST["start_date"] . " 00:00");
-               $end      = $localization->date_parseLocal($_REQUEST["end_date"] . " 20:00");
-               $isallday = true;
-       } else {
-               $start    = $localization->date_parseLocal($_REQUEST["start_date"] . " " . $_REQUEST["start_time"]);
-               $end      = $localization->date_parseLocal($_REQUEST["end_date"] . " " . $_REQUEST["end_time"]);
-               $isallday = false;
+       $calendar = null;
+       foreach ($calendars as $cal) {
+               $prop = $cal->getProperties(array("id"));
+               if (isset($prop["id"]) && $prop["id"] == $id) $calendar = $cal;
        }
 
-       if ($uri == "new") {
-               $cals = dav_getMyCals($uid);
-               foreach ($cals as $c) {
-                       $cs = wdcal_calendar_factory($uid, $c->namespace, $c->namespace_id);
-                       $p = $cs->getPermissionsCalendar($uid);
-
-                       if ($p["write"]) try {
-                               $cs->addItem($start, $end, dav_compat_getRequestVar("subject"), $isallday, dav_compat_parse_text_serverside("wdcal_desc"),
-                                       dav_compat_getRequestVar("location"), dav_compat_getRequestVar("color"), $timezone,
-                                       isset($_REQUEST["notification"]), $_REQUEST["notification_type"], $_REQUEST["notification_value"]);
-                       } catch (Exception $e) {
-                               notification(t("Error") . ": " . $e);
-                       }
-                       dav_compat_redirect($goaway_url);
-               }
-
-       } else {
-               $cals = dav_getMyCals($uid);
-               foreach ($cals as $c) {
-                       $cs = wdcal_calendar_factory($uid, $c->namespace, $c->namespace_id);
-                       $p  = $cs->getPermissionsItem($uid, $uri, $recurr_uri);
-                       if ($p["write"]) try {
-                               $cs->updateItem($uri, $start, $end,
-                                       dav_compat_getRequestVar("subject"), $isallday, dav_compat_parse_text_serverside("wdcal_desc"),
-                                       dav_compat_getRequestVar("location"), dav_compat_getRequestVar("color"), $timezone,
-                                       isset($_REQUEST["notification"]), $_REQUEST["notification_type"], $_REQUEST["notification_value"]);
-                       } catch (Exception $e) {
-                               notification(t("Error") . ": " . $e);
-                       }
-                       dav_compat_redirect($goaway_url);
-               }
-       }
+       return $calendar;
 }
 
 
 /**
- *
+ * @param string $uid
+ * @return Sabre\VObject\Component\VCalendar $vObject
  */
-function wdcal_print_feed($base_path = "")
+function dav_create_empty_vevent($uid = "")
 {
-       $user_id = dav_compat_get_curr_user_id();
-       $cals    = array();
-       if (isset($_REQUEST["cal"])) foreach ($_REQUEST["cal"] as $c) {
-               $x              = explode("-", $c);
-               $calendarSource = wdcal_calendar_factory($user_id, $x[0], $x[1]);
-               $calp           = $calendarSource->getPermissionsCalendar($user_id);
-               if ($calp["read"]) $cals[] = $calendarSource;
-       }
+       if ($uid == "") $uid = uniqid();
+       return Sabre\VObject\Reader::read("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//" . DAV_APPNAME . "//DAV-Plugin//EN\r\nBEGIN:VEVENT\r\nUID:" . $uid . "@" . dav_compat_get_hostname() .
+               "\r\nDTSTAMP:" . date("Ymd") . "T" . date("His") . "Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
+}
 
-       $ret = null;
-       /** @var $cals array|AnimexxCalSource[] */
-
-       $method = $_GET["method"];
-       switch ($method) {
-               case "add":
-                       $cs = null;
-                       foreach ($cals as $c) if ($cs == null) {
-                               $x = $c->getPermissionsCalendar($user_id);
-                               if ($x["read"]) $cs = $c;
-                       }
-                       if ($cs == null) {
-                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
-                                                                                         'Msg'       => t('No access')));
-                               killme();
-                       }
-                       try {
-                               $start  = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarStartTime"]));
-                               $end    = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarEndTime"]));
-                               $newuri = $cs->addItem($start, $end, $_REQUEST["CalendarTitle"], $_REQUEST["IsAllDayEvent"]);
-                               $ret    = array(
-                                       'IsSuccess' => true,
-                                       'Msg'       => 'add success',
-                                       'Data'      => $newuri,
-                               );
-
-                       } catch (Exception $e) {
-                               $ret = array(
-                                       'IsSuccess' => false,
-                                       'Msg'       => $e->__toString(),
-                               );
-                       }
-                       break;
-               case "list":
-                       $weekstartday = (isset($_REQUEST["weekstartday"]) ? IntVal($_REQUEST["weekstartday"]) : 1); // 1 = Monday
-                       $num_days     = (isset($_REQUEST["num_days"]) ? IntVal($_REQUEST["num_days"]) : 7);
-                       $ret          = null;
-
-                       $date          = wdcal_get_list_range_params($_REQUEST["showdate"], $weekstartday, $num_days, $_REQUEST["viewtype"]);
-                       $ret           = array();
-                       $ret['events'] = array();
-                       $ret["issort"] = true;
-                       $ret["start"]  = $date[0];
-                       $ret["end"]    = $date[1];
-                       $ret['error']  = null;
-
-                       foreach ($cals as $c) {
-                               $events        = $c->listItemsByRange($date[0], $date[1], $base_path);
-                               $ret["events"] = array_merge($ret["events"], $events);
-                       }
-
-                       $tmpev = array();
-                       foreach ($ret["events"] as $e) {
-                               if (!isset($tmpev[$e["start"]])) $tmpev[$e["start"]] = array();
-                               $tmpev[$e["start"]][] = $e;
-                       }
-                       ksort($tmpev);
-                       $ret["events"] = array();
-                       foreach ($tmpev as $e) foreach ($e as $f) $ret["events"][] = $f;
 
+/**
+ * @param Sabre\VObject\Component\VCalendar $vObject
+ * @return Sabre\VObject\Component\VEvent|null
+ */
+function dav_get_eventComponent(&$vObject)
+{
+       $component     = null;
+       $componentType = "";
+       foreach ($vObject->getComponents() as $component) {
+               if ($component->name !== 'VTIMEZONE') {
+                       $componentType = $component->name;
                        break;
-               case "update":
-                       $found = false;
-                       $start = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarStartTime"]));
-                       $end   = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarEndTime"]));
-                       foreach ($cals as $c) try {
-                               $permissions_item = $c->getPermissionsItem($user_id, $_REQUEST["calendarId"], "");
-                               if ($permissions_item["write"]) {
-                                       $c->updateItem($_REQUEST["calendarId"], $start, $end);
-                                       $found = true;
-                               }
-                       } catch (Exception $e) {
-                       }
-                       ;
-
-                       if ($found) {
-                               $ret = array(
-                                       'IsSuccess' => true,
-                                       'Msg'       => 'Succefully',
-                               );
-                       } else {
-                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
-                                                                                         'Msg'       => t('No access')));
-                               killme();
-                       }
-
-                       try {
-                       } catch (Exception $e) {
-                               $ret = array(
-                                       'IsSuccess' => false,
-                                       'Msg'       => $e->__toString(),
-                               );
-                       }
-                       break;
-               case "remove":
-                       $found = false;
-                       foreach ($cals as $c) try {
-                               $permissions_item = $c->getPermissionsItem($user_id, $_REQUEST["calendarId"], "");
-                               if ($permissions_item["write"]) $c->removeItem($_REQUEST["calendarId"]);
-                       } catch (Exception $e) {
-                       }
-
-                       if ($found) {
-                               $ret = array(
-                                       'IsSuccess' => true,
-                                       'Msg'       => 'Succefully',
-                               );
-                       } else {
-                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
-                                                                                         'Msg'       => t('No access')));
-                               killme();
-                       }
-                       break;
+               }
        }
-       echo wdcal_jsonp_encode($ret);
-       killme();
-}
+       if ($componentType != "VEVENT") return null;
 
+       return $component;
+}
diff --git a/dav/common/calendar_rendering.fnk.php b/dav/common/calendar_rendering.fnk.php
new file mode 100644 (file)
index 0000000..d80892f
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+
+
+
+/**
+ * @param Sabre\VObject\Component\VAlarm $alarm
+ * @param Sabre\VObject\Component\VEvent|Sabre\VObject\Component\VTodo $parent
+ * @return DateTime|null
+ * @throws Sabre_DAV_Exception
+ */
+function renderCalDavEntry_calcalarm(&$alarm, &$parent)
+{
+       $trigger = $alarm->__get("TRIGGER");
+       if (!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
+               $triggerDuration = Sabre\VObject\DateTimeParser::parseDuration($trigger->value);
+
+               $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
+
+               if ($related === 'START') {
+                       /** @var Sabre\VObject\Property\DateTime $dtstart  */
+                       $dtstart          = $parent->__get("DTSTART");
+                       $effectiveTrigger = $dtstart->getDateTime();
+                       $effectiveTrigger->add($triggerDuration);
+               } else {
+                       if ($parent->name === 'VTODO') {
+                               $endProp = 'DUE';
+                       } else {
+                               $endProp = 'DTEND';
+                       }
+
+                       /** @var Sabre\VObject\Property\DateTime $dtstart  */
+                       $dtstart = $parent->__get("DTSTART");
+                       if (isset($parent->$endProp)) {
+                               $effectiveTrigger = clone $parent->$endProp->getDateTime();
+                               $effectiveTrigger->add($triggerDuration);
+                       } elseif ($parent->__get("DURATION") != "") {
+                               $effectiveTrigger = clone $dtstart->getDateTime();
+                               $duration         = Sabre\VObject\DateTimeParser::parseDuration($parent->__get("DURATION"));
+                               $effectiveTrigger->add($duration);
+                               $effectiveTrigger->add($triggerDuration);
+                       } else {
+                               $effectiveTrigger = clone $dtstart->getDateTime();
+                               $effectiveTrigger->add($triggerDuration);
+                       }
+               }
+       } else {
+               // ??? @TODO
+               $effectiveTrigger = $trigger->getDateTime();
+       }
+       return $effectiveTrigger;
+}
+
+/**
+ * @param array $calendar
+ * @param array $calendarobject
+ * @throws Sabre_DAV_Exception_BadRequest
+ * @return void
+ */
+function renderCalDavEntry_data(&$calendar, &$calendarobject)
+{
+       /** @var Sabre\VObject\Component\VCalendar $vObject  */
+       $vObject       = Sabre\VObject\Reader::read($calendarobject["calendardata"]);
+       $componentType = null;
+       /** @var Sabre\VObject\Component\VEvent $component  */
+       $component = null;
+       foreach ($vObject->getComponents() as $component) {
+               if ($component->name !== 'VTIMEZONE') {
+                       $componentType = $component->name;
+                       break;
+               }
+       }
+       if (!$componentType) {
+               throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+       }
+
+       $timezoneOffset = date("P"); // @TODO Get the actual timezone from the event
+
+
+       if ($componentType !== 'VEVENT') return;
+
+       $event = array(
+               "description" => ($component->__get("DESCRIPTION") ? $component->__get("DESCRIPTION")->value : null),
+               "summary"     => ($component->__get("SUMMARY") ? $component->__get("SUMMARY")->value : null),
+               "location"    => ($component->__get("LOCATION") ? $component->__get("LOCATION")->value : null),
+               "color"       => ($component->__get("X-ANIMEXX-COLOR") ? $component->__get("X-ANIMEXX-COLOR")->value : null),
+       );
+
+       $recurring = ($component->__get("RRULE") ? 1 : 0);
+       /** @var Sabre\VObject\Property\DateTime $dtstart  */
+       $dtstart = $component->__get("DTSTART");
+       $allday  = ($dtstart->getDateType() == Sabre\VObject\Property\DateTime::DATE ? 1 : 0);
+
+       /** @var array|Sabre\VObject\Component\VAlarm[] $alarms  */
+       $alarms = array();
+       foreach ($component->getComponents() as $a_component) if ($a_component->name == "VALARM") {
+               /** var Sabre\VObject\Component\VAlarm $component */
+               $alarms[] = $a_component;
+       }
+
+       $it       = new Sabre\VObject\RecurrenceIterator($vObject, (string)$component->__get("UID"));
+       $last_end = 0;
+       $max_ts   = mktime(0, 0, 0, 1, 1, CALDAV_MAX_YEAR * 1);
+       $first    = true;
+
+       while ($it->valid() && $last_end < $max_ts && ($recurring || $first)) {
+               $first    = false;
+               $last_end = $it->getDtEnd()->getTimestamp();
+               $start    = $it->getDtStart()->getTimestamp();
+
+               q("INSERT INTO %s%sjqcalendar (`calendar_id`, `calendarobject_id`, `Summary`, `StartTime`, `EndTime`, `IsEditable`, `IsAllDayEvent`, `IsRecurring`, `Color`) VALUES
+                       (%d, %d, '%s', CONVERT_TZ('%s', '$timezoneOffset', @@session.time_zone), CONVERT_TZ('%s', '$timezoneOffset', @@session.time_zone), %d, %d, %d, '%s')",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["id"]), IntVal($calendarobject["id"]), dbesc($event["summary"]), date("Y-m-d H:i:s", $start),
+                       date("Y-m-d H:i:s", $last_end), 1, $allday, $recurring, dbesc(substr($event["color"], 1))
+               );
+
+               foreach ($alarms as $alarm) {
+                       $alarm    = renderCalDavEntry_calcalarm($alarm, $component);
+                       $notified = ($alarm->getTimestamp() < time() ? 1 : 0);
+                       q("INSERT INTO %s%snotifications (`calendar_id`, `calendarobject_id`, `alert_date`, `notified`) VALUES (%d, %d, CONVERT_TZ('%s', '$timezoneOffset', @@session.time_zone), %d)",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["id"]), IntVal($calendarobject["id"]), $alarm->format("Y-m-d H:i:s"), $notified
+                       );
+               }
+
+               $it->next();
+       }
+
+       return;
+
+}
+
+
+/**
+ *
+ */
+function renderAllCalDavEntries()
+{
+       q("DELETE FROM %s%sjqcalendar", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       q("DELETE FROM %s%snotifications", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       $calendars = q("SELECT * FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       $anz       = count($calendars);
+       $i         = 0;
+       foreach ($calendars as $calendar) {
+               $i++;
+               if (($i % 100) == 0) echo "$i / $anz\n";
+               $calobjs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["id"]));
+               foreach ($calobjs as $calobj) renderCalDavEntry_data($calendar, $calobj);
+       }
+}
+
+
+/**
+ * @param string $uri
+ * @return bool
+ */
+function renderCalDavEntry_uri($uri)
+{
+       $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
+       if (count($calobj) == 0) return false;
+
+       q("DELETE FROM %s%sjqcalendar WHERE `calendar_id` = %d AND `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]), IntVal($calobj[0]["id"]));
+       q("DELETE FROM %s%snotifications WHERE `calendar_id` = %d AND `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]), IntVal($calobj[0]["id"]));
+
+       $calendars = q("SELECT * FROM %s%scalendars WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]));
+
+       renderCalDavEntry_data($calendars[0], $calobj[0]);
+       return true;
+}
+
+
+/**
+ * @param int $calobj_id
+ * @return bool
+ */
+function renderCalDavEntry_calobj_id($calobj_id)
+{
+       $calobj_id = IntVal($calobj_id);
+       q("DELETE FROM %s%sjqcalendar WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+       q("DELETE FROM %s%snotifications WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+
+       $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `id` = '%d'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+       if (count($calobj) == 0) return false;
+
+       $calendars = q("SELECT * FROM %s%scalendars WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]));
+
+       renderCalDavEntry_data($calendars[0], $calobj[0]);
+       return true;
+}
diff --git a/dav/common/dav_caldav_backend.inc.php b/dav/common/dav_caldav_backend.inc.php
deleted file mode 100644 (file)
index c09d48d..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-<?php
-
-class Sabre_CalDAV_Backend_Std extends Sabre_CalDAV_Backend_Common
-{
-
-       public function getNamespace()
-       {
-               return CALDAV_NAMESPACE_PRIVATE;
-       }
-
-       public function getCalUrlPrefix()
-       {
-               return "private";
-       }
-
-       /**
-        * Creates a new calendar for a principal.
-        *
-        * If the creation was a success, an id must be returned that can be used to reference
-        * this calendar in other methods, such as updateCalendar.
-        *
-        * @param string $principalUri
-        * @param string $calendarUri
-        * @param array $properties
-        * @return void
-        */
-       public function createCalendar($principalUri, $calendarUri, array $properties)
-       {
-               // TODO: Implement createCalendar() method.
-       }
-
-       /**
-        * Delete a calendar and all it's objects
-        *
-        * @param string $calendarId
-        * @return void
-        */
-       public function deleteCalendar($calendarId)
-       {
-               // TODO: Implement deleteCalendar() method.
-       }
-
-
-       /**
-        * Returns all calendar objects within a calendar.
-        *
-        * Every item contains an array with the following keys:
-        *   * id - unique identifier which will be used for subsequent updates
-        *   * calendardata - The iCalendar-compatible calendar data
-        *   * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
-        *   * lastmodified - a timestamp of the last modification time
-        *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
-        *   '  "abcdef"')
-        *   * calendarid - The calendarid as it was passed to this function.
-        *   * size - The size of the calendar objects, in bytes.
-        *
-        * Note that the etag is optional, but it's highly encouraged to return for
-        * speed reasons.
-        *
-        * The calendardata is also optional. If it's not returned
-        * 'getCalendarObject' will be called later, which *is* expected to return
-        * calendardata.
-        *
-        * If neither etag or size are specified, the calendardata will be
-        * used/fetched to determine these numbers. If both are specified the
-        * amount of times this is needed is reduced by a great degree.
-        *
-        * @param string $calendarId
-        * @return array
-        */
-       function getCalendarObjects($calendarId)
-       {
-               $x    = explode("-", $calendarId);
-               $objs = q("SELECT * FROM %s%scalendarobjects WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]));
-               $ret  = array();
-               foreach ($objs as $obj) {
-                       $ret[] = array(
-                               "id"           => IntVal($obj["id"]),
-                               "calendardata" => $obj["calendardata"],
-                               "uri"          => $obj["uri"],
-                               "lastmodified" => $obj["lastmodified"],
-                               "calendarid"   => $calendarId,
-                               "etag"         => $obj["etag"],
-                               "size"         => IntVal($obj["size"]),
-                       );
-               }
-               return $ret;
-       }
-
-       /**
-        * Returns information from a single calendar object, based on it's object
-        * uri.
-        *
-        * The returned array must have the same keys as getCalendarObjects. The
-        * 'calendardata' object is required here though, while it's not required
-        * for getCalendarObjects.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @throws Sabre_DAV_Exception_FileNotFound
-        * @return array
-        */
-       function getCalendarObject($calendarId, $objectUri)
-       {
-               $x = explode("-", $calendarId);
-
-               $o = q("SELECT * FROM %s%scalendarobjects WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($objectUri));
-               if (count($o) > 0) {
-                       $o[0]["calendarid"]   = $calendarId;
-                       $o[0]["calendardata"] = str_ireplace("Europe/Belgrade", "Europe/Berlin", $o[0]["calendardata"]);
-                       return $o[0];
-               } else throw new Sabre_DAV_Exception_FileNotFound($calendarId . " / " . $objectUri);
-       }
-
-       /**
-        * Creates a new calendar object.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @param string $calendarData
-        * @return null|string|void
-        */
-       function createCalendarObject($calendarId, $objectUri, $calendarData)
-       {
-               $x = explode("-", $calendarId);
-
-               q("INSERT INTO %s%scalendarobjects (`namespace`, `namespace_id`, `uri`, `calendardata`, `lastmodified`, `etag`, `size`) VALUES (%d, %d, '%s', '%s', NOW(), '%s', %d)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                       IntVal($x[0]), IntVal($x[1]), dbesc($objectUri), addslashes($calendarData), md5($calendarData), strlen($calendarData)
-               );
-
-               $this->increaseCalendarCtag($x[0], $x[1]);
-               renderCalDavEntry_uri($objectUri);
-       }
-
-       /**
-        * Updates an existing calendarobject, based on it's uri.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @param string $calendarData
-        * @return null|string|void
-        */
-       function updateCalendarObject($calendarId, $objectUri, $calendarData)
-       {
-               $x = explode("-", $calendarId);
-
-               q("UPDATE %s%scalendarobjects SET `calendardata` = '%s', `lastmodified` = NOW(), `etag` = '%s', `size` = %d WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($calendarData), md5($calendarData), strlen($calendarData), IntVal($x[0]), IntVal($x[1]), dbesc($objectUri));
-
-               $this->increaseCalendarCtag($x[0], $x[1]);
-               renderCalDavEntry_uri($objectUri);
-       }
-
-       /**
-        * Deletes an existing calendar object.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @return void
-        */
-       function deleteCalendarObject($calendarId, $objectUri)
-       {
-               $x = explode("-", $calendarId);
-
-               q("DELETE FROM %s%scalendarobjects WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($objectUri)
-               );
-
-               $this->increaseCalendarCtag($x[0], $x[1]);
-               renderCalDavEntry_uri($objectUri);
-       }
-}
index c1152dc0106118c6e7b8dda0e2d6cfcc4dda38ef..97d5722a9bed27d1832bc3cfe938c3cb2d774614 100644 (file)
 <?php
 
-abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract {
+abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract
+{
        /**
-        * List of CalDAV properties, and how they map to database fieldnames
-        *
-        * Add your own properties by simply adding on to this array
-        *
         * @var array
         */
-       public $propertyMap = array(
-               '{DAV:}displayname'                          => 'displayname',
+       protected $propertyMap = array(
+               '{DAV:}displayname'                                   => 'displayname',
                '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
                '{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
-               '{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
-               '{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
+               '{http://apple.com/ns/ical/}calendar-order'           => 'calendarorder',
+               '{http://apple.com/ns/ical/}calendar-color'           => 'calendarcolor',
        );
 
 
+       /**
+        * @abstract
+        * @return int
+        */
        abstract public function getNamespace();
-       abstract public function getCalUrlPrefix();
 
        /**
-        * @param int $namespace
-        * @param int $namespace_id
+        * @static
+        * @abstract
+        * @return string
+        */
+       abstract public static function getBackendTypeName();
+
+
+       /**
+        * @param int $calendarId
+        * @param string $sd
+        * @param string $ed
+        * @param string $base_path
+        * @return array
+        */
+       abstract public function listItemsByRange($calendarId, $sd, $ed, $base_path);
+
+
+       /**
+        * @var array
+        */
+       static private $calendarCache = array();
+
+       /**
+        * @var array
         */
-       protected function increaseCalendarCtag($namespace, $namespace_id) {
-               $namespace = IntVal($namespace);
-               $namespace_id = IntVal($namespace_id);
+       static private $calendarObjectCache = array();
 
-               q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $namespace, $namespace_id);
+       /**
+        * @static
+        * @param int $calendarId
+        * @return array
+        */
+       static public function loadCalendarById($calendarId)
+       {
+               if (!isset(self::$calendarCache[$calendarId])) {
+                       $c                                = q("SELECT * FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+                       self::$calendarCache[$calendarId] = $c[0];
+               }
+               return self::$calendarCache[$calendarId];
        }
 
+       /**
+        * @static
+        * @param int $obj_id
+        * @return array
+        */
+       static public function loadCalendarobjectById($obj_id)
+       {
+               if (!isset(self::$calendarObjectCache[$obj_id])) {
+                       $o                                  = q("SELECT * FROM %s%scalendarobjects WHERE `id` = %d",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($obj_id)
+                       );
+                       self::$calendarObjectCache[$obj_id] = $o[0];
+               }
+               return self::$calendarObjectCache[$obj_id];
+       }
 
 
        /**
-        * Returns a list of calendars for a principal.
-        *
-        * Every project is an array with the following keys:
-        *  * id, a unique id that will be used by other functions to modify the
-        *    calendar. This can be the same as the uri or a database key.
-        *  * uri, which the basename of the uri with which the calendar is
-        *    accessed.
-        *  * principaluri. The owner of the calendar. Almost always the same as
-        *    principalUri passed to this method.
+        * @static
+        * @param Sabre\VObject\Component\VEvent $component
+        * @return int
+        */
+       public static function getDtEndTimeStamp(&$component)
+       {
+               /** @var Sabre\VObject\Property\DateTime $dtstart */
+               $dtstart = $component->__get("DTSTART");
+               if ($component->__get("DTEND")) {
+                       /** @var Sabre\VObject\Property\DateTime $dtend */
+                       $dtend = $component->__get("DTEND");
+                       return $dtend->getDateTime()->getTimeStamp();
+               } elseif ($component->__get("DURATION")) {
+                       $endDate = clone $dtstart->getDateTime();
+                       $endDate->add(Sabre\VObject\DateTimeParser::parse($component->__get("DURATION")->value));
+                       return $endDate->getTimeStamp();
+               } elseif ($dtstart->getDateType() === Sabre\VObject\Property\DateTime::DATE) {
+                       $endDate = clone $dtstart->getDateTime();
+                       $endDate->modify('+1 day');
+                       return $endDate->getTimeStamp();
+               } else {
+                       return $dtstart->getDateTime()->getTimeStamp() + 3600;
+               }
+
+       }
+
+
+       /**
+        * Parses some information from calendar objects, used for optimized
+        * calendar-queries.
         *
-        * Furthermore it can contain webdav properties in clark notation. A very
-        * common one is '{DAV:}displayname'.
+        * Returns an array with the following keys:
+        *   * etag
+        *   * size
+        *   * componentType
+        *   * firstOccurence
+        *   * lastOccurence
         *
-        * @param string $principalUri
+        * @param string $calendarData
+        * @throws Sabre_DAV_Exception_BadRequest
         * @return array
         */
-       public function getCalendarsForUser($principalUri)
+       protected function getDenormalizedData($calendarData)
        {
-               list(,$name) = Sabre_DAV_URLUtil::splitPath($principalUri);
-               $user_id = dav_compat_username2id($name);
-
-               $cals = q("SELECT * FROM %s%scalendars WHERE `uid`=%d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $user_id, $this->getNamespace());
-               $ret = array();
-               foreach ($cals as $cal) {
-                       $dat = array(
-                               "id" => $cal["namespace"] . "-" . $cal["namespace_id"],
-                               "uri" => $this->getCalUrlPrefix() . "-" . $cal["namespace_id"],
-                               "principaluri" => $principalUri,
-                               '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag']?$cal['ctag']:'0',
-                               "calendar_class" => "Sabre_CalDAV_Calendar",
-                       );
-                       foreach ($this->propertyMap as $key=>$field) $dat[$key] = $cal[$field];
+               /** @var Sabre\VObject\Component\VEvent $vObject */
+               $vObject        = Sabre\VObject\Reader::read($calendarData);
+               $componentType  = null;
+               $component      = null;
+               $firstOccurence = null;
+               $lastOccurence  = null;
 
-                       $ret[] = $dat;
+               foreach ($vObject->getComponents() as $component) {
+                       if ($component->name !== 'VTIMEZONE') {
+                               $componentType = $component->name;
+                               break;
+                       }
+               }
+               if (!$componentType) {
+                       throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
                }
+               if ($componentType === 'VEVENT') {
+                       /** @var Sabre\VObject\Component\VEvent $component */
+                       /** @var Sabre\VObject\Property\DateTime $dtstart  */
+                       $dtstart        = $component->__get("DTSTART");
+                       $firstOccurence = $dtstart->getDateTime()->getTimeStamp();
+                       // Finding the last occurence is a bit harder
+                       if (!$component->__get("RRULE")) {
+                               $lastOccurence = self::getDtEndTimeStamp($component);
+                       } else {
+                               $it      = new Sabre\VObject\RecurrenceIterator($vObject, (string)$component->__get("UID"));
+                               $maxDate = new DateTime(CALDAV_MAX_YEAR . "-01-01");
+                               if ($it->isInfinite()) {
+                                       $lastOccurence = $maxDate->getTimeStamp();
+                               } else {
+                                       $end = $it->getDtEnd();
+                                       while ($it->valid() && $end < $maxDate) {
+                                               $end = $it->getDtEnd();
+                                               $it->next();
+
+                                       }
+                                       $lastOccurence = $end->getTimeStamp();
+                               }
+
+                       }
+               }
+
+               return array(
+                       'etag'           => md5($calendarData),
+                       'size'           => strlen($calendarData),
+                       'componentType'  => $componentType,
+                       'firstOccurence' => $firstOccurence,
+                       'lastOccurence'  => $lastOccurence,
+               );
 
-               return $ret;
        }
 
        /**
@@ -109,10 +212,11 @@ abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract
         * @param array $mutations
         * @return bool|array
         */
-       public function updateCalendar($calendarId, array $mutations) {
+       public function updateCalendar($calendarId, array $mutations)
+       {
 
                $newValues = array();
-               $result = array(
+               $result    = array(
                        200 => array(), // Ok
                        403 => array(), // Forbidden
                        424 => array(), // Failed Dependency
@@ -120,17 +224,17 @@ abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract
 
                $hasError = false;
 
-               foreach($mutations as $propertyName=>$propertyValue) {
+               foreach ($mutations as $propertyName=> $propertyValue) {
 
                        // We don't know about this property.
                        if (!isset($this->propertyMap[$propertyName])) {
-                               $hasError = true;
+                               $hasError                   = true;
                                $result[403][$propertyName] = null;
                                unset($mutations[$propertyName]);
                                continue;
                        }
 
-                       $fieldName = $this->propertyMap[$propertyName];
+                       $fieldName             = $this->propertyMap[$propertyName];
                        $newValues[$fieldName] = $propertyValue;
 
                }
@@ -138,33 +242,46 @@ abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract
                // If there were any errors we need to fail the request
                if ($hasError) {
                        // Properties has the remaining properties
-                       foreach($mutations as $propertyName=>$propertyValue) {
+                       foreach ($mutations as $propertyName=> $propertyValue) {
                                $result[424][$propertyName] = null;
                        }
 
                        // Removing unused statuscodes for cleanliness
-                       foreach($result as $status=>$properties) {
-                               if (is_array($properties) && count($properties)===0) unset($result[$status]);
+                       foreach ($result as $status=> $properties) {
+                               if (is_array($properties) && count($properties) === 0) unset($result[$status]);
                        }
 
                        return $result;
 
                }
 
-               $x = explode("-", $calendarId);
-
-               $this->increaseCalendarCtag($x[0], $x[1]);
+               $this->increaseCalendarCtag($calendarId);
 
                $valuesSql = array();
-               foreach($newValues as $fieldName=>$value) $valuesSql[] = "`" . $fieldName . "` = '" . dbesc($value) . "'";
+               foreach ($newValues as $fieldName=> $value) $valuesSql[] = "`" . $fieldName . "` = '" . dbesc($value) . "'";
                if (count($valuesSql) > 0) {
-                       q("UPDATE %s%scalendars SET " . implode(", ", $valuesSql) . " WHERE `namespace` = %d AND `namespace_id` = %d",
-                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1])
-                       );
+                       q("UPDATE %s%scalendars SET " . implode(", ", $valuesSql) . " WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
                }
 
                return true;
 
        }
 
+       /**
+        * @param int $calendarId
+        */
+       protected function increaseCalendarCtag($calendarId)
+       {
+               q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `id` = '%d'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+               self::$calendarObjectCache = array();
+       }
+
+       /**
+        * @abstract
+        * @param int $calendar_id
+        * @param int $calendarobject_id
+        * @return string
+        */
+       abstract function getItemDetailRedirect($calendar_id, $calendarobject_id);
+
 }
\ No newline at end of file
diff --git a/dav/common/dav_caldav_backend_private.inc.php b/dav/common/dav_caldav_backend_private.inc.php
new file mode 100644 (file)
index 0000000..1eb3b36
--- /dev/null
@@ -0,0 +1,510 @@
+<?php
+
+class Sabre_CalDAV_Backend_Private extends Sabre_CalDAV_Backend_Common
+{
+
+
+       /**
+        * @var null|Sabre_CalDAV_Backend_Private
+        */
+       private static $instance = null;
+
+       /**
+        * @static
+        * @return Sabre_CalDAV_Backend_Private
+        */
+       public static function getInstance()
+       {
+               if (self::$instance == null) {
+                       self::$instance = new Sabre_CalDAV_Backend_Private();
+               }
+               return self::$instance;
+       }
+
+
+       /**
+        * @return int
+        */
+       public function getNamespace()
+       {
+               return CALDAV_NAMESPACE_PRIVATE;
+       }
+
+       /**
+        * @static
+        * @return string
+        */
+       public static function getBackendTypeName()
+       {
+               return t("Private Events");
+       }
+
+       /**
+        * @obsolete
+        * @param array $calendar
+        * @param int $user
+        * @return array
+        */
+       public function getPermissionsCalendar($calendar, $user)
+       {
+               if ($calendar["namespace"] == CALDAV_NAMESPACE_PRIVATE && $user == $calendar["namespace_id"]) return array("read"=> true, "write"=> true);
+               return array("read"=> false, "write"=> false);
+       }
+
+       /**
+        * @obsolete
+        * @param array $calendar
+        * @param int $user
+        * @param string $calendarobject_id
+        * @param null|array $item_arr
+        * @return array
+        */
+       public function getPermissionsItem($calendar, $user, $calendarobject_id, $item_arr = null)
+       {
+               return $this->getPermissionsCalendar($calendar, $user);
+       }
+
+
+       /**
+        * @param array $row
+        * @param array $calendar
+        * @param string $base_path
+        * @return array
+        */
+       private function jqcal2wdcal($row, $calendar, $base_path)
+       {
+               $not      = q("SELECT COUNT(*) num FROM %s%snotifications WHERE `calendar_id` = %d AND `calendarobject_id` = %d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($row["calendar_id"]), IntVal($row["calendarobject_id"])
+               );
+               $editable = $this->getPermissionsItem($calendar["namespace_id"], $row["calendarobject_id"], $row);
+
+               $end = wdcal_mySql2PhpTime($row["EndTime"]);
+               if ($row["IsAllDayEvent"]) $end -= 1;
+
+               return array(
+                       "jq_id"             => $row["id"],
+                       "ev_id"             => $row["calendarobject_id"],
+                       "summary"           => escape_tags($row["Summary"]),
+                       "start"             => wdcal_mySql2PhpTime($row["StartTime"]),
+                       "end"               => $end,
+                       "is_allday"         => $row["IsAllDayEvent"],
+                       "is_moredays"       => 0,
+                       "is_recurring"      => $row["IsRecurring"],
+                       "color"             => (is_null($row["Color"]) || $row["Color"] == "" ? $calendar["calendarcolor"] : $row["Color"]),
+                       "is_editable"       => ($editable ? 1 : 0),
+                       "is_editable_quick" => ($editable && !$row["IsRecurring"] ? 1 : 0),
+                       "location"          => "Loc.",
+                       "attendees"         => '',
+                       "has_notification"  => ($not[0]["num"] > 0 ? 1 : 0),
+                       "url_detail"        => $base_path . $row["calendarobject_id"] . "/",
+                       "url_edit"          => $base_path . $row["calendarobject_id"] . "/edit/",
+                       "special_type"      => "",
+               );
+       }
+
+       /**
+        * @param int $calendarId
+        * @param string $sd
+        * @param string $ed
+        * @param string $base_path
+        * @return array
+        */
+       public function listItemsByRange($calendarId, $sd, $ed, $base_path)
+       {
+               $calendar       = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+               $von            = wdcal_php2MySqlTime($sd);
+               $bis            = wdcal_php2MySqlTime($ed);
+               $timezoneOffset = date("P");
+
+               // @TODO Events, die früher angefangen haben, aber noch andauern
+               $evs = q("SELECT *, CONVERT_TZ(`StartTime`, @@session.time_zone, '$timezoneOffset') StartTime, CONVERT_TZ(`EndTime`, @@session.time_zone, '$timezoneOffset') EndTime
+                       FROM %s%sjqcalendar WHERE `calendar_id` = %d AND `StartTime` between '%s' and '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($von), dbesc($bis));
+
+               $events = array();
+               foreach ($evs as $row) $events[] = $this->jqcal2wdcal($row, $calendar, $base_path . $row["calendar_id"] . "/");
+
+               return $events;
+       }
+
+
+       /**
+        * @param int $calendar_id
+        * @param int $calendarobject_id
+        * @return string
+        */
+       public function getItemDetailRedirect($calendar_id, $calendarobject_id)
+       {
+               return "/dav/wdcal/$calendar_id/$calendarobject_id/edit/";
+       }
+
+       /**
+        * Returns a list of calendars for a principal.
+        *
+        * Every project is an array with the following keys:
+        *  * id, a unique id that will be used by other functions to modify the
+        *    calendar. This can be the same as the uri or a database key.
+        *  * uri, which the basename of the uri with which the calendar is
+        *    accessed.
+        *  * principaluri. The owner of the calendar. Almost always the same as
+        *    principalUri passed to this method.
+        *
+        * Furthermore it can contain webdav properties in clark notation. A very
+        * common one is '{DAV:}displayname'.
+        *
+        * @param string $principalUri
+        * @throws DAVVersionMismatchException
+        * @return array
+        */
+       public function getCalendarsForUser($principalUri)
+       {
+               $n = dav_compat_principal2namespace($principalUri);
+               if ($n["namespace"] != $this->getNamespace()) return array();
+
+               $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"]));
+               $ret  = array();
+               foreach ($cals as $cal) {
+                       if (!isset($cal["uri"])) throw new DAVVersionMismatchException();
+                       if (in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue;
+
+                       $components = array();
+                       if ($cal["has_vevent"]) $components[] = "VEVENT";
+                       if ($cal["has_vtodo"]) $components[] = "VTODO";
+
+                       $dat = array(
+                               "id"                                                                       => $cal["id"],
+                               "uri"                                                                      => $cal["uri"],
+                               "principaluri"                                                             => $principalUri,
+                               '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag'                  => $cal['ctag'] ? $cal['ctag'] : '0',
+                               '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet($components),
+                               "calendar_class"                                                           => "Sabre_CalDAV_Calendar_Private",
+                       );
+                       foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field];
+
+                       $ret[] = $dat;
+               }
+
+               return $ret;
+       }
+
+
+       /**
+        * Creates a new calendar for a principal.
+        *
+        * If the creation was a success, an id must be returned that can be used to reference
+        * this calendar in other methods, such as updateCalendar.
+        *
+        * @param string $principalUri
+        * @param string $calendarUri
+        * @param array $properties
+        * @throws Sabre_DAV_Exception|Sabre_DAV_Exception_Conflict
+        * @return string|void
+        */
+       public function createCalendar($principalUri, $calendarUri, array $properties)
+       {
+
+               $uid = dav_compat_principal2uid($principalUri);
+
+               $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, dbesc($calendarUri));
+               if (count($r) > 0) throw new Sabre_DAV_Exception_Conflict("A calendar with this URI already exists");
+
+               $keys = array("`namespace`", "`namespace_id`", "`ctag`", "`uri`");
+               $vals = array(CALDAV_NAMESPACE_PRIVATE, IntVal($uid), 1, "'" . dbesc($calendarUri) . "'");
+
+               // Default value
+               $sccs       = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+               $has_vevent = $has_vtodo = 1;
+               if (isset($properties[$sccs])) {
+                       if (!($properties[$sccs] instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet)) {
+                               throw new Sabre_DAV_Exception('The ' . $sccs . ' property must be of type: Sabre_CalDAV_Property_SupportedCalendarComponentSet');
+                       }
+                       $v          = $properties[$sccs]->getValue();
+                       $has_vevent = $has_vtodo = 0;
+                       foreach ($v as $w) {
+                               if (mb_strtolower($w) == "vevent") $has_vevent = 1;
+                               if (mb_strtolower($w) == "vtodo") $has_vtodo = 1;
+                       }
+               }
+               $keys[] = "`has_vevent`";
+               $keys[] = "`has_vtodo`";
+               $vals[] = $has_vevent;
+               $vals[] = $has_vtodo;
+
+               foreach ($this->propertyMap as $xmlName=> $dbName) {
+                       if (isset($properties[$xmlName])) {
+                               $keys[] = "`$dbName`";
+                               $vals[] = "'" . dbesc($properties[$xmlName]) . "'";
+                       }
+               }
+
+               $sql = sprintf("INSERT INTO %s%scalendars (" . implode(', ', $keys) . ") VALUES (" . implode(', ', $vals) . ")", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+
+               q($sql);
+
+               $x = q("SELECT id FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, $calendarUri
+               );
+               return $x[0]["id"];
+
+       }
+
+       /**
+        * Updates properties for a calendar.
+        *
+        * The mutations array uses the propertyName in clark-notation as key,
+        * and the array value for the property value. In the case a property
+        * should be deleted, the property value will be null.
+        *
+        * This method must be atomic. If one property cannot be changed, the
+        * entire operation must fail.
+        *
+        * If the operation was successful, true can be returned.
+        * If the operation failed, false can be returned.
+        *
+        * Deletion of a non-existent property is always successful.
+        *
+        * Lastly, it is optional to return detailed information about any
+        * failures. In this case an array should be returned with the following
+        * structure:
+        *
+        * array(
+        *   403 => array(
+        *      '{DAV:}displayname' => null,
+        *   ),
+        *   424 => array(
+        *      '{DAV:}owner' => null,
+        *   )
+        * )
+        *
+        * In this example it was forbidden to update {DAV:}displayname.
+        * (403 Forbidden), which in turn also caused {DAV:}owner to fail
+        * (424 Failed Dependency) because the request needs to be atomic.
+        *
+        * @param string $calendarId
+        * @param array $mutations
+        * @return bool|array
+        */
+       public function updateCalendar($calendarId, array $mutations)
+       {
+
+               $newValues = array();
+               $result    = array(
+                       200 => array(), // Ok
+                       403 => array(), // Forbidden
+                       424 => array(), // Failed Dependency
+               );
+
+               $hasError = false;
+
+               foreach ($mutations as $propertyName=> $propertyValue) {
+
+                       // We don't know about this property.
+                       if (!isset($this->propertyMap[$propertyName])) {
+                               $hasError                   = true;
+                               $result[403][$propertyName] = null;
+                               unset($mutations[$propertyName]);
+                               continue;
+                       }
+
+                       $fieldName             = $this->propertyMap[$propertyName];
+                       $newValues[$fieldName] = $propertyValue;
+
+               }
+
+               // If there were any errors we need to fail the request
+               if ($hasError) {
+                       // Properties has the remaining properties
+                       foreach ($mutations as $propertyName=> $propertyValue) {
+                               $result[424][$propertyName] = null;
+                       }
+
+                       // Removing unused statuscodes for cleanliness
+                       foreach ($result as $status=> $properties) {
+                               if (is_array($properties) && count($properties) === 0) unset($result[$status]);
+                       }
+
+                       return $result;
+
+               }
+
+               $sql = "`ctag` = `ctag` + 1";
+               foreach ($newValues as $key=> $val) $sql .= ", `" . $key . "` = '" . dbesc($val) . "'";
+
+               $sql = sprintf("UPDATE %s%scalendars SET $sql WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+               q($sql);
+
+               return true;
+
+       }
+
+
+       /**
+        * Delete a calendar and all it's objects
+        *
+        * @param string $calendarId
+        * @return void
+        */
+       public function deleteCalendar($calendarId)
+       {
+               q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+               q("DELETE FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+       }
+
+
+       /**
+        * Returns all calendar objects within a calendar.
+        *
+        * Every item contains an array with the following keys:
+        *   * id - unique identifier which will be used for subsequent updates
+        *   * calendardata - The iCalendar-compatible calendar data
+        *   * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
+        *   * lastmodified - a timestamp of the last modification time
+        *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+        *   '  "abcdef"')
+        *   * calendarid - The calendarid as it was passed to this function.
+        *   * size - The size of the calendar objects, in bytes.
+        *
+        * Note that the etag is optional, but it's highly encouraged to return for
+        * speed reasons.
+        *
+        * The calendardata is also optional. If it's not returned
+        * 'getCalendarObject' will be called later, which *is* expected to return
+        * calendardata.
+        *
+        * If neither etag or size are specified, the calendardata will be
+        * used/fetched to determine these numbers. If both are specified the
+        * amount of times this is needed is reduced by a great degree.
+        *
+        * @param mixed $calendarId
+        * @return array
+        */
+       function getCalendarObjects($calendarId)
+       {
+               $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+               $ret  = array();
+               foreach ($objs as $obj) {
+                       $ret[] = array(
+                               "id"           => IntVal($obj["id"]),
+                               "calendardata" => $obj["calendardata"],
+                               "uri"          => $obj["uri"],
+                               "lastmodified" => $obj["lastmodified"],
+                               "calendarid"   => $calendarId,
+                               "etag"         => $obj["etag"],
+                               "size"         => IntVal($obj["size"]),
+                       );
+               }
+               return $ret;
+       }
+
+       /**
+        * Returns information from a single calendar object, based on it's object
+        * uri.
+        *
+        * The returned array must have the same keys as getCalendarObjects. The
+        * 'calendardata' object is required here though, while it's not required
+        * for getCalendarObjects.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return array
+        */
+       function getCalendarObject($calendarId, $objectUri)
+       {
+               $o = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+               if (count($o) > 0) {
+                       $o[0]["calendarid"]   = $calendarId;
+                       $o[0]["calendardata"] = str_ireplace("Europe/Belgrade", "Europe/Berlin", $o[0]["calendardata"]);
+                       return $o[0];
+               } else throw new Sabre_DAV_Exception_NotFound($calendarId . " / " . $objectUri);
+       }
+
+       /**
+        * Creates a new calendar object.
+        *
+        * It is possible return an etag from this function, which will be used in
+        * the response to this PUT request. Note that the ETag must be surrounded
+        * by double-quotes.
+        *
+        * However, you should only really return this ETag if you don't mangle the
+        * calendar-data. If the result of a subsequent GET to this object is not
+        * the exact same as this request body, you should omit the ETag.
+        *
+        * @param mixed $calendarId
+        * @param string $objectUri
+        * @param string $calendarData
+        * @return string|null
+        */
+       function createCalendarObject($calendarId, $objectUri, $calendarData)
+       {
+               $calendarData = icalendar_sanitize_string($calendarData);
+
+               $extraData = $this->getDenormalizedData($calendarData);
+
+               q("INSERT INTO %s%scalendarobjects (`calendar_id`, `uri`, `calendardata`, `lastmodified`, `componentType`, `firstOccurence`, `lastOccurence`, `etag`, `size`)
+                       VALUES (%d, '%s', '%s', NOW(), '%s', '%s', '%s', '%s', %d)",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri), addslashes($calendarData), dbesc($extraData['componentType']),
+                       dbesc(wdcal_php2MySqlTime($extraData['firstOccurence'])), dbesc(wdcal_php2MySqlTime($extraData['lastOccurence'])), dbesc($extraData["etag"]), IntVal($extraData["size"])
+               );
+
+               $this->increaseCalendarCtag($calendarId);
+               renderCalDavEntry_uri($objectUri);
+
+               return '"' . $extraData['etag'] . '"';
+       }
+
+       /**
+        * Updates an existing calendarobject, based on it's uri.
+        *
+        * It is possible return an etag from this function, which will be used in
+        * the response to this PUT request. Note that the ETag must be surrounded
+        * by double-quotes.
+        *
+        * However, you should only really return this ETag if you don't mangle the
+        * calendar-data. If the result of a subsequent GET to this object is not
+        * the exact same as this request body, you should omit the ETag.
+        *
+        * @param mixed $calendarId
+        * @param string $objectUri
+        * @param string $calendarData
+        * @return string|null
+        */
+       function updateCalendarObject($calendarId, $objectUri, $calendarData)
+       {
+               $calendarData = icalendar_sanitize_string($calendarData);
+
+               $extraData = $this->getDenormalizedData($calendarData);
+
+               q("UPDATE %s%scalendarobjects SET `calendardata` = '%s', `lastmodified` = NOW(), `etag` = '%s', `size` = %d, `componentType` = '%s', `firstOccurence` = '%s', `lastOccurence` = '%s'
+                       WHERE `calendar_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($calendarData), dbesc($extraData["etag"]), IntVal($extraData["size"]), dbesc($extraData["componentType"]),
+                       dbesc(wdcal_php2MySqlTime($extraData["firstOccurence"])), dbesc(wdcal_php2MySqlTime($extraData["lastOccurence"])), IntVal($calendarId), dbesc($objectUri));
+
+               $this->increaseCalendarCtag($calendarId);
+               renderCalDavEntry_uri($objectUri);
+
+               return '"' . $extraData['etag'] . '"';
+       }
+
+       /**
+        * Deletes an existing calendar object.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return void
+        */
+       function deleteCalendarObject($calendarId, $objectUri)
+       {
+               $r = q("SELECT `id` FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+               if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
+
+               q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+
+               $this->increaseCalendarCtag($calendarId);
+               renderCalDavEntry_calobj_id($r[0]["id"]);
+       }
+}
diff --git a/dav/common/dav_caldav_backend_virtual.inc.php b/dav/common/dav_caldav_backend_virtual.inc.php
new file mode 100644 (file)
index 0000000..e2759cf
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+
+abstract class Sabre_CalDAV_Backend_Virtual extends Sabre_CalDAV_Backend_Common
+{
+
+
+
+       /**
+        * @static
+        * @abstract
+        * @param int $calendarId
+        * @param string $uri
+        * @return array
+        */
+       /*
+       abstract public function getItemsByUri($calendarId, $uri);
+    */
+
+       /**
+        * @static
+        * @param int $uid
+        * @param int $namespace
+        */
+       static public function invalidateCache($uid = 0, $namespace = 0) {
+               q("DELETE FROM %s%scal_virtual_object_sync WHERE `uid` = %d AND `namespace` = %d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid), IntVal($namespace));
+       }
+
+       /**
+        * @static
+        * @abstract
+        * @param int $calendarId
+        */
+       static abstract protected function createCache_internal($calendarId);
+
+       /**
+        * @static
+        * @param int $calendarId
+        */
+       static protected function createCache($calendarId) {
+               $calendarId = IntVal($calendarId);
+               q("DELETE FROM %s%scal_virtual_object_cache WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+               static::createCache_internal($calendarId);
+               q("REPLACE INTO %s%scal_virtual_object_sync (`calendar_id`, `date`) VALUES (%d, NOW())", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+       }
+
+       /**
+        * @param string $calendarId
+        * @return array
+        */
+       public function getCalendarObjects($calendarId)
+       {
+               $calendarId = IntVal($calendarId);
+               $r = q("SELECT COUNT(*) n FROM %s%scal_virtual_object_sync WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+
+               if ($r[0]["n"] == 0) static::createCache($calendarId);
+
+               $r = q("SELECT * FROM %s%scal_virtual_object_cache WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+
+               $ret = array();
+               foreach ($r as $obj) {
+                       $ret[] = array(
+                               "id" => IntVal($obj["data_uri"]),
+                               "calendardata" => $obj["calendardata"],
+                               "uri" => $obj["data_uri"],
+                               "lastmodified" => $obj["date"],
+                               "calendarid" => $calendarId,
+                               "etag" => $obj["etag"],
+                               "size" => IntVal($obj["size"]),
+                       );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Returns information from a single calendar object, based on it's object
+        * uri.
+        *
+        * The returned array must have the same keys as getCalendarObjects. The
+        * 'calendardata' object is required here though, while it's not required
+        * for getCalendarObjects.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return array
+        */
+       public function getCalendarObject($calendarId, $objectUri)
+       {
+               $calendarId = IntVal($calendarId);
+               $r = q("SELECT COUNT(*) n FROM %s%scal_virtual_object_sync WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+               if ($r[0]["n"] == 0) static::createCache($calendarId);
+
+               $r = q("SELECT * FROM %s%scal_virtual_object_cache WHERE `data_uri` = '%s' AND `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($objectUri), IntVal($calendarId));
+               if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
+
+               $obj = $r[0];
+               $ret = array(
+                       "id" => IntVal($obj["data_uri"]),
+                       "calendardata" => $obj["calendardata"],
+                       "uri" => $obj["data_uri"],
+                       "lastmodified" => $obj["date"],
+                       "calendarid" => $calendarId,
+                       "etag" => $obj["etag"],
+                       "size" => IntVal($obj["size"]),
+               );
+               return $ret;
+       }
+
+
+
+       /**
+        * Creates a new calendar for a principal.
+        *
+        * If the creation was a success, an id must be returned that can be used to reference
+        * this calendar in other methods, such as updateCalendar.
+        *
+        * @param string $principalUri
+        * @param string $calendarUri
+        * @param array $properties
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       public function createCalendar($principalUri, $calendarUri, array $properties)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+       /**
+        * Delete a calendar and all it's objects
+        *
+        * @param string $calendarId
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       public function deleteCalendar($calendarId)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+
+       /**
+        * Creates a new calendar object.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @param string $calendarData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return null|string|void
+        */
+       function createCalendarObject($calendarId, $objectUri, $calendarData)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+       /**
+        * Updates an existing calendarobject, based on it's uri.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @param string $calendarData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return null|string|void
+        */
+       function updateCalendarObject($calendarId, $objectUri, $calendarData)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+       /**
+        * Deletes an existing calendar object.
+        *
+        * @param string $calendarId
+        * @param string $objectUri
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       function deleteCalendarObject($calendarId, $objectUri)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+
+}
diff --git a/dav/common/dav_caldav_calendar_private.inc.php b/dav/common/dav_caldav_calendar_private.inc.php
new file mode 100644 (file)
index 0000000..65f7c2b
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+
+class Sabre_CalDAV_Calendar_Private extends Sabre_CalDAV_Calendar
+{
+
+       public function getACL()
+       {
+
+               return array(
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'],
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{DAV:}write',
+                               'principal' => $this->calendarInfo['principaluri'],
+                               'protected' => true,
+                       ),
+                       /*
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{DAV:}write',
+                               'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
+                               'principal' => '{DAV:}authenticated',
+                               'protected' => true,
+                       ),
+                       */
+
+               );
+
+       }
+
+
+
+}
\ No newline at end of file
diff --git a/dav/common/dav_caldav_calendar_virtual.inc.php b/dav/common/dav_caldav_calendar_virtual.inc.php
new file mode 100644 (file)
index 0000000..ee14071
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+class Sabre_CalDAV_Calendar_Virtual extends Sabre_CalDAV_Calendar {
+
+       /**
+        * Returns a list of ACE's for this node.
+        *
+        * Each ACE has the following properties:
+        *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+        *     currently the only supported privileges
+        *   * 'principal', a url to the principal who owns the node
+        *   * 'protected' (optional), indicating that this ACE is not allowed to
+        *      be updated.
+        *
+        * @return array
+        */
+       public function getACL() {
+
+               return array(
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'],
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{DAV:}read',
+                               'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
+                               'protected' => true,
+                       ),
+                       array(
+                               'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
+                               'principal' => '{DAV:}authenticated',
+                               'protected' => true,
+                       ),
+
+               );
+
+       }
+}
diff --git a/dav/common/dav_carddav_backend_common.inc.php b/dav/common/dav_carddav_backend_common.inc.php
new file mode 100644 (file)
index 0000000..4279f8a
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+abstract class Sabre_CardDAV_Backend_Common extends Sabre_CardDAV_Backend_Abstract
+{
+       /**
+        * @abstract
+        * @return int
+        */
+       abstract public function getNamespace();
+
+       /**
+        * @static
+        * @abstract
+        * @return string
+        */
+       abstract public static function getBackendTypeName();
+
+       /**
+        * @var array
+        */
+       static private $addressbookCache = array();
+
+       /**
+        * @var array
+        */
+       static private $addressbookObjectCache = array();
+
+       /**
+        * @static
+        * @param int $addressbookId
+        * @return array
+        */
+       static public function loadCalendarById($addressbookId)
+       {
+               if (!isset(self::$addressbookCache[$addressbookId])) {
+                       $c                                = q("SELECT * FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId));
+                       self::$addressbookCache[$addressbookId] = $c[0];
+               }
+               return self::$addressbookCache[$addressbookId];
+       }
+
+       /**
+        * @static
+        * @param int $obj_id
+        * @return array
+        */
+       static public function loadAddressbookobjectById($obj_id)
+       {
+               if (!isset(self::$addressbookObjectCache[$obj_id])) {
+                       $o                                  = q("SELECT * FROM %s%saddressbookobjects WHERE `id` = %d",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($obj_id)
+                       );
+                       self::$addressbookObjectCache[$obj_id] = $o[0];
+               }
+               return self::$addressbookObjectCache[$obj_id];
+       }
+
+
+       /**
+        * Updates an addressbook's properties
+        *
+        * See Sabre_DAV_IProperties for a description of the mutations array, as
+        * well as the return value.
+        *
+        * @param mixed $addressBookId
+        * @param array $mutations
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @see Sabre_DAV_IProperties::updateProperties
+        * @return bool|array
+        */
+       public function updateAddressBook($addressBookId, array $mutations)
+       {
+               $updates = array();
+
+               foreach ($mutations as $property=> $newValue) {
+
+                       switch ($property) {
+                               case '{DAV:}displayname' :
+                                       $updates['displayname'] = $newValue;
+                                       break;
+                               case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
+                                       $updates['description'] = $newValue;
+                                       break;
+                               default :
+                                       // If any unsupported values were being updated, we must
+                                       // let the entire request fail.
+                                       return false;
+                       }
+
+               }
+
+               // No values are being updated?
+               if (!$updates) {
+                       return false;
+               }
+
+               $query = 'UPDATE ' . CALDAV_SQL_DB . CALDAV_SQL_PREFIX . 'addressbooks SET ctag = ctag + 1 ';
+               foreach ($updates as $key=> $value) {
+                       $query .= ', `' . dbesc($key) . '` = ' . dbesc($key) . ' ';
+               }
+               $query .= ' WHERE id = ' . IntVal($addressBookId);
+               q($query);
+
+               return true;
+
+       }
+
+       /**
+        * @param int $addressbookId
+        */
+       protected function increaseAddressbookCtag($addressbookId)
+       {
+               q("UPDATE %s%saddressbooks SET `ctag` = `ctag` + 1 WHERE `id` = '%d'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId));
+               self::$addressbookCache = array();
+       }
+}
\ No newline at end of file
diff --git a/dav/common/dav_carddav_backend_private.inc.php b/dav/common/dav_carddav_backend_private.inc.php
new file mode 100644 (file)
index 0000000..71923b9
--- /dev/null
@@ -0,0 +1,275 @@
+<?php
+
+class Sabre_CardDAV_Backend_Std extends Sabre_CardDAV_Backend_Common
+{
+
+       /**
+        * @var null|Sabre_CardDAV_Backend_Std
+        */
+       private static $instance = null;
+
+       /**
+        * @static
+        * @return Sabre_CardDAV_Backend_Std
+        */
+       public static function getInstance() {
+               if (self::$instance == null) {
+                       self::$instance = new Sabre_CardDAV_Backend_Std();
+               }
+               return self::$instance;
+       }
+
+
+       /**
+        * Sets up the object
+        */
+       public function __construct()
+       {
+
+       }
+
+
+       /**
+        * @return int
+        */
+       public function getNamespace()
+       {
+               return CARDDAV_NAMESPACE_PRIVATE;
+       }
+
+       /**
+        * @static
+        * @return string
+        */
+       public static function getBackendTypeName()
+       {
+               return t("Private Addressbooks");
+       }
+
+       /**
+        * Returns the list of addressbooks for a specific user.
+        *
+        * @param string $principalUri
+        * @return array
+        */
+       public function getAddressBooksForUser($principalUri)
+       {
+               $n = dav_compat_principal2namespace($principalUri);
+               if ($n["namespace"] != $this->getNamespace()) return array();
+
+               $addressBooks = array();
+
+               $books = q("SELECT * FROM %s%saddressbooks WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($n["namespace"]), IntVal($n["namespace_id"]));
+               foreach ($books as $row) {
+                       if (in_array($row["uri"], $GLOBALS["CARDDAV_PRIVATE_SYSTEM_ADDRESSBOOKS"])) continue;
+
+                       $addressBooks[] = array(
+                               'id'                                                                => $row['id'],
+                               'uri'                                                               => $row['uri'],
+                               'principaluri'                                                      => $principalUri,
+                               '{DAV:}displayname'                                                 => $row['displayname'],
+                               '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+                               '{http://calendarserver.org/ns/}getctag'                            => $row['ctag'],
+                               '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data'  =>
+                               new Sabre_CardDAV_Property_SupportedAddressData(),
+                       );
+               }
+
+               return $addressBooks;
+
+       }
+
+
+       /**
+        * Creates a new address book
+        *
+        * @param string $principalUri
+        * @param string $url Just the 'basename' of the url.
+        * @param array $properties
+        * @throws Sabre_DAV_Exception_BadRequest
+        * @return void
+        */
+       public function createAddressBook($principalUri, $url, array $properties)
+       {
+               $uid = dav_compat_principal2uid($principalUri);
+
+               $values = array(
+                       'displayname'  => null,
+                       'description'  => null,
+                       'principaluri' => $principalUri,
+                       'uri'          => $url,
+               );
+
+               foreach ($properties as $property=> $newValue) {
+
+                       switch ($property) {
+                               case '{DAV:}displayname' :
+                                       $values['displayname'] = $newValue;
+                                       break;
+                               case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
+                                       $values['description'] = $newValue;
+                                       break;
+                               default :
+                                       throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property);
+                       }
+
+               }
+
+               q("INSERT INTO %s%saddressbooks (`uri`, `displayname`, `description`, `namespace`, `namespace_id`, `ctag`) VALUES ('%s', '%s', '%s', %d, %d, 1)",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($values["uri"]), dbesc($values["displayname"]), dbesc($values["description"]), CARDDAV_NAMESPACE_PRIVATE, IntVal($uid)
+               );
+       }
+
+       /**
+        * Deletes an entire addressbook and all its contents
+        *
+        * @param int $addressBookId
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       public function deleteAddressBook($addressBookId)
+       {
+               q("DELETE FROM %s%saddressbookobjects WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+               q("DELETE FROM %s%saddressbooks WHERE `addressbook_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+       }
+
+       /**
+        * Returns all cards for a specific addressbook id.
+        *
+        * This method should return the following properties for each card:
+        *   * carddata - raw vcard data
+        *   * uri - Some unique url
+        *   * lastmodified - A unix timestamp
+        *
+        * It's recommended to also return the following properties:
+        *   * etag - A unique etag. This must change every time the card changes.
+        *   * size - The size of the card in bytes.
+        *
+        * If these last two properties are provided, less time will be spent
+        * calculating them. If they are specified, you can also ommit carddata.
+        * This may speed up certain requests, especially with large cards.
+        *
+        * @param string $addressbookId
+        * @return array
+        */
+       public function getCards($addressbookId)
+       {
+               $r = q('SELECT `id`, `carddata`, `uri`, `lastmodified`, `etag`, `size`, `contact` FROM %s%saddressbookobjects WHERE `addressbook_id` = %d AND `manually_deleted` = 0',
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId)
+               );
+               if ($r) return $r;
+               return array();
+       }
+
+       /**
+        * Returns a specfic card.
+        *
+        * The same set of properties must be returned as with getCards. The only
+        * exception is that 'carddata' is absolutely required.
+        *
+        * @param mixed $addressBookId
+        * @param string $cardUri
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return array
+        */
+       public function getCard($addressBookId, $cardUri)
+       {
+               $x = q("SELECT `id`, `carddata`, `uri`, `lastmodified`, `etag`, `size` FROM %s%saddressbookobjects WHERE `addressbook_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId), dbesc($cardUri));
+               if (count($x) == 0) throw new Sabre_DAV_Exception_NotFound();
+               return $x[0];
+       }
+
+       /**
+        * Creates a new card.
+        *
+        * The addressbook id will be passed as the first argument. This is the
+        * same id as it is returned from the getAddressbooksForUser method.
+        *
+        * The cardUri is a base uri, and doesn't include the full path. The
+        * cardData argument is the vcard body, and is passed as a string.
+        *
+        * It is possible to return an ETag from this method. This ETag is for the
+        * newly created resource, and must be enclosed with double quotes (that
+        * is, the string itself must contain the double quotes).
+        *
+        * You should only return the ETag if you store the carddata as-is. If a
+        * subsequent GET request on the same card does not have the same body,
+        * byte-by-byte and you did return an ETag here, clients tend to get
+        * confused.
+        *
+        * If you don't return an ETag, you can just return null.
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @param string $cardData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return string
+        */
+       public function createCard($addressBookId, $cardUri, $cardData)
+       {
+               $etag = md5($cardData);
+               q("INSERT INTO %s%saddressbookobjects (`carddata`, `uri`, `lastmodified`, `addressbook_id`, `etag`, `size`) VALUES ('%s', '%s', NOW(), %d, '%s', %d)",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), dbesc($cardUri), IntVal($addressBookId), dbesc($etag), strlen($cardData)
+               );
+
+               q('UPDATE %s%saddressbooks SET `ctag` = `ctag` + 1 WHERE `id` = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+
+               return '"' . $etag . '"';
+
+       }
+
+       /**
+        * Updates a card.
+        *
+        * The addressbook id will be passed as the first argument. This is the
+        * same id as it is returned from the getAddressbooksForUser method.
+        *
+        * The cardUri is a base uri, and doesn't include the full path. The
+        * cardData argument is the vcard body, and is passed as a string.
+        *
+        * It is possible to return an ETag from this method. This ETag should
+        * match that of the updated resource, and must be enclosed with double
+        * quotes (that is: the string itself must contain the actual quotes).
+        *
+        * You should only return the ETag if you store the carddata as-is. If a
+        * subsequent GET request on the same card does not have the same body,
+        * byte-by-byte and you did return an ETag here, clients tend to get
+        * confused.
+        *
+        * If you don't return an ETag, you can just return null.
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @param string $cardData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return string|null
+        */
+       public function updateCard($addressBookId, $cardUri, $cardData)
+       {
+               $etag = md5($cardData);
+               q("UPDATE %s%saddressbookobjects SET `carddata` = '%s', `lastmodified` = NOW(), `etag` = '%s', `size` = %d, `manually_edited` = 1 WHERE `uri` = '%s' AND `addressbook_id` = %d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), dbesc($etag), strlen($cardData), dbesc($cardUri), IntVal($addressBookId)
+               );
+
+               q('UPDATE %s%saddressbooks SET `ctag` = `ctag` + 1 WHERE `id` = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+
+               return '"' . $etag . '"';
+       }
+
+       /**
+        * Deletes a card
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return bool
+        */
+       public function deleteCard($addressBookId, $cardUri)
+       {
+               q("DELETE FROM %s%saddressbookobjects WHERE `addressbook_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId), dbesc($cardUri));
+               q('UPDATE %s%saddressbooks SET `ctag` = `ctag` + 1 WHERE `id` = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+
+               return true;
+       }
+}
diff --git a/dav/common/dav_carddav_backend_std.inc.php b/dav/common/dav_carddav_backend_std.inc.php
deleted file mode 100644 (file)
index c63a74f..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-<?php
-
-/**
- * PDO CardDAV backend
- *
- * This CardDAV backend uses PDO to store addressbooks
- *
- * @package Sabre
- * @subpackage CardDAV
- * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
-class Sabre_CardDAV_Backend_Std extends Sabre_CardDAV_Backend_Abstract
-{
-
-       /**
-        * Sets up the object
-        */
-       public function __construct()
-       {
-
-       }
-
-       /**
-        * Returns the list of addressbooks for a specific user.
-        *
-        * @param string $principalUri
-        * @return array
-        */
-       public function getAddressBooksForUser($principalUri)
-       {
-               $uid = dav_compat_principal2uid($principalUri);
-
-               $addressBooks = array();
-
-               $books = q("SELECT id, uri, displayname, principaluri, description, ctag FROM %s%saddressbooks_phone WHERE principaluri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($principalUri));
-               if (count($books) == 0) {
-                       q("INSERT INTO %s%saddressbooks_phone (uid, principaluri, displayname, uri, description, ctag) VALUES (%d, '%s', '%s', '%s', '%s', 1)",
-                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $uid, dbesc($principalUri), 'Other', 'phone', 'Manually added contacts'
-                       );
-                       $books = q("SELECT id, uri, displayname, principaluri, description, ctag FROM %s%saddressbooks_phone WHERE principaluri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($principalUri));
-               }
-               foreach ($books as $row) {
-                       $addressBooks[] = array(
-                               'id'                                                                => CARDDAV_NAMESPACE_PHONECONTACTS . "-" . $row['id'],
-                               'uri'                                                               => $row['uri'],
-                               'principaluri'                                                      => $row['principaluri'],
-                               '{DAV:}displayname'                                                 => $row['displayname'],
-                               '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
-                               '{http://calendarserver.org/ns/}getctag'                            => $row['ctag'],
-                               '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data'  =>
-                               new Sabre_CardDAV_Property_SupportedAddressData(),
-                       );
-               }
-
-               return $addressBooks;
-
-       }
-
-
-       /**
-        * Updates an addressbook's properties
-        *
-        * See Sabre_DAV_IProperties for a description of the mutations array, as
-        * well as the return value.
-        *
-        * @param mixed $addressBookId
-        * @param array $mutations
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @see Sabre_DAV_IProperties::updateProperties
-        * @return bool|array
-        */
-       public function updateAddressBook($addressBookId, array $mutations)
-       {
-               $x = explode("-", $addressBookId);
-
-               $updates = array();
-
-               foreach ($mutations as $property=> $newValue) {
-
-                       switch ($property) {
-                               case '{DAV:}displayname' :
-                                       $updates['displayname'] = $newValue;
-                                       break;
-                               case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
-                                       $updates['description'] = $newValue;
-                                       break;
-                               default :
-                                       // If any unsupported values were being updated, we must
-                                       // let the entire request fail.
-                                       return false;
-                       }
-
-               }
-
-               // No values are being updated?
-               if (!$updates) {
-                       return false;
-               }
-
-               $query = 'UPDATE ' . CALDAV_SQL_DB . CALDAV_SQL_PREFIX . 'addressbooks_phone SET ctag = ctag + 1 ';
-               foreach ($updates as $key=> $value) {
-                       $query .= ', `' . dbesc($key) . '` = ' . dbesc($key) . ' ';
-               }
-               $query .= ' WHERE id = ' . IntVal($x[1]);
-               q($query);
-
-               return true;
-
-       }
-
-       /**
-        * Creates a new address book
-        *
-        * @param string $principalUri
-        * @param string $url Just the 'basename' of the url.
-        * @param array $properties
-        * @throws Sabre_DAV_Exception_BadRequest
-        * @return void
-        */
-       public function createAddressBook($principalUri, $url, array $properties)
-       {
-               $values = array(
-                       'displayname'  => null,
-                       'description'  => null,
-                       'principaluri' => $principalUri,
-                       'uri'          => $url,
-               );
-
-               foreach ($properties as $property=> $newValue) {
-
-                       switch ($property) {
-                               case '{DAV:}displayname' :
-                                       $values['displayname'] = $newValue;
-                                       break;
-                               case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
-                                       $values['description'] = $newValue;
-                                       break;
-                               default :
-                                       throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property);
-                       }
-
-               }
-
-               q("INSERT INTO %s%saddressbooks_phone (uri, displayname, description, principaluri, ctag) VALUES ('%s', '%s', '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($values["uri"]), dbesc($values["displayname"]), dbesc($values["description"]), dbesc($values["principaluri"])
-               );
-       }
-
-       /**
-        * Deletes an entire addressbook and all its contents
-        *
-        * @param int $addressBookId
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       public function deleteAddressBook($addressBookId)
-       {
-               $x = explode("-", $addressBookId);
-               q("DELETE FROM %s%scards WHERE namespace = %d AND namespace_id = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]));
-               q("DELETE FROM %s%saddressbooks_phone WHERE id = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-       }
-
-       /**
-        * Returns all cards for a specific addressbook id.
-        *
-        * This method should return the following properties for each card:
-        *   * carddata - raw vcard data
-        *   * uri - Some unique url
-        *   * lastmodified - A unix timestamp
-        *
-        * It's recommended to also return the following properties:
-        *   * etag - A unique etag. This must change every time the card changes.
-        *   * size - The size of the card in bytes.
-        *
-        * If these last two properties are provided, less time will be spent
-        * calculating them. If they are specified, you can also ommit carddata.
-        * This may speed up certain requests, especially with large cards.
-        *
-        * @param string $addressbookId
-        * @return array
-        */
-       public function getCards($addressbookId)
-       {
-               $x = explode("-", $addressbookId);
-
-               $r = q('SELECT id, carddata, uri, lastmodified, etag, size, contact FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND manually_deleted = 0',
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1])
-               );
-               if ($r) return $r;
-               return array();
-       }
-
-       /**
-        * Returns a specfic card.
-        *
-        * The same set of properties must be returned as with getCards. The only
-        * exception is that 'carddata' is absolutely required.
-        *
-        * @param mixed $addressBookId
-        * @param string $cardUri
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return array
-        */
-       public function getCard($addressBookId, $cardUri)
-       {
-               $x = explode("-", $addressBookId);
-               $x = q("SELECT id, carddata, uri, lastmodified, etag, size FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND uri = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
-               if (count($x) == 0) throw new Sabre_DAV_Exception_NotFound();
-               return $x[0];
-       }
-
-       /**
-        * Creates a new card.
-        *
-        * The addressbook id will be passed as the first argument. This is the
-        * same id as it is returned from the getAddressbooksForUser method.
-        *
-        * The cardUri is a base uri, and doesn't include the full path. The
-        * cardData argument is the vcard body, and is passed as a string.
-        *
-        * It is possible to return an ETag from this method. This ETag is for the
-        * newly created resource, and must be enclosed with double quotes (that
-        * is, the string itself must contain the double quotes).
-        *
-        * You should only return the ETag if you store the carddata as-is. If a
-        * subsequent GET request on the same card does not have the same body,
-        * byte-by-byte and you did return an ETag here, clients tend to get
-        * confused.
-        *
-        * If you don't return an ETag, you can just return null.
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @param string $cardData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return string
-        */
-       public function createCard($addressBookId, $cardUri, $cardData)
-       {
-               $x = explode("-", $addressBookId);
-
-               $etag = md5($cardData);
-               q("INSERT INTO %s%scards (carddata, uri, lastmodified, namespace, namespace_id, etag, size) VALUES ('%s', '%s', %d, %d, '%s', %d)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), dbesc($cardUri), time(), IntVal($x[0]), IntVal($x[1]), $etag, strlen($cardData)
-               );
-
-               q('UPDATE %s%saddressbooks_phone SET ctag = ctag + 1 WHERE id = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-
-               return '"' . $etag . '"';
-
-       }
-
-       /**
-        * Updates a card.
-        *
-        * The addressbook id will be passed as the first argument. This is the
-        * same id as it is returned from the getAddressbooksForUser method.
-        *
-        * The cardUri is a base uri, and doesn't include the full path. The
-        * cardData argument is the vcard body, and is passed as a string.
-        *
-        * It is possible to return an ETag from this method. This ETag should
-        * match that of the updated resource, and must be enclosed with double
-        * quotes (that is: the string itself must contain the actual quotes).
-        *
-        * You should only return the ETag if you store the carddata as-is. If a
-        * subsequent GET request on the same card does not have the same body,
-        * byte-by-byte and you did return an ETag here, clients tend to get
-        * confused.
-        *
-        * If you don't return an ETag, you can just return null.
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @param string $cardData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return string|null
-        */
-       public function updateCard($addressBookId, $cardUri, $cardData)
-       {
-               $x = explode("-", $addressBookId);
-
-               $etag = md5($cardData);
-               q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1])
-               );
-
-               q('UPDATE %s%saddressbooks_phone SET ctag = ctag + 1 WHERE id = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-
-               return '"' . $etag . '"';
-       }
-
-       /**
-        * Deletes a card
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return bool
-        */
-       public function deleteCard($addressBookId, $cardUri)
-       {
-               $x = explode("-", $addressBookId);
-
-               q("DELETE FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
-               q('UPDATE %s%saddressbooks_phone SET ctag = ctag + 1 WHERE id = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-
-               return true;
-       }
-}
diff --git a/dav/common/dav_carddav_backend_virtual.inc.php b/dav/common/dav_carddav_backend_virtual.inc.php
new file mode 100644 (file)
index 0000000..f81a0a4
--- /dev/null
@@ -0,0 +1,269 @@
+<?php
+
+abstract class Sabre_CardDAV_Backend_Virtual extends Sabre_CardDAV_Backend_Common
+{
+
+       /**
+        * @static
+        * @abstract
+        * @param int $addressbookId
+        * @param string $uri
+        * @return array
+        */
+       /*
+       abstract public function getItemsByUri($addressbookId, $uri);
+    */
+
+       /**
+        * @static
+        * @param int $addressbookId
+        */
+       static public function invalidateCache($addressbookId) {
+               q("UPDATE %s%saddressbooks SET `needs_rebuild` = 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId));
+       }
+
+       /**
+        * @static
+        * @abstract
+        * @param int $addressbookId
+        * @param bool $force
+        */
+       static abstract protected function createCache_internal($addressbookId, $force = false);
+
+       /**
+        * @param int $addressbookId
+        * @param null|array $addressbook
+        * @param bool $force
+        */
+       public function createCache($addressbookId, $addressbook = null, $force = false) {
+               $addressbookId = IntVal($addressbookId);
+
+               if (!$addressbook) {
+                       $add = q("SELECT `needs_rebuild`, `uri` FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+                       $addressbook = $add[0];
+               }
+               if ($addressbook["needs_rebuild"] == 1 || $force) {
+                       static::createCache_internal($addressbookId, $force);
+                       q("UPDATE %s%saddressbooks SET `needs_rebuild` = 0, `ctag` = `ctag` + 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+               }
+       }
+
+       /**
+        * @static
+        * @abstract
+        * @param int $addressbookId
+        * @param int $contactId
+        * @param bool $force
+        */
+       static abstract protected function createCardCache($addressbookId, $contactId, $force = false);
+
+       /**
+        * @param int $addressbookId
+        * @return array
+        */
+       public function getCards($addressbookId)
+       {
+               $addressbookId = IntVal($addressbookId);
+               $add = q("SELECT * FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+               if ($add[0]["needs_rebuild"]) {
+                       static::createCache_internal($addressbookId);
+                       q("UPDATE %s%saddressbooks SET `needs_rebuild` = 0, `ctag` = `ctag` + 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+                       $add[0]["needs_rebuild"] = 0;
+                       $add[0]["ctag"]++;
+               }
+
+               $ret = array();
+               $x = q("SELECT * FROM %s%saddressbookobjects WHERE `addressbook_id` = %d AND `manually_deleted` = 0", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+               foreach ($x as $y) $ret[] = self::getCard($addressbookId, $add[0]["uri"], $add[0], $y);
+
+               return $ret;
+       }
+
+
+       /**
+        * Replaces the x-prop_name value. Replaces the prop_name value IF the old value is the same as the old value of x-prop_name (meaning: the user has not manually changed it)
+        *
+        * @param Sabre\VObject\Component $component
+        * @param string $prop_name
+        * @param string $prop_value
+        * @param array $parameters
+        * @return void
+        */
+       static public function card_set_automatic_value(&$component, $prop_name, $prop_value, $parameters = array()) {
+               $automatic = $component->select("X-" . $prop_name);
+               $curr = $component->select($prop_name);
+
+               if (count($automatic) == 0) {
+                       $prop = new Sabre\VObject\Property('X-' . $prop_name, $prop_value);
+                       foreach ($parameters as $key=>$val) $prop->add($key, $val);
+                       $component->children[] = $prop;
+
+                       if (count($curr) == 0) {
+                               $prop = new Sabre\VObject\Property($prop_name, $prop_value);
+                               foreach ($parameters as $key=>$val) $prop->add($key, $val);
+                               $component->children[] = $prop;
+                       }
+
+               } else foreach ($automatic as $auto_prop) {
+                       /** @var Sabre\VObject\Property $auto_prop */
+                       /** @var Sabre\VObject\Property $actual_prop */
+                       foreach ($curr as $actual_prop) {
+                               if ($auto_prop->value == $actual_prop->value) $actual_prop->setValue($prop_value);
+                       }
+                       $auto_prop->setValue($prop_value);
+               }
+       }
+
+
+
+       /**
+        * Deletes the x-prop_name value. Deletes the prop_name value IF the old value is the same as the old value of x-prop_name (meaning: the user has not manually changed it)
+        *
+        * @param Sabre\VObject\Component $component
+        * @param string $prop_name
+        * @param array $parameters
+        */
+       static public function card_del_automatic_value(&$component, $prop_name, $parameters = array()) {
+               // @TODO
+       }
+
+       /**
+        * @param int $addressbookId
+        * @param string $objectUri
+        * @param array $book
+        * @param array $obj
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return array
+        */
+       public function getCard($addressbookId, $objectUri, $book = null, $obj = null)
+       {
+               $addressbookId = IntVal($addressbookId);
+
+               if ($book == null) {
+                       $add = q("SELECT `needs_rebuild`, `uri` FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+                       $book = $add[0];
+               }
+               if ($book["needs_rebuild"] == 1) {
+                       static::createCache_internal($addressbookId);
+                       q("UPDATE %s%saddressbooks SET `needs_rebuild` = 0, `ctag` = `ctag` + 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId);
+                       $add[0]["needs_rebuild"] = 0;
+               }
+
+               if ($obj == null) {
+                       $r = q("SELECT * FROM %s%saddressbookobjects WHERE `uri` = '%s' AND `addressbook_id` = %d AND `manually_deleted` = 0",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($objectUri), IntVal($addressbookId));
+                       if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
+                       $obj = $r[0];
+                       if ($obj["needs_rebuild"] == 1) $obj = static::createCardCache($addressbookId, $obj["contact"]);
+               }
+
+               $ret = array(
+                       "id" => IntVal($obj["uri"]),
+                       "carddata" => $obj["carddata"],
+                       "uri" => $obj["uri"],
+                       "lastmodified" => $obj["lastmodified"],
+                       "addressbookid" => $addressbookId,
+                       "etag" => $obj["etag"],
+                       "size" => IntVal($obj["size"]),
+               );
+               return $ret;
+       }
+
+
+
+       /**
+        * @param string $principalUri
+        * @param string $addressbookUri
+        * @param array $properties
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       public function createAddressBook($principalUri, $addressbookUri, array $properties)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+       /**
+        * @param string $addressbookId
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return void
+        */
+       public function deleteAddressBook($addressbookId)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+
+       /**
+        * @param string $addressbookId
+        * @param string $objectUri
+        * @param string $cardData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return null|string|void
+        */
+       function createCard($addressbookId, $objectUri, $cardData)
+       {
+               throw new Sabre_DAV_Exception_Forbidden();
+       }
+
+       /**
+        * Updates a card.
+        *
+        * The addressbook id will be passed as the first argument. This is the
+        * same id as it is returned from the getAddressbooksForUser method.
+        *
+        * The cardUri is a base uri, and doesn't include the full path. The
+        * cardData argument is the vcard body, and is passed as a string.
+        *
+        * It is possible to return an ETag from this method. This ETag should
+        * match that of the updated resource, and must be enclosed with double
+        * quotes (that is: the string itself must contain the actual quotes).
+        *
+        * You should only return the ETag if you store the carddata as-is. If a
+        * subsequent GET request on the same card does not have the same body,
+        * byte-by-byte and you did return an ETag here, clients tend to get
+        * confused.
+        *
+        * If you don't return an ETag, you can just return null.
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @param string $cardData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return string|null
+        */
+       public function updateCard($addressBookId, $cardUri, $cardData)
+       {
+               echo "Die!"; die(); // @TODO
+               $x = explode("-", $addressBookId);
+
+               $etag = md5($cardData);
+               q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1])
+               );
+               q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
+
+               return '"' . $etag . '"';
+       }
+
+
+
+
+       /**
+        * Deletes a card
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return bool
+        */
+       public function deleteCard($addressBookId, $cardUri)
+       {
+               q("UPDATE %s%scards SET `manually_deleted` = 1 WHERE `addressbook_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId), dbesc($cardUri));
+               q('UPDATE %s%saddressbooks SET ctag = ctag + 1 WHERE `id` = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressBookId));
+
+               return true;
+       }
+
+
+}
\ No newline at end of file
index 8ad8da73e27c140f7fd09b77f692c1f3048e7245..b292074d6497082f4f704ba3165f69ca597f8f25 100644 (file)
@@ -1,14 +1,6 @@
 <?php
 
-/**
- * The UserCalenders class contains all calendars associated to one user 
- * 
- * @package Sabre
- * @subpackage CalDAV
- * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/) 
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
+
 class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
 
     /**
@@ -21,7 +13,7 @@ class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection
     /**
      * CalDAV backends
      * 
-     * @var array|Sabre_CalDAV_Backend_Abstract[]
+     * @var array|Sabre_CalDAV_Backend_Common[]
      */
     protected $caldavBackends;
 
@@ -36,7 +28,7 @@ class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection
      * Constructor 
      * 
      * @param Sabre_DAVACL_IPrincipalBackend $principalBackend
-     * @param array|Sabre_CalDAV_Backend_Abstract $caldavBackends
+     * @param array|Sabre_CalDAV_Backend_Common[] $caldavBackends
      * @param mixed $userUri 
      */
     public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, $caldavBackends, $userUri) {
@@ -130,7 +122,7 @@ class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection
         * Returns a single calendar, by name
         *
         * @param string $name
-        * @throws Sabre_DAV_Exception_FileNotFound
+        * @throws Sabre_DAV_Exception_NotFound
         * @todo needs optimizing
         * @return \Sabre_CalDAV_Calendar|\Sabre_DAV_INode
         */
@@ -141,7 +133,7 @@ class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection
                 return $child;
 
         }
-        throw new Sabre_DAV_Exception_FileNotFound('Calendar with name \'' . $name . '\' could not be found');
+        throw new Sabre_DAV_Exception_NotFound('Calendar with name \'' . $name . '\' could not be found');
 
     }
 
@@ -177,7 +169,6 @@ class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection
                $objs[] = new $calendar["calendar_class"]($this->principalBackend, $backend, $calendar);
                }
                }
-        //$objs[] = new Sabre_CalDAV_AnimexxUserZirkelCalendars($this->principalBackend, $this->caldavBackend, $this->username);
         return $objs;
 
     }
diff --git a/dav/common/dbclasses/dbclass.friendica.calendarobjects.class.php b/dav/common/dbclasses/dbclass.friendica.calendarobjects.class.php
deleted file mode 100644 (file)
index 509938b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-# Generated automatically - do not change!
-
-class DBClass_friendica_calendarobjects extends DBClass_animexx {
-       /** @var $PRIMARY_KEY array */
-       public $PRIMARY_KEY = array("id");
-
-       protected $SRC_TABLE = 'calendarobjects';
-       /** @var $calendardata string|null */
-       /** @var $uri string */
-       /** @var $lastmodified string|null */
-       /** @var $etag string */
-
-       public $calendardata, $uri, $lastmodified, $etag;
-
-       /** @var $id int */
-       /** @var $namespace int */
-       /** @var $namespace_id int */
-       /** @var $size int */
-
-       public $id, $namespace, $namespace_id, $size;
-
-
-       protected $_string_fields = array('calendardata', 'uri', 'lastmodified', 'etag');
-       protected $_int_fields = array('id', 'namespace', 'namespace_id', 'size');
-       protected $_null_fields = array('calendardata', 'lastmodified');
-}
diff --git a/dav/common/dbclasses/dbclass.friendica.calendars.class.php b/dav/common/dbclasses/dbclass.friendica.calendars.class.php
deleted file mode 100644 (file)
index b6d39f7..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-# Generated automatically - do not change!
-
-class DBClass_friendica_calendars extends DBClass_animexx {
-       /** @var $PRIMARY_KEY array */
-       public $PRIMARY_KEY = array("namespace", "namespace_id");
-
-       protected $SRC_TABLE = 'calendars';
-       /** @var $calendarcolor string */
-       /** @var $displayname string */
-       /** @var $timezone string */
-       /** @var $description string */
-
-       public $calendarcolor, $displayname, $timezone, $description;
-
-       /** @var $namespace int */
-       /** @var $namespace_id int */
-       /** @var $uid int */
-       /** @var $calendarorder int */
-       /** @var $ctag int */
-
-       public $namespace, $namespace_id, $uid, $calendarorder, $ctag;
-
-
-       protected $_string_fields = array('calendarcolor', 'displayname', 'timezone', 'description');
-       protected $_int_fields = array('namespace', 'namespace_id', 'uid', 'calendarorder', 'ctag');
-       protected $_null_fields = array();
-}
diff --git a/dav/common/dbclasses/dbclass.friendica.jqcalendar.class.php b/dav/common/dbclasses/dbclass.friendica.jqcalendar.class.php
deleted file mode 100644 (file)
index 6311107..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-# Generated automatically - do not change!
-
-class DBClass_friendica_jqcalendar extends DBClass_animexx {
-       /** @var $PRIMARY_KEY array */
-       public $PRIMARY_KEY = array("id");
-
-       protected $SRC_TABLE = 'jqcalendar';
-       /** @var $ical_uri string */
-       /** @var $ical_recurr_uri string */
-       /** @var $Subject string|null */
-       /** @var $Location string|null */
-       /** @var $Description string|null */
-       /** @var $StartTime string|null */
-       /** @var $EndTime string|null */
-       /** @var $Color string|null */
-       /** @var $RecurringRule string|null */
-
-       public $ical_uri, $ical_recurr_uri, $Subject, $Location, $Description, $StartTime, $EndTime, $Color, $RecurringRule;
-
-       /** @var $id int */
-       /** @var $uid int */
-       /** @var $namespace int */
-       /** @var $namespace_id int */
-       /** @var $permission_edit int */
-       /** @var $IsAllDayEvent int */
-
-       public $id, $uid, $namespace, $namespace_id, $permission_edit, $IsAllDayEvent;
-
-
-       protected $_string_fields = array('ical_uri', 'ical_recurr_uri', 'Subject', 'Location', 'Description', 'StartTime', 'EndTime', 'Color', 'RecurringRule');
-       protected $_int_fields = array('id', 'uid', 'namespace', 'namespace_id', 'permission_edit', 'IsAllDayEvent');
-       protected $_null_fields = array('Subject', 'Location', 'Description', 'StartTime', 'EndTime', 'Color', 'RecurringRule');
-}
diff --git a/dav/common/dbclasses/dbclass.friendica.notifications.class.php b/dav/common/dbclasses/dbclass.friendica.notifications.class.php
deleted file mode 100644 (file)
index 8f39a2b..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-# Generated automatically - do not change!
-
-class DBClass_friendica_notifications extends DBClass_animexx {
-       /** @var $PRIMARY_KEY array */
-       public $PRIMARY_KEY = array("id");
-
-       protected $SRC_TABLE = 'notifications';
-       /** @var $ical_uri string */
-       /** @var $ical_recurr_uri string */
-       /** @var $alert_date string */
-       /** @var $rel_type string */
-
-       public $ical_uri, $ical_recurr_uri, $alert_date, $rel_type;
-
-       /** @var $id int */
-       /** @var $uid int */
-       /** @var $namespace int */
-       /** @var $namespace_id int */
-       /** @var $rel_value int */
-       /** @var $notified int */
-
-       public $id, $uid, $namespace, $namespace_id, $rel_value, $notified;
-
-       /** @var $REL_TYPE_VALUES array */
-       public static $REL_TYPE_VALUES = array('second', 'minute', 'hour', 'day', 'week', 'month', 'year');
-       public static $REL_TYPE_SECOND = 'second';
-       public static $REL_TYPE_MINUTE = 'minute';
-       public static $REL_TYPE_HOUR = 'hour';
-       public static $REL_TYPE_DAY = 'day';
-       public static $REL_TYPE_WEEK = 'week';
-       public static $REL_TYPE_MONTH = 'month';
-       public static $REL_TYPE_YEAR = 'year';
-
-
-       protected $_string_fields = array('ical_uri', 'ical_recurr_uri', 'alert_date', 'rel_type');
-       protected $_int_fields = array('id', 'uid', 'namespace', 'namespace_id', 'rel_value', 'notified');
-       protected $_null_fields = array();
-}
diff --git a/dav/common/dbclasses/dbclass_animexx.class.php b/dav/common/dbclasses/dbclass_animexx.class.php
deleted file mode 100644 (file)
index b08623e..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-class DBClass_animexx
-{
-       protected $_string_fields = array();
-       protected $_int_fields = array();
-       protected $_float_fields = array();
-       protected $_null_fields = array();
-
-       public $PRIMARY_KEY = array();
-       protected $SRC_TABLE = "";
-
-       /**
-        * @param $dbarray_or_id
-        * @throws Exception
-        */
-       function __construct($dbarray_or_id)
-       {
-               if (is_numeric($dbarray_or_id) && count($this->PRIMARY_KEY) == 1) {
-                       $dbarray_or_id = q("SELECT * FROM %s%s%s WHERE %s=%d",
-                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->SRC_TABLE, $this->PRIMARY_KEY[0], IntVal($dbarray_or_id)
-                       );
-                       if (count($dbarray_or_id) == 0) throw new Exception("Not found");
-                       $dbarray_or_id = $dbarray_or_id[0];
-               }
-               if (is_array($dbarray_or_id)) {
-                       foreach ($this->_string_fields as $field) {
-                               $this->$field = $dbarray_or_id[$field];
-                       }
-                       foreach ($this->_int_fields as $field) {
-                               $this->$field = IntVal($dbarray_or_id[$field]);
-                       }
-                       foreach ($this->_float_fields as $field) {
-                               $this->$field = FloatVal($dbarray_or_id[$field]);
-                       }
-               } else throw new Exception("Not found");
-       }
-
-       /**
-        * @return array
-        */
-       function toArray()
-       {
-               $arr = array();
-               foreach ($this->_string_fields as $field) $arr[$field] = $this->$field;
-               foreach ($this->_int_fields as $field) $arr[$field] = $this->$field;
-               foreach ($this->_float_fields as $field) $arr[$field] = $this->$field;
-               return $arr;
-       }
-}
diff --git a/dav/common/virtual_cal_source_backend.inc.php b/dav/common/virtual_cal_source_backend.inc.php
deleted file mode 100644 (file)
index 5549a6a..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-abstract class VirtualCalSourceBackend {
-
-       /**
-        * @static
-        * @param int $uid
-        * @param int $namespace
-        */
-       static public function invalidateCache($uid = 0, $namespace = 0) {
-               q("DELETE FROM %s%scache_synchronized WHERE `uid` = %d AND `namespace` = %d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid), IntVal($namespace));
-       }
-
-       /**
-        * @static
-        * @abstract
-        * @param int $uid
-        * @param int $namespace_id
-        */
-       static abstract function createCache($uid = 0, $namespace_id = 0);
-
-       /**
-        * @static
-        * @param int $uid
-        * @param int $namespace
-        * @return array
-        */
-       static public function getCachedItems($uid = 0, $namespace = 0) {
-               $uid = IntVal($uid);
-               $namespace = IntVal($namespace);
-               $r = q("SELECT COUNT(*) n FROM %s%scache_synchronized WHERE `uid` = %d AND `namespace` = %d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid), $namespace);
-
-               if ($r[0]["n"] == 0) self::createCache();
-
-               $r = q("SELECT * FROM %s%scal_virtual_object_cache WHERE `uid` = %d AND `namespace` = %d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $uid, $namespace);
-
-               return $r;
-       }
-
-       /**
-        * @static
-        * @abstract
-        * @param int $uid
-        * @param int $namespace_id
-        * @param string $date_from
-        * @param string $date_to
-        * @return array
-        */
-       abstract static public function getItemsByTime($uid = 0, $namespace_id = 0, $date_from = "", $date_to = "");
-
-       /**
-        * @static
-        * @abstract
-        * @param int $uid
-        * @param string $uri
-        * @return array
-        */
-       abstract static public function getItemsByUri($uid = 0, $uri);
-
-}
index 877d3852522e8ad08c5132902bea4f12355d021e..adcaf15f51bcc8504e4b230c8ab158b4c94a6da3 100644 (file)
@@ -16,16 +16,19 @@ function wdcal_edit_getStartEnd() {
 
 function wdcal_edit_checktime_startChanged() {
        "use strict";
+
        var time = wdcal_edit_getStartEnd();
        if (time.start.getTime() >= time.end.getTime()) {
                var newend = new Date(time.start.getTime() + 3600000);
                $("#cal_end_date").datepicker("setDate", newend);
                $.timePicker("#cal_end_time").setTime(newend);
        }
+       wdcal_edit_recur_recalc();
 }
 
 function wdcal_edit_checktime_endChanged() {
        "use strict";
+
        var time = wdcal_edit_getStartEnd();
        if (time.start.getTime() >= time.end.getTime()) {
                var newstart = new Date(time.end.getTime() - 3600000);
@@ -34,10 +37,34 @@ function wdcal_edit_checktime_endChanged() {
        }
 }
 
-function wdcal_edit_init(dateFormat) {
+function wdcal_edit_recur_recalc() {
+       "use strict";
+
+       var start = $("#cal_start_date").datepicker("getDate");
+       $(".rec_month_name").text($.datepicker._defaults.monthNames[start.getMonth()]);
+       $("#rec_yearly_day option[value=bymonthday]").text($("#rec_yearly_day option[value=bymonthday]").data("orig").replace("#num#", start.getDate()));
+       $("#rec_monthly_day option[value=bymonthday]").text($("#rec_monthly_day option[value=bymonthday]").data("orig").replace("#num#", start.getDate()));
+       var month = new Date(start.getFullYear(), start.getMonth() + 1, 0);
+       var monthlast = month.getDate() - start.getDate() + 1;
+       $("#rec_yearly_day option[value=bymonthday_neg]").text($("#rec_yearly_day option[value=bymonthday_neg]").data("orig").replace("#num#", monthlast));
+       $("#rec_monthly_day option[value=bymonthday_neg]").text($("#rec_monthly_day option[value=bymonthday_neg]").data("orig").replace("#num#", monthlast));
+       var wk = Math.ceil(start.getDate() / 7);
+       var wkname = $.datepicker._defaults.dayNames[start.getDay()];
+       $("#rec_yearly_day option[value=byday]").text($("#rec_yearly_day option[value=byday]").data("orig").replace("#num#", wk).replace("#wkday#", wkname));
+       $("#rec_monthly_day option[value=byday]").text($("#rec_monthly_day option[value=byday]").data("orig").replace("#num#", wk).replace("#wkday#", wkname));
+       var wk_inv = Math.ceil(monthlast / 7);
+       $("#rec_yearly_day option[value=byday_neg]").text($("#rec_yearly_day option[value=byday_neg]").data("orig").replace("#num#", wk_inv).replace("#wkday#", wkname));
+       $("#rec_monthly_day option[value=byday_neg]").text($("#rec_monthly_day option[value=byday_neg]").data("orig").replace("#num#", wk_inv).replace("#wkday#", wkname));
+}
+
+function wdcal_edit_init(dateFormat, base_path) {
        "use strict";
 
        $("#cal_color").colorPicker();
+       $("#color_override").on("click", function() {
+               if ($("#color_override").prop("checked")) $("#cal_color_holder").show();
+               else $("#cal_color_holder").hide();
+       });
 
        $("#cal_start_time").timePicker({ step: 15 }).on("change", wdcal_edit_checktime_startChanged);
        $("#cal_end_time").timePicker().on("change", wdcal_edit_checktime_endChanged);
@@ -49,6 +76,8 @@ function wdcal_edit_init(dateFormat) {
                "dateFormat": dateFormat
        }).on("change", wdcal_edit_checktime_endChanged);
 
+       $("#rec_until_date").datepicker({ "dateFormat": dateFormat });
+
        $("#notification").on("click change", function() {
                if ($(this).prop("checked")) $("#notification_detail").show();
                else ($("#notification_detail")).hide();
@@ -58,4 +87,135 @@ function wdcal_edit_init(dateFormat) {
                if ($(this).prop("checked")) $("#cal_end_time, #cal_start_time").hide();
                else $("#cal_end_time, #cal_start_time").show();
        }).change();
-}
\ No newline at end of file
+
+       $("#rec_frequency").on("click change", function() {
+               var val = $("#rec_frequency").val();
+               if (val == "") $("#rec_details").hide();
+               else $("#rec_details").show();
+
+               if (val == "daily") $(".rec_daily").show();
+               else $(".rec_daily").hide();
+
+               if (val == "weekly") $(".rec_weekly").show();
+               else $(".rec_weekly").hide();
+
+               if (val == "monthly") $(".rec_monthly").show();
+               else $(".rec_monthly").hide();
+
+               if (val == "yearly") $(".rec_yearly").show();
+               else $(".rec_yearly").hide();
+       }).change();
+
+       $("#rec_until_type").on("click change", function() {
+               var val = $("#rec_until_type").val();
+
+               if (val == "count") $("#rec_until_count").show();
+               else $("#rec_until_count").hide();
+
+               if (val == "date") $("#rec_until_date").show();
+               else $("#rec_until_date").hide();
+       }).change();
+
+       $("#rec_yearly_day option, #rec_monthly_day option").each(function() {
+               $(this).data("orig", $(this).text());
+       });
+
+       $("#new_alarm_adder a").click(function(ev) {
+               $("#new_alarm").val("1");
+               $("#noti_new_row").show();
+               $("#new_alarm_adder").hide();
+               ev.preventDefault();
+       });
+
+       wdcal_edit_recur_recalc();
+
+       $(document).on("click", ".exception_remover", function(ev) {
+               ev.preventDefault();
+               var $this = $(this),
+                       $par = $this.parents(".rec_exceptions");
+               $this.parents(".except").remove();
+               if ($par.find(".rec_exceptions_holder").children().length == 0) {
+                       $par.find(".rec_exceptions_holder").hide();
+                       $par.find(".rec_exceptions_none").show();
+               }
+       });
+
+       $(".exception_adder").click(function(ev) {
+               ev.preventDefault();
+
+               var exceptions = [];
+               $(".rec_exceptions .except input").each(function() {
+                       exceptions.push($(this).val());
+               });
+               var rec_weekly_byday = [];
+               $(".rec_weekly_byday:checked").each(function() {
+                       rec_weekly_byday.push($(this).val());
+               });
+               var rec_daily_byday = [];
+               $(".rec_daily_byday:checked").each(function() {
+                       rec_daily_byday.push($(this).val());
+               });
+               var opts = {
+                       "start_date": $("input[name=start_date]").val(),
+                       "start_time": $("input[name=start_time]").val(),
+                       "end_date": $("input[name=end_date]").val(),
+                       "end_time": $("input[name=end_time]").val(),
+                       "rec_frequency": $("#rec_frequency").val(),
+                       "rec_interval": $("#rec_interval").val(),
+                       "rec_until_type": $("#rec_until_type").val(),
+                       "rec_until_count": $("#rec_until_count").val(),
+                       "rec_until_date": $("#rec_until_date").val(),
+                       "rec_weekly_byday": rec_weekly_byday,
+                       "rec_daily_byday": rec_daily_byday,
+                       "rec_weekly_wkst": $("input[name=rec_weekly_wkst]:checked").val(),
+                       "rec_monthly_day": $("#rec_monthly_day").val(),
+                       "rec_yearly_day": $("#rec_yearly_day").val(),
+                       "rec_exceptions": exceptions
+               };
+               if ($("#cal_allday").prop("checked")) opts["allday"] = 1;
+               var $dial = $("<div id='exception_setter_dialog'>Loading...</div>");
+               $dial.appendTo("body");
+               $dial.dialog({
+                       "width": 400,
+                       "height": 300,
+                       "title": "Exceptions"
+               });
+               $dial.load(base_path + "getExceptionDates/", opts, function() {
+                       $dial.find(".exception_selector_link").click(function(ev2) {
+                               ev2.preventDefault();
+                               var ts = $(this).data("timestamp");
+                               var str = $(this).html();
+                               var $part = $("<div data-timestamp='" + ts + "' class='except'><input type='hidden' class='rec_exception' name='rec_exceptions[]' value='" + ts + "'><a href='#' class='exception_remover'>[remove]</a> " + str + "</div>");
+                               var found = false;
+                               $(".rec_exceptions_holder .except").each(function() {
+                                       if (!found && ts < $(this).data("timestamp")) {
+                                               found = true;
+                                               $part.insertBefore(this);
+                                       }
+                               });
+                               if (!found) $(".rec_exceptions_holder").append($part);
+                               $(".rec_exceptions .rec_exceptions_holder").show();
+                               $(".rec_exceptions .rec_exceptions_none").hide();
+
+                               $dial.dialog("destroy").remove();
+                       })
+               });
+       });
+}
+
+
+function wdcal_edit_calendars_start(dateFormat, base_path) {
+       "use strict";
+
+       $(".cal_color").colorPicker();
+
+       $(".delete_cal").click(function(ev) {
+               if (!confirm("Do you really want to delete this calendar? All events will be moved to another private calendar.")) ev.preventDefault();
+       });
+
+       $(".calendar_add_caller").click(function(ev) {
+               $(".cal_add_row").show();
+               $(this).parents("div").hide();
+               ev.preventDefault();
+       });
+}
index 194ae56642a8160e56bee2503f6679182fb36d6a..201917c0e151f9c1f8f146fb5438d14ba2416bfc 100644 (file)
@@ -5,8 +5,10 @@
 (function ($) {\r
        "use strict";\r
 \r
-       var __WDAY = new Array(i18n.xgcalendar.dateformat.sun, i18n.xgcalendar.dateformat.mon, i18n.xgcalendar.dateformat.tue, i18n.xgcalendar.dateformat.wed, i18n.xgcalendar.dateformat.thu, i18n.xgcalendar.dateformat.fri, i18n.xgcalendar.dateformat.sat);\r
-       var __MonthName = new Array(i18n.xgcalendar.dateformat.jan, i18n.xgcalendar.dateformat.feb, i18n.xgcalendar.dateformat.mar, i18n.xgcalendar.dateformat.apr, i18n.xgcalendar.dateformat.may, i18n.xgcalendar.dateformat.jun, i18n.xgcalendar.dateformat.jul, i18n.xgcalendar.dateformat.aug, i18n.xgcalendar.dateformat.sep, i18n.xgcalendar.dateformat.oct, i18n.xgcalendar.dateformat.nov, i18n.xgcalendar.dateformat.dec);\r
+       var __WDAY = $.datepicker._defaults.dayNamesShort;\r
+               //new Array(i18n.xgcalendar.dateformat.sun, i18n.xgcalendar.dateformat.mon, i18n.xgcalendar.dateformat.tue, i18n.xgcalendar.dateformat.wed, i18n.xgcalendar.dateformat.thu, i18n.xgcalendar.dateformat.fri, i18n.xgcalendar.dateformat.sat);\r
+       var __MonthName = $.datepicker._defaults.monthNamesShort;\r
+               //new Array(i18n.xgcalendar.dateformat.jan, i18n.xgcalendar.dateformat.feb, i18n.xgcalendar.dateformat.mar, i18n.xgcalendar.dateformat.apr, i18n.xgcalendar.dateformat.may, i18n.xgcalendar.dateformat.jun, i18n.xgcalendar.dateformat.jul, i18n.xgcalendar.dateformat.aug, i18n.xgcalendar.dateformat.sep, i18n.xgcalendar.dateformat.oct, i18n.xgcalendar.dateformat.nov, i18n.xgcalendar.dateformat.dec);\r
 \r
 \r
        function dateFormat(format) {\r
                        for (i = 0; i < l; i++) {\r
                                var $col = $container.find(".tgCol" + i);\r
                                for (var j = 0; j < events[i].length; j++) {\r
-                                       if (events[i][j].event["color"] && events[i][j].event["color"].match(/^#[0-9a-f]{6}$/i)) {\r
-                                               c = events[i][j].event["color"];\r
+                                       if (events[i][j].event["color"] && events[i][j].event["color"].match(/^[0-9a-f]{6}$/i)) {\r
+                                               c = "#" + events[i][j].event["color"];\r
                                        }\r
                                        else {\r
                                                c = option.std_color;\r
                function getTitle(event) {\r
                        var timeshow, eventshow;\r
                        var showtime = event["is_allday"] != 1;\r
-                       eventshow = event["subject"];\r
+                       eventshow = event["summary"];\r
                        var startformat = getymformat(event["start"], null, showtime, true);\r
                        var endformat = getymformat(event["end"], event["start"], showtime, true);\r
                        timeshow = dateFormat.call(event["start"], startformat) + " - " + dateFormat.call(event["end"], endformat);\r
                        var p = { bdcolor:theme[0], bgcolor2:theme[0], bgcolor1:theme[2], width:"70%", icon:"", title:"", data:"" };\r
                        p.starttime = pZero(e.st.hour) + ":" + pZero(e.st.minute);\r
                        p.endtime = pZero(e.et.hour) + ":" + pZero(e.et.minute);\r
-                       p.content = e.event["subject"];\r
+                       p.content = e.event["summary"];\r
                        p.title = getTitle(e.event);\r
                        var icons = [];\r
                        if (e.event["has_notification"] == 1) icons.push("<I class=\"cic cic-tmr\">&nbsp;</I>");\r
                        var p = { color:theme[2], title:"", extendClass:"", extendHTML:"", data:"" };\r
 \r
                        p.title = getTitle(e.event);\r
-                       p.id = "bbit_cal_event_" + e.event["uri"];\r
+                       p.id = "bbit_cal_event_" + e.event["jq_id"];\r
                        if (option.enableDrag && e.event["is_editable_quick"] == 1) {\r
                                p.eclass = "drag";\r
                        }\r
                        else {\r
-                               p.eclass = "cal_" + e.event["uri"];\r
+                               p.eclass = "cal_" + e.event["jq_id"];\r
                        }\r
                        p.eclass += " " + (e.event["is_editable"] ? "editable" : "not_editable");\r
                        var sp = "<span style=\"cursor: pointer\">{content}</span>";\r
                        }\r
                        var cen;\r
                        if (!e.allday && !sf) {\r
-                               cen = pZero(e.st.hour) + ":" + pZero(e.st.minute) + " " + e.event["subject"];\r
+                               cen = pZero(e.st.hour) + ":" + pZero(e.st.minute) + " " + e.event["summary"];\r
                        }\r
                        else {\r
-                               cen = e.event["subject"];\r
+                               cen = e.event["summary"];\r
                        }\r
                        var content = [];\r
                        if (cen.indexOf("Geburtstag:") == 0) {\r
                                        }\r
                                        if (option.eventItems[i]["start"] >= es) {\r
                                                for (var j = 0; j < jl; j++) {\r
-                                                       if (option.eventItems[i]["uri"] == events[j]["uri"] && option.eventItems[i]["start"] < start) {\r
+                                                       if (option.eventItems[i]["jq_id"] == events[j]["jq_id"] && option.eventItems[i]["start"] < start) {\r
                                                                events.splice(j, 1); //for duplicated event\r
                                                                jl--;\r
                                                                break;\r
                        $("#bbit-cs-buddle").css("visibility", "hidden");\r
                        var calid = $("#bbit-cs-id").val();\r
                        var param = [\r
-                               { "name":"calendarId", value:calid },\r
+                               { "name":"jq_id", value:calid },\r
                                { "name":"type", value:type}\r
                        ];\r
                        var de = rebyKey(calid, true);\r
                                var location = "";\r
                                if (data["location"] != "") location = data["location"] + ", ";\r
                                $("#bbit-cs-buddle-timeshow").html(location + ss.join(""));\r
-                               $bud.find(".bbit-cs-what").html(data["subject"]).attr("href", data["url_detail"]);\r
-                               $("#bbit-cs-id").val(data["uri"]);\r
+                               $bud.find(".bbit-cs-what").html(data["summary"]).attr("href", data["url_detail"]);\r
+                               $("#bbit-cs-id").val(data["jq_id"]);\r
                                $bud.data("cdata", data);\r
                                $bud.css({ "visibility":"visible", left:pos.left, top:pos.top });\r
 \r
                                        return false;\r
                                }\r
                                option.isloading = true;\r
-                               var id = data["uri"];\r
+                               var id = data["jq_id"];\r
                                var os = data["start"];\r
                                var od = data["end"];\r
                                var param = [\r
-                                       { "name":"calendarId", value:id },\r
+                                       { "name":"jq_id", value:id },\r
                                        { "name":"CalendarStartTime", value:Math.floor(start.getTime() / 1000) },\r
                                        { "name":"CalendarEndTime", value:Math.floor(end.getTime() / 1000) }\r
                                ];\r
                                temparr.push('<table class="cb-table"><tbody><tr><th class="cb-key">');\r
                                temparr.push(i18n.xgcalendar.time, ':</th><td class=cb-value><div id="bbit-cal-buddle-timeshow"></div></td></tr><tr><th class="cb-key">');\r
                                temparr.push(i18n.xgcalendar.content, ':</th><td class="cb-value"><div class="textbox-fill-wrapper"><div class="textbox-fill-mid"><input id="bbit-cal-what" class="textbox-fill-input"/></div></div><div class="cb-example">');\r
-                               temparr.push(i18n.xgcalendar.example, '</div></td></tr></tbody></table><input id="bbit-cal-start" type="hidden"/><input id="bbit-cal-end" type="hidden"/><input id="bbit-cal-allday" type="hidden"/><input id="bbit-cal-quickAddBTN" value="');\r
+                               temparr.push(i18n.xgcalendar.example, '</div></td></tr></tbody></table><input id="bbit-cal-start" type="hidden"/><input id="bbit-cal-end" type="hidden"/><input id="bbit-cal-allday" type="hidden"/><input value="');\r
                                temparr.push(i18n.xgcalendar.create_event, '" type="submit"/>&nbsp; <a href="" class="lk bbit-cal-editLink">');\r
                                temparr.push(i18n.xgcalendar.update_detail, ' <StrONG>&gt;&gt;</StrONG></SPAN></div></div></div><tr><td><div id="bl1" class="bubble-corner"><div class="bubble-sprite bubble-bl"></div></div><td><div class="bubble-bottom"></div><td><div id="br1" class="bubble-corner"><div class="bubble-sprite bubble-br"></div></div></tr></tbody></table><div id="bubbleClose1" class="bubble-closebutton"></div><div id="prong2" class="prong"><div class=bubble-sprite></div></div></div>');\r
                                temparr.push('</form>');\r
                                                        param[param.length] = option.extParam[pi];\r
                                                }\r
                                        }\r
-\r
                                        if (option.quickAddHandler && $.isFunction(option.quickAddHandler)) {\r
                                                option.quickAddHandler.call(this, param);\r
                                                $("#bbit-cal-buddle").css("visibility", "hidden");\r
                                                        ed = new Date(dateend),\r
                                                        diff = DateDiff("d", sd, ed);\r
                                                var newdata = {\r
-                                                       "uri":"",\r
-                                                       "subject":what,\r
+                                                       "jq_id":"",\r
+                                                       "ev_id":"",\r
+                                                       "summary":what,\r
                                                        "start":sd,\r
                                                        "end":ed,\r
                                                        "is_allday":(allday == "1" ? 1 : 0),\r
                        $("#bbit-cal-start").val(start.getTime());\r
                        $("#bbit-cal-end").val(end.getTime());\r
 \r
-                       var addurl = option.baseurl + "new/?start=" + Math.floor($("#bbit-cal-start").val() / 1000) + "&end=" + Math.floor($("#bbit-cal-end").val() / 1000) + "&isallday=" + (isallday ? "1" : "0");\r
+                       var addurl = option.baseurl + "new/?start=" + Math.floor($("#bbit-cal-start").val() / 1000) + "&end=" + Math.floor($("#bbit-cal-end").val() / 1000) +\r
+                               "&isallday=" + (isallday ? "1" : "0") + "&title=";\r
                        buddle.find(".bbit-cal-editLink").attr("href", addurl);\r
 \r
                        buddle.css({ "visibility":"visible", left:off.left, top:off.top });\r
                        calwhat.blur().focus(); //add 2010-01-26 blur() fixed chrome \r
-                       $(document).one("mousedown", function () {\r
+                       $(document).on("mousedown", function () {\r
                                $("#bbit-cal-buddle").css("visibility", "hidden");\r
                                releasedragevent();\r
                        });\r
+                       $(document).on("keyup", "#bbit-cal-what", function() {\r
+                               buddle.find(".bbit-cal-editLink").attr("href", addurl + encodeURIComponent($("#bbit-cal-what").val()));\r
+                       });\r
                        return false;\r
                }\r
 \r
                                var sl = option.eventItems.length;\r
                                var i = -1;\r
                                for (var j = 0; j < sl; j++) {\r
-                                       if (option.eventItems[j]["uri"] == key) {\r
+                                       if (option.eventItems[j]["jq_id"] == key) {\r
                                                i = j;\r
                                                break;\r
                                        }\r
                                                                        d.target.hide();\r
                                                                        ny = gP(gh.sh, gh.sm);\r
                                                                        d.top = ny;\r
-                                                                       tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], false, false, data["color"]);\r
+                                                                       tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], false, false, data["color"]);\r
                                                                        cpwrap = $("<div class='ca-evpi drag-chip-wrapper' style='top:" + ny + "px'/>").html(tempdata);\r
                                                                        evid = ".tgOver" + d.target.parent().data("col");\r
                                                                        $gridcontainer.find(evid).append(cpwrap);\r
                                                                                //log.info("ny=" + ny);\r
                                                                                gh = gW(ny, ny + d.h);\r
                                                                                //log.info("sh=" + gh.sh + ",sm=" + gh.sm);\r
-                                                                               tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], false, false, data["color"]);\r
+                                                                               tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], false, false, data["color"]);\r
                                                                                d.cpwrap.css("top", ny + "px").html(tempdata);\r
                                                                        }\r
                                                                        d.ny = ny;\r
                                                                        d.target.hide();\r
                                                                        ny = gP(gh.sh, gh.sm);\r
                                                                        d.top = ny;\r
-                                                                       tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], "100%", true, data["color"]);\r
+                                                                       tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], "100%", true, data["color"]);\r
                                                                        cpwrap = $("<div class='ca-evpi drag-chip-wrapper' style='top:" + ny + "px'/>").html(tempdata);\r
                                                                        evid = ".tgOver" + d.target.parent().data("col");\r
                                                                        $gridcontainer.find(evid).append(cpwrap);\r
                                                                        nh = pnh > 1 ? nh - pnh + Math.ceil(option.hour_height / 2) : nh - pnh;\r
                                                                        if (d.nh != nh) {\r
                                                                                gh = gW(d.top, d.top + nh);\r
-                                                                               tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], "100%", true, data["color"]);\r
+                                                                               tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], "100%", true, data["color"]);\r
                                                                                d.cpwrap.html(tempdata);\r
                                                                        }\r
                                                                        d.nh = nh;\r
index 261a7fb76e15a3b1e9070db2fb75943802bd3c7e..e043ddee797589fb9355f1b15aa077b07118fc36 100644 (file)
@@ -7,26 +7,7 @@ var i18n = $.extend({}, i18n || {}, {
             "year_index": 2,\r
             "month_index": 1,\r
             "day_index": 0,\r
-            "day": "d",\r
-            "sun": "So",\r
-            "mon": "Mo",\r
-            "tue": "Di",\r
-            "wed": "Mi",\r
-            "thu": "Do",\r
-            "fri": "Fr",\r
-            "sat": "Sa",\r
-            "jan": "Jan",\r
-            "feb": "Feb",\r
-            "mar": "Mär",\r
-            "apr": "Apr",\r
-            "may": "Mai",\r
-            "jun": "Jun",\r
-            "jul": "Jul",\r
-            "aug": "Aug",\r
-            "sep": "Sep",\r
-            "oct": "Okt",\r
-            "nov": "Nov",\r
-            "dec": "Dez"\r
+            "day": "d"\r
         },\r
         "no_implemented": "Nicht eingebaut",\r
         "to_date_view": "Zum aktuellen Datum gehen",\r
index 3f305a736517d0d2cb5953129179df03b65b46e2..6a34110d3c525e1cb7ef99942aacaf62d01cf0a2 100644 (file)
@@ -7,26 +7,7 @@ var i18n = $.extend({}, i18n || {}, {
                        "year_index": 2,
                        "month_index": 1,
                        "day_index": 0,
-                       "day": "d",
-                       "sun": "Su",
-                       "mon": "Mo",
-                       "tue": "Tu",
-                       "wed": "Mi",
-                       "thu": "Th",
-                       "fri": "Fr",
-                       "sat": "Sa",
-                       "jan": "Jan",
-                       "feb": "Feb",
-                       "mar": "Mar",
-                       "apr": "Apr",
-                       "may": "May",
-                       "jun": "Jun",
-                       "jul": "Jul",
-                       "aug": "Aug",
-                       "sep": "Sep",
-                       "oct": "Oct",
-                       "nov": "Nov",
-                       "dec": "Dec"
+                       "day": "d"
                },
                "no_implemented": "Not implemented",
                "to_date_view": "Go to today",
diff --git a/dav/common/wdcal_backend.inc.php b/dav/common/wdcal_backend.inc.php
new file mode 100644 (file)
index 0000000..330f298
--- /dev/null
@@ -0,0 +1,238 @@
+<?php
+
+
+
+
+/**
+ * @param mixed $obj
+ * @return string
+ */
+function wdcal_jsonp_encode($obj)
+{
+       $str = json_encode($obj);
+       if (isset($_REQUEST["callback"])) {
+               $str = $_REQUEST["callback"] . "(" . $str . ")";
+       }
+       return $str;
+}
+
+
+/**
+ * @param string $day
+ * @param int $weekstartday
+ * @param int $num_days
+ * @param string $type
+ * @return array
+ */
+function wdcal_get_list_range_params($day, $weekstartday, $num_days, $type)
+{
+       $phpTime = IntVal($day);
+       switch ($type) {
+               case "month":
+                       $st = mktime(0, 0, 0, date("m", $phpTime), 1, date("Y", $phpTime));
+                       $et = mktime(0, 0, -1, date("m", $phpTime) + 1, 1, date("Y", $phpTime));
+                       break;
+               case "week":
+                       //suppose first day of a week is monday
+                       $monday = date("d", $phpTime) - date('N', $phpTime) + 1;
+                       //echo date('N', $phpTime);
+                       $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
+                       $et = mktime(0, 0, -1, date("m", $phpTime), $monday + 7, date("Y", $phpTime));
+                       break;
+               case "multi_days":
+                       //suppose first day of a week is monday
+                       $monday = date("d", $phpTime) - date('N', $phpTime) + $weekstartday;
+                       //echo date('N', $phpTime);
+                       $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
+                       $et = mktime(0, 0, -1, date("m", $phpTime), $monday + $num_days, date("Y", $phpTime));
+                       break;
+               case "day":
+                       $st = mktime(0, 0, 0, date("m", $phpTime), date("d", $phpTime), date("Y", $phpTime));
+                       $et = mktime(0, 0, -1, date("m", $phpTime), date("d", $phpTime) + 1, date("Y", $phpTime));
+                       break;
+               default:
+                       return array(0, 0);
+       }
+       return array($st, $et);
+}
+
+
+/**
+ * @param Sabre_DAV_Server $server
+ * @param string $right
+ * @return null|Sabre_CalDAV_Calendar
+ */
+function wdcal_print_feed_getCal(&$server, $right)
+{
+       $cals     = dav_get_current_user_calendars($server, $right);
+       $calfound = null;
+       for ($i = 0; $i < count($cals) && $calfound === null; $i++) {
+               $prop = $cals[$i]->getProperties(array("id"));
+               if (isset($prop["id"]) && (!isset($_REQUEST["cal"]) || in_array($prop["id"], $_REQUEST["cal"]))) $calfound = $cals[$i];
+       }
+       return $calfound;
+}
+
+
+/**
+ *
+ */
+function wdcal_print_feed($base_path = "")
+{
+       $server = dav_create_server(true, true, false);
+
+       $ret = null;
+
+       $method = $_GET["method"];
+       switch ($method) {
+               case "add":
+                       $cs = wdcal_print_feed_getCal($server, DAV_ACL_WRITE);
+                       if ($cs == null) {
+                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                         'Msg'       => t('No access')));
+                               killme();
+                       }
+                       try {
+                               $item      = dav_create_empty_vevent();
+                               $component = dav_get_eventComponent($item);
+                               $component->add("SUMMARY", icalendar_sanitize_string(dav_compat_parse_text_serverside("CalendarTitle")));
+
+                               if (isset($_REQUEST["allday"])) $type = Sabre\VObject\Property\DateTime::DATE;
+                               else $type = Sabre\VObject\Property\DateTime::LOCALTZ;
+
+                               $datetime_start = new Sabre\VObject\Property\DateTime("DTSTART");
+                               $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarStartTime"]))), $type);
+                               $datetime_end = new Sabre\VObject\Property\DateTime("DTEND");
+                               $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarEndTime"]))), $type);
+
+                               $component->add($datetime_start);
+                               $component->add($datetime_end);
+
+                               $uid  = $component->__get("UID");
+                               $data = $item->serialize();
+
+                               $cs->createFile($uid . ".ics", $data);
+
+                               $ret = array(
+                                       'IsSuccess' => true,
+                                       'Msg'       => 'add success',
+                                       'Data'      => $uid . ".ics",
+                               );
+
+                       } catch (Exception $e) {
+                               $ret = array(
+                                       'IsSuccess' => false,
+                                       'Msg'       => $e->__toString(),
+                               );
+                       }
+                       break;
+               case "list":
+                       $weekstartday = (isset($_REQUEST["weekstartday"]) ? IntVal($_REQUEST["weekstartday"]) : 1); // 1 = Monday
+                       $num_days     = (isset($_REQUEST["num_days"]) ? IntVal($_REQUEST["num_days"]) : 7);
+                       $ret          = null;
+
+                       $date          = wdcal_get_list_range_params($_REQUEST["showdate"], $weekstartday, $num_days, $_REQUEST["viewtype"]);
+                       $ret           = array();
+                       $ret['events'] = array();
+                       $ret["issort"] = true;
+                       $ret["start"]  = $date[0];
+                       $ret["end"]    = $date[1];
+                       $ret['error']  = null;
+
+                       $cals = dav_get_current_user_calendars($server, DAV_ACL_READ);
+                       foreach ($cals as $cal) {
+                               $prop = $cal->getProperties(array("id"));
+                               if (isset($prop["id"]) && (!isset($_REQUEST["cal"]) || in_array($prop["id"], $_REQUEST["cal"]))) {
+                                       $backend       = wdcal_calendar_factory_by_id($prop["id"]);
+                                       $events        = $backend->listItemsByRange($prop["id"], $date[0], $date[1], $base_path);
+                                       $ret["events"] = array_merge($ret["events"], $events);
+                               }
+                       }
+
+                       $tmpev = array();
+                       foreach ($ret["events"] as $e) {
+                               if (!isset($tmpev[$e["start"]])) $tmpev[$e["start"]] = array();
+                               $tmpev[$e["start"]][] = $e;
+                       }
+                       ksort($tmpev);
+                       $ret["events"] = array();
+                       foreach ($tmpev as $e) foreach ($e as $f) $ret["events"][] = $f;
+
+                       break;
+               case "update":
+                       $r = q("SELECT `calendarobject_id`, `calendar_id` FROM %s%sjqcalendar WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["jq_id"]));
+                       if (count($r) != 1) {
+                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                         'Msg'       => t('No access')));
+                               killme();
+                       }
+                       try {
+                               $cs      = dav_get_current_user_calendar_by_id($server, $r[0]["calendar_id"], DAV_ACL_READ);
+                               $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($r[0]["calendarobject_id"]);
+
+                               $vObject   = dav_get_current_user_calendarobject($server, $cs, $obj_uri["uri"], DAV_ACL_WRITE);
+                               $component = dav_get_eventComponent($vObject);
+
+                               if (!$component) {
+                                       echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                                 'Msg'       => t('No access')));
+                                       killme();
+                               }
+
+                               if (isset($_REQUEST["allday"])) $type = Sabre\VObject\Property\DateTime::DATE;
+                               else $type = Sabre\VObject\Property\DateTime::LOCALTZ;
+
+                               $datetime_start = new Sabre\VObject\Property\DateTime("DTSTART");
+                               $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarStartTime"]))), $type);
+                               $datetime_end = new Sabre\VObject\Property\DateTime("DTEND");
+                               $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarEndTime"]))), $type);
+
+                               $component->__unset("DTSTART");
+                               $component->__unset("DTEND");
+                               $component->add($datetime_start);
+                               $component->add($datetime_end);
+
+                               $data = $vObject->serialize();
+                               /** @var Sabre_CalDAV_CalendarObject $child  */
+                               $child = $cs->getChild($obj_uri["uri"]);
+                               $child->put($data);
+
+                               $ret = array(
+                                       'IsSuccess' => true,
+                                       'Msg'       => 'Succefully',
+                               );
+                       } catch (Exception $e) {
+                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                         'Msg'       => t('No access')));
+                               killme();
+                       }
+                       break;
+               case "remove":
+                       $r = q("SELECT `calendarobject_id`, `calendar_id` FROM %s%sjqcalendar WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["jq_id"]));
+                       if (count($r) != 1) {
+                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                         'Msg'       => t('No access')));
+                               killme();
+                       }
+                       try {
+                               $cs      = dav_get_current_user_calendar_by_id($server, $r[0]["calendar_id"], DAV_ACL_WRITE);
+                               $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($r[0]["calendarobject_id"]);
+                               $child   = $cs->getChild($obj_uri["uri"]);
+                               $child->delete();
+
+                               $ret = array(
+                                       'IsSuccess' => true,
+                                       'Msg'       => 'Succefully',
+                               );
+                       } catch (Exception $e) {
+                               echo wdcal_jsonp_encode(array('IsSuccess' => false,
+                                                                                         'Msg'       => t('No access')));
+                               killme();
+                       }
+
+                       break;
+       }
+       echo wdcal_jsonp_encode($ret);
+       killme();
+}
+
diff --git a/dav/common/wdcal_cal_source.inc.php b/dav/common/wdcal_cal_source.inc.php
deleted file mode 100644 (file)
index 9db3402..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-
-
-abstract class AnimexxCalSource
-{
-
-       /**
-        * @var int $namespace_id
-        */
-       protected $namespace_id;
-
-       /**
-        * @var DBClass_friendica_calendars $calendarDb
-        */
-       protected $calendarDb;
-
-       /**
-        * @var int
-        */
-       protected $user_id;
-
-
-       /**
-        * @param int $user_id
-        * @param int $namespace_id
-        * @throws Sabre_DAV_Exception_NotFound
-        */
-       function __construct($user_id = 0, $namespace_id = 0)
-       {
-               $this->namespace_id = IntVal($namespace_id);
-               $this->user_id = IntVal($user_id);
-
-               $x                  = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uid` = %d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), $this->namespace_id, $this->user_id
-               );
-
-               if (count($x) != 1) throw new Sabre_DAV_Exception_NotFound("Not found");
-
-               try {
-                       $this->calendarDb = new DBClass_friendica_calendars($x[0]);
-               } catch (Exception $e) {
-                       throw new Sabre_DAV_Exception_NotFound("Not found");
-               }
-       }
-
-       /**
-        * @abstract
-        * @return int
-        */
-       public static abstract function getNamespace();
-
-       /**
-        * @abstract
-        * @param int $user
-        * @return array
-        */
-       public abstract function getPermissionsCalendar($user);
-
-       /**
-        * @abstract
-        * @param int $user
-        * @param string $item_uri
-        * @param string $recurrence_uri
-        * @param array|null $item_arr
-        * @return array
-        */
-       public abstract function getPermissionsItem($user, $item_uri, $recurrence_uri, $item_arr = null);
-
-       /**
-        * @param string $uri
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        */
-       public abstract function updateItem($uri, $start, $end, $subject = "", $allday = false, $description = "", $location = "", $color = null,
-                                                                               $timezone = "", $notification = true, $notification_type = null, $notification_value = null);
-
-
-       /**
-        * @abstract
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        * @return array
-        */
-       public abstract function addItem($start, $end, $subject, $allday = false, $description = "", $location = "", $color = null,
-                                                                        $timezone = "", $notification = true, $notification_type = null, $notification_value = null);
-
-
-       /**
-        * @param string $uri
-        */
-       public abstract function removeItem($uri);
-
-
-       /**
-        * @abstract
-        * @param string $sd
-        * @param string $ed
-        * @param string $base_path
-        * @return array
-        */
-       public abstract function listItemsByRange($sd, $ed, $base_path);
-
-
-       /**
-        * @abstract
-        * @param string $uri
-        * @return array
-        */
-       public abstract function getItemByUri($uri);
-
-
-       /**
-        * @param string $uri
-        * @return null|string
-        */
-       public function getItemDetailRedirect($uri) {
-               return null;
-       }
-
-}
diff --git a/dav/common/wdcal_cal_source_private.inc.php b/dav/common/wdcal_cal_source_private.inc.php
deleted file mode 100644 (file)
index cb5918e..0000000
+++ /dev/null
@@ -1,332 +0,0 @@
-<?php
-
-class AnimexxCalSourcePrivate extends AnimexxCalSource
-{
-
-       /**
-        * @return int
-        */
-       public static function getNamespace()
-       {
-               return CALDAV_NAMESPACE_PRIVATE;
-       }
-
-       /**
-        * @param int $user
-        * @return array
-        */
-       public function getPermissionsCalendar($user)
-       {
-               if ($user == $this->calendarDb->uid) return array("read"=> true, "write"=> true);
-               return array("read"=> false, "write"=> false);
-       }
-
-       /**
-        * @param int $user
-        * @param string $item_uri
-        * @param string $recurrence_uri
-        * @param null|array $item_arr
-        * @return array
-        */
-       public function getPermissionsItem($user, $item_uri, $recurrence_uri, $item_arr = null)
-       {
-               $cal_perm = $this->getPermissionsCalendar($user);
-               if (!$cal_perm["read"]) return array("read"=> false, "write"=> false);
-               if (!$cal_perm["write"]) array("read"=> true, "write"=> false);
-
-               if ($item_arr === null) {
-                       $x = q("SELECT `permission_edit` FROM %s%sjqcalendar WHERE `namespace` = %d AND `namespace_id` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), $this->namespace_id, dbesc($item_uri), dbesc($recurrence_uri)
-                       );
-                       if (!$x || count($x) == 0) return array("read"=> false, "write"=> false);
-                       return array("read"=> true, "write"=> ($x[0]["permission_edit"]));
-               } else {
-                       return array("read"=> true, "write"=> ($item_arr["permission_edit"]));
-               }
-
-       }
-
-       /**
-        * @param string $uri
-        * @throws Sabre_DAV_Exception_NotFound
-        */
-       public function removeItem($uri){
-               $obj_alt = q("SELECT * FROM %s%sjqcalendar WHERE namespace = %d AND namespace_id = %d AND ical_uri = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), $this->namespace_id, dbesc($uri));
-
-               if (count($obj_alt) == 0) throw new Sabre_DAV_Exception_NotFound("Not found");
-
-               $calendarBackend = new Sabre_CalDAV_Backend_Std();
-               $calendarBackend->deleteCalendarObject($this->getNamespace() . "-" . $this->namespace_id, $obj_alt[0]["ical_uri"]);
-       }
-
-       /**
-        * @param string $uri
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        * @throws Sabre_DAV_Exception_NotFound
-        * @throws Sabre_DAV_Exception_Conflict
-        */
-       public function updateItem($uri, $start, $end, $subject = "", $allday = false, $description = "", $location = "", $color = null, $timezone = "", $notification = true, $notification_type = null, $notification_value = null)
-       {
-               $a = get_app();
-
-               $usr_id = IntVal($this->calendarDb->uid);
-
-               $old = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `namespace` = %d AND `namespace_id` = %d AND `ical_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $usr_id, $this->getNamespace(), $this->namespace_id, dbesc($uri));
-               if (count($old) == 0) throw new Sabre_DAV_Exception_NotFound("Not Found 1");
-               $old_obj = new DBClass_friendica_jqcalendar($old[0]);
-
-               $calendarBackend = new Sabre_CalDAV_Backend_Std();
-               $obj             = $calendarBackend->getCalendarObject($this->getNamespace() . "-" . $this->namespace_id, $old_obj->ical_uri);
-               if (!$obj) throw new Sabre_DAV_Exception_NotFound("Not Found 2");
-
-               $v = new vcalendar();
-               $v->setConfig('unique_id', $a->get_hostname());
-
-               $v->setMethod('PUBLISH');
-               $v->setProperty("x-wr-calname", "AnimexxCal");
-               $v->setProperty("X-WR-CALDESC", "Animexx Calendar");
-               $v->setProperty("X-WR-TIMEZONE", $a->timezone);
-
-               $obj["calendardata"] = icalendar_sanitize_string($obj["calendardata"]);
-
-               $v->parse($obj["calendardata"]);
-               /** @var $vevent vevent */
-               $vevent = $v->getComponent('vevent');
-
-               if (trim($vevent->getProperty('uid')) . ".ics" != $old_obj->ical_uri)
-                       throw new Sabre_DAV_Exception_Conflict("URI != URI: " . $old_obj->ical_uri . " vs. " . trim($vevent->getProperty("uid")));
-
-               if ($end["year"] < $start["year"] ||
-                       ($end["year"] == $start["year"] && $end["month"] < $start["month"]) ||
-                       ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] < $start["day"]) ||
-                       ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] < $start["hour"]) ||
-                       ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] < $start["minute"]) ||
-                       ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] == $start["minute"] && $end["second"] < $start["second"])
-               ) {
-                       $end = $start;
-                       if ($end["hour"] < 23) $end["hour"]++;
-               } // DTEND muss <= DTSTART
-
-               if ($start["hour"] == 0 && $start["minute"] == 0 && $end["hour"] == 23 && $end["minute"] == 59) {
-                       $allday = true;
-               }
-
-               if ($allday) {
-                       $vevent->setDtstart($start["year"], $start["month"], $start["day"], FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
-                       $end = mktime(0, 0, 0, $end["month"], $end["day"], $end["year"]) + 3600 * 24;
-
-                       // If a DST change occurs on the current day
-                       $end += date("Z", ($end - 3600*24)) - date("Z", $end);
-
-                       $vevent->setDtend(date("Y", $end), date("m", $end), date("d", $end), FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
-               } else {
-                       $vevent->setDtstart($start["year"], $start["month"], $start["day"], $start["hour"], $start["minute"], $start["second"], FALSE, array("VALUE"=> "DATE-TIME"));
-                       $vevent->setDtend($end["year"], $end["month"], $end["day"], $end["hour"], $end["minute"], $end["second"], FALSE, array("VALUE"=> "DATE-TIME"));
-               }
-
-               if ($subject != "") {
-                       $vevent->setProperty('LOCATION', $location);
-                       $vevent->setProperty('summary', $subject);
-                       $vevent->setProperty('description', $description);
-               }
-               if (!is_null($color) && $color >= 0) $vevent->setProperty("X-ANIMEXX-COLOR", $color);
-
-               if (!$notification || $notification_type != null) {
-                       $vevent->deleteComponent("VALARM");
-
-                       if ($notification) {
-                               $valarm = new valarm();
-
-                               $valarm->setTrigger(
-                                       ($notification_type == "year" ? $notification_value : 0),
-                                       ($notification_type == "month" ? $notification_value : 0),
-                                       ($notification_type == "day" ? $notification_value : 0),
-                                       ($notification_type == "week" ? $notification_value : 0),
-                                       ($notification_type == "hour" ? $notification_value : 0),
-                                       ($notification_type == "minute" ? $notification_value : 0),
-                                       ($notification_type == "minute" ? $notification_value : 0),
-                                       true,
-                                       ($notification_value > 0)
-                               );
-                               $valarm->setProperty("ACTION", "DISPLAY");
-                               $valarm->setProperty("DESCRIPTION", $subject);
-
-                               $vevent->setComponent($valarm);
-                       }
-               }
-
-
-               $v->deleteComponent("vevent");
-               $v->setComponent($vevent, trim($vevent->getProperty("uid")));
-               $ical = $v->createCalendar();
-
-               $calendarBackend->updateCalendarObject($this->getNamespace() . "-" . $this->namespace_id, $old_obj->ical_uri, $ical);
-       }
-
-       /**
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        * @return array|string
-        */
-       public function addItem($start, $end, $subject, $allday = false, $description = "", $location = "", $color = null,
-                                                       $timezone = "", $notification = true, $notification_type = null, $notification_value = null)
-       {
-               $a = get_app();
-
-               $v = new vcalendar();
-               $v->setConfig('unique_id', $a->get_hostname());
-
-               $v->setProperty('method', 'PUBLISH');
-               $v->setProperty("x-wr-calname", "AnimexxCal");
-               $v->setProperty("X-WR-CALDESC", "Animexx Calendar");
-               $v->setProperty("X-WR-TIMEZONE", $a->timezone);
-
-               $vevent = dav_create_vevent($start, $end, $allday);
-               $vevent->setLocation(icalendar_sanitize_string($location));
-               $vevent->setSummary(icalendar_sanitize_string($subject));
-               $vevent->setDescription(icalendar_sanitize_string($description));
-
-               if (!is_null($color) && $color >= 0) $vevent->setProperty("X-ANIMEXX-COLOR", $color);
-
-               if ($notification && $notification_type == null) {
-                       if ($allday) {
-                               $notification_type  = "hour";
-                               $notification_value = 24;
-                       } else {
-                               $notification_type  = "minute";
-                               $notification_value = 60;
-                       }
-               }
-               if ($notification) {
-                       $valarm = new valarm();
-
-                       $valarm->setTrigger(
-                               ($notification_type == "year" ? $notification_value : 0),
-                               ($notification_type == "month" ? $notification_value : 0),
-                               ($notification_type == "day" ? $notification_value : 0),
-                               ($notification_type == "week" ? $notification_value : 0),
-                               ($notification_type == "hour" ? $notification_value : 0),
-                               ($notification_type == "minute" ? $notification_value : 0),
-                               ($notification_type == "second" ? $notification_value : 0),
-                               true,
-                               ($notification_value > 0)
-                       );
-                       $valarm->setAction("DISPLAY");
-                       $valarm->setDescription($subject);
-
-                       $vevent->setComponent($valarm);
-
-               }
-
-               $v->setComponent($vevent);
-               $ical   = $v->createCalendar();
-               $obj_id = trim($vevent->getProperty("UID"));
-
-               $calendarBackend = new Sabre_CalDAV_Backend_Std();
-               $calendarBackend->createCalendarObject($this->getNamespace() . "-" . $this->namespace_id, $obj_id . ".ics", $ical);
-
-               return $obj_id . ".ics";
-       }
-
-       private function jqcal2wdcal($row, $usr_id, $base_path) {
-               $evo             = new DBClass_friendica_jqcalendar($row);
-               $not             = q("SELECT COUNT(*) num FROM %s%snotifications WHERE `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($row["ical_uri"]), $row["ical_recurr_uri"]
-               );
-               $editable        = $this->getPermissionsItem($usr_id, $row["ical_uri"], $row["ical_recurr_uri"], $row);
-               $recurring       = (is_null($evo->RecurringRule) || $evo->RecurringRule == "" || $evo->RecurringRule == "NULL" ? 0 : 1);
-
-               $end = wdcal_mySql2PhpTime($evo->EndTime);
-               if ($evo->IsAllDayEvent) $end -= 1;
-
-               $arr             = array(
-                       "uri"               => $evo->ical_uri,
-                       "subject"           => escape_tags($evo->Subject),
-                       "start"             => wdcal_mySql2PhpTime($evo->StartTime),
-                       "end"               => $end,
-                       "is_allday"         => $evo->IsAllDayEvent,
-                       "is_moredays"       => 0,
-                       "is_recurring"      => $recurring,
-                       "color"             => (is_null($evo->Color) || $evo->Color == "" ? $this->calendarDb->calendarcolor : $evo->Color),
-                       "is_editable"       => ($editable ? 1 : 0),
-                       "is_editable_quick" => ($editable && !$recurring ? 1 : 0),
-                       "location"          => $evo->Location,
-                       "attendees"         => '',
-                       "has_notification"  => ($not[0]["num"] > 0 ? 1 : 0),
-                       "url_detail"        => $base_path . $evo->ical_uri . "/",
-                       "url_edit"          => $base_path . $evo->ical_uri . "/edit/",
-                       "special_type"      => "",
-               );
-               return $arr;
-       }
-
-       /**
-        * @param string $sd
-        * @param string $ed
-        * @param string $base_path
-        * @return array
-        */
-       public function listItemsByRange($sd, $ed, $base_path)
-       {
-
-               $usr_id = IntVal($this->calendarDb->uid);
-
-               $von           = wdcal_php2MySqlTime($sd);
-               $bis           = wdcal_php2MySqlTime($ed);
-
-               // @TODO Events, die früher angefangen haben, aber noch andauern
-               $evs = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `namespace` = %d AND `namespace_id` = %d AND `starttime` between '%s' and '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                       $usr_id, $this->getNamespace(), $this->namespace_id, dbesc($von), dbesc($bis));
-
-               $events = array();
-               foreach ($evs as $row) $events[] = $this->jqcal2wdcal($row, $usr_id, $base_path);
-
-               return $events;
-       }
-
-       /**
-        * @param string $uri
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return array
-        */
-       public function getItemByUri($uri)
-       {
-               $usr_id = IntVal($this->calendarDb->uid);
-               $evs = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `namespace` = %d AND `namespace_id` = %d AND `ical_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                       $usr_id, $this->getNamespace(), $this->namespace_id, dbesc($uri));
-               if (count($evs) == 0) throw new Sabre_DAV_Exception_NotFound();
-               return $this->jqcal2wdcal($evs[0], $usr_id);
-       }
-
-
-       /**
-        * @param string $uri
-        * @return string
-        */
-       public function getItemDetailRedirect($uri) {
-               return "/dav/wdcal/$uri/edit/";
-       }
-}
index 8d53edee9e218611d95b790806e32f72bf0a8ffd..c7b66fb1187c2f4a0d67ccf6b7a36cd9073e9edc 100644 (file)
@@ -40,6 +40,13 @@ abstract class wdcal_local
                return $format;
        }
 
+       /**
+        * @static
+        * @abstract
+        * @return string
+        */
+       abstract static function getLanguageCode();
+
        /**
         * @abstract
         * @static
@@ -77,6 +84,13 @@ abstract class wdcal_local
         */
        abstract function date_timestamp2local($ts);
 
+       /**
+        * @abstract
+        * @param int $ts
+        * @return string
+        */
+       abstract function date_timestamp2localDate($ts);
+
        /**
         * @abstract
         * @return int
@@ -119,6 +133,14 @@ abstract class wdcal_local
 
 class wdcal_local_us extends wdcal_local {
 
+       /**
+        * @static
+        * @return string
+        */
+       static function getLanguageCode() {
+               return "en";
+       }
+
        /**
         * @return string
         */
@@ -152,6 +174,14 @@ class wdcal_local_us extends wdcal_local {
                return date("m/d/Y H:i", $ts);
        }
 
+       /**
+        * @param int $ts
+        * @return string
+        */
+       function date_timestamp2localDate($ts) {
+               return date("l, F jS Y", $ts);
+       }
+
        /**
         * @return int
         */
@@ -198,6 +228,14 @@ class wdcal_local_us extends wdcal_local {
 
 class wdcal_local_de extends  wdcal_local {
 
+       /**
+        * @static
+        * @return string
+        */
+       static function getLanguageCode() {
+               return "de";
+       }
+
        /**
         * @return string
         */
@@ -231,6 +269,14 @@ class wdcal_local_de extends  wdcal_local {
                return date("d.m.Y H:i", $ts);
        }
 
+       /**
+        * @param int $ts
+        * @return string
+        */
+       function date_timestamp2localDate($ts) {
+               return date("l, j. F Y", $ts);
+       }
+
        /**
         * @return int
         */
diff --git a/dav/common/wdcal_edit.inc.php b/dav/common/wdcal_edit.inc.php
new file mode 100644 (file)
index 0000000..d507a90
--- /dev/null
@@ -0,0 +1,808 @@
+<?php
+
+/**
+ * @param wdcal_local $localization
+ * @param string $baseurl
+ * @param int $calendar_id
+ * @param int $uri
+ * @return string
+ */
+function wdcal_getEditPage_str(&$localization, $baseurl, $calendar_id, $uri)
+{
+       $server = dav_create_server(true, true, false);
+
+       if ($uri > 0) {
+               $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+               if (!$calendar) {
+                       $calendar  = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_READ);
+                       $calendars = array();
+               } else {
+                       $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+               }
+
+               if ($calendar == null) return "Calendar not found";
+
+               $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($uri);
+
+               $vObject   = dav_get_current_user_calendarobject($server, $calendar, $obj_uri["uri"], DAV_ACL_WRITE);
+               $component = dav_get_eventComponent($vObject);
+
+               if ($component == null) return t('Could not open component for editing');
+
+               /** @var Sabre\VObject\Property\DateTime $dtstart  */
+               $dtstart = $component->__get("DTSTART");
+               $event   = array(
+                       "id"            => IntVal($uri),
+                       "Summary"       => ($component->__get("SUMMARY") ? $component->__get("SUMMARY")->value : null),
+                       "StartTime"     => $dtstart->getDateTime()->getTimeStamp(),
+                       "EndTime"       => Sabre_CalDAV_Backend_Common::getDtEndTimeStamp($component),
+                       "IsAllDayEvent" => (strlen($dtstart->value) == 8),
+                       "Description"   => ($component->__get("DESCRIPTION") ? $component->__get("DESCRIPTION")->value : null),
+                       "Location"      => ($component->__get("LOCATION") ? $component->__get("LOCATION")->value : null),
+                       "Color"         => ($component->__get("X-ANIMEXX-COLOR") ? $component->__get("X-ANIMEXX-COLOR")->value : null),
+               );
+
+               $exdates             = $component->select("EXDATE");
+               $recurrentce_exdates = array();
+               /** @var Sabre\VObject\Property\MultiDateTime $x */
+               foreach ($exdates as $x) {
+                       /** @var DateTime $y */
+                       $z = $x->getDateTimes();
+                       foreach ($z as $y) $recurrentce_exdates[] = $y->getTimeStamp();
+               }
+
+               $notifications = array();
+               $alarms = $component->select("VALARM");
+               foreach ($alarms as $alarm)  {
+                       /** @var Sabre_VObject_Component_VAlarm $alarm */
+                       $action = $alarm->__get("ACTION")->value;
+                       $trigger = $alarm->__get("TRIGGER");
+
+                       if(isset($trigger['VALUE']) && strtoupper($trigger['VALUE']) !== 'DURATION') {
+                               notice("The notification of this event cannot be parsed");
+                               continue;
+                       }
+
+                       /** @var DateInterval $triggerDuration  */
+                       $triggerDuration = Sabre_VObject_DateTimeParser::parseDuration($trigger);
+                       $unit = "hour";
+                       $value = 1;
+                       if ($triggerDuration->s > 0) {
+                               $unit = "second";
+                               $value = $triggerDuration->s + $triggerDuration->i * 60 + $triggerDuration->h * 3600 + $triggerDuration->d * 3600 * 24; // @TODO support more than days?
+                       } elseif ($triggerDuration->i) {
+                               $unit = "minute";
+                               $value = $triggerDuration->i + $triggerDuration->h * 60 + $triggerDuration->d * 60 * 24;
+                       } elseif ($triggerDuration->h) {
+                               $unit = "hour";
+                               $value = $triggerDuration->h + $triggerDuration->d * 24;
+                       } elseif ($triggerDuration->d > 0) {
+                               $unit = "day";
+                               $value = $triggerDuration->d;
+                       }
+
+                       $rel = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'end' : 'start';
+
+
+                       $notifications[] = array(
+                               "action" => strtolower($action),
+                               "rel" => $rel,
+                               "trigger_unit" => $unit,
+                               "trigger_value" => $value,
+                       );
+               }
+
+               if ($component->select("RRULE")) $recurrence = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->__get("UID"));
+               else $recurrence = null;
+
+       } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) {
+               $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+               //$calendar  = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+
+               $event = array(
+                       "id"            => 0,
+                       "Summary"       => $_REQUEST["title"],
+                       "StartTime"     => InTVal($_REQUEST["start"]),
+                       "EndTime"       => IntVal($_REQUEST["end"]),
+                       "IsAllDayEvent" => $_REQUEST["isallday"],
+                       "Description"   => "",
+                       "Location"      => "",
+                       "Color"         => null,
+               );
+               if ($_REQUEST["isallday"]) {
+                       $notifications = array();
+               } else {
+                       $notifications = array(array("action" => "email", "rel" => "start", "trigger_unit" => "hour", "trigger_value" => 1));
+               }
+               $recurrence          = null;
+               $recurrentce_exdates = array();
+       } else {
+               $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+               //$calendar  = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+
+               $event               = array(
+                       "id"            => 0,
+                       "Summary"       => "",
+                       "StartTime"     => time(),
+                       "EndTime"       => time() + 3600,
+                       "IsAllDayEvent" => "0",
+                       "Description"   => "",
+                       "Location"      => "",
+                       "Color"         => null,
+               );
+               $notifications = array(array("action" => "email", "rel" => "start", "trigger_unit" => "hour", "trigger_value" => 1));
+               $recurrence          = null;
+               $recurrentce_exdates = array();
+       }
+
+       $postto = $baseurl . "/dav/wdcal/" . ($uri == 0 ? "new/" : $calendar_id . "/" . $uri . "/edit/");
+
+       $out = "<a href='" . $baseurl . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
+       $out .= "<form method='POST' action='$postto'>
+               <input type='hidden' name='form_security_token' value='" . get_form_security_token('caledit') . "'>\n";
+
+       $out .= "<h2>" . t("Event data") . "</h2>";
+
+       $out .= "<label for='calendar' class='block'>" . t("Calendar") . ":</label><select id='calendar' name='calendar' size='1'>";
+       $found   = false;
+       $cal_col = "aaaaaa";
+       foreach ($calendars as $cal) {
+               $prop = $cal->getProperties(array("id", DAV_DISPLAYNAME, DAV_CALENDARCOLOR));
+               $out .= "<option value='" . $prop["id"] . "' ";
+               if ($prop["id"] == $calendar_id) {
+                       $out .= "selected";
+                       $cal_col = $prop[DAV_CALENDARCOLOR];
+                       $found   = true;
+               } elseif (!$found) $cal_col = $prop[DAV_CALENDARCOLOR];
+               $out .= ">" . escape_tags($prop[DAV_DISPLAYNAME]) . "</option>\n";
+       }
+
+       $out .= "</select>";
+       $out .= "&nbsp; &nbsp; <label class='plain'><input type='checkbox' name='color_override' id='color_override' ";
+       if (!is_null($event["Color"])) $out .= "checked";
+       $out .= "> " . t("Special color") . ":</label>";
+       $out .= "<span id='cal_color_holder' ";
+       if (is_null($event["Color"])) $out .= "style='display: none;'";
+       $out .= "><input name='color' id='cal_color' value='" . (is_null($event["Color"]) ? "#" . $cal_col : escape_tags($event["Color"])) . "'></span>";
+       $out .= "<br>\n";
+
+       $out .= "<label class='block' for='cal_summary'>" . t("Subject") . ":</label>
+               <input name='summary' id='cal_summary' value=\"" . escape_tags($event["Summary"]) . "\"><br>\n";
+       $out .= "<label class='block' for='cal_allday'>Is All-Day event:</label><input type='checkbox' name='allday' id='cal_allday' " . ($event["IsAllDayEvent"] ? "checked" : "") . "><br>\n";
+
+       $out .= "<label class='block' for='cal_start_date'>" . t("Starts") . ":</label>";
+       $out .= "<input name='start_date' value='" . $localization->dateformat_datepicker_php($event["StartTime"]) . "' id='cal_start_date'>";
+       $out .= "<input name='start_time' value='" . date("H:i", $event["StartTime"]) . "' id='cal_start_time'>";
+       $out .= "<br>\n";
+
+       $out .= "<label class='block' for='cal_end_date'>" . t("Ends") . ":</label>";
+       $out .= "<input name='end_date' value='" . $localization->dateformat_datepicker_php($event["EndTime"]) . "' id='cal_end_date'>";
+       $out .= "<input name='end_time' value='" . date("H:i", $event["EndTime"]) . "' id='cal_end_time'>";
+       $out .= "<br>\n";
+
+       $out .= "<label class='block' for='cal_location'>" . t("Location") . ":</label><input name='location' id='cal_location' value=\"" . escape_tags($event["Location"]) . "\"><br>\n";
+
+       $out .= "<label class='block' for='event-desc-textarea'>" . t("Description") . ":</label> <textarea id='event-desc-textarea' name='wdcal_desc' style='vertical-align: top; width: 400px; height: 100px;'>" . escape_tags($event["Description"]) . "</textarea>";
+       $out .= "<br style='clear: both;'>";
+
+       $out .= "<h2>" . t("Recurrence") . "</h2>";
+
+       $out .= "<label class='block' for='rec_frequency'>" . t("Frequency") . ":</label> <select id='rec_frequency' name='rec_frequency' size='1'>";
+       $out .= "<option value=''>" . t("None") . "</option>\n";
+       $out .= "<option value='daily' ";
+       if ($recurrence && $recurrence->frequency == "daily") $out .= "selected";
+       $out .= ">" . t("Daily") . "</option>\n";
+       $out .= "<option value='weekly' ";
+       if ($recurrence && $recurrence->frequency == "weekly") $out .= "selected";
+       $out .= ">" . t("Weekly") . "</option>\n";
+       $out .= "<option value='monthly' ";
+       if ($recurrence && $recurrence->frequency == "monthly") $out .= "selected";
+       $out .= ">" . t("Monthly") . "</option>\n";
+       $out .= "<option value='yearly' ";
+       if ($recurrence && $recurrence->frequency == "yearly") $out .= "selected";
+       $out .= ">" . t("Yearly") . "</option>\n";
+       $out .= "</select><br>\n";
+       $out .= "<div id='rec_details'>";
+
+       $select = "<select id='rec_interval' name='rec_interval' size='1'>";
+       for ($i = 1; $i < 50; $i++) {
+               $select .= "<option value='$i' ";
+               if ($recurrence && $i == $recurrence->interval) $select .= "selected";
+               $select .= ">$i</option>\n";
+       }
+       $select .= "</select>";
+       $time = "<span class='rec_daily'>" . t("days") . "</span>";
+       $time .= "<span class='rec_weekly'>" . t("weeks") . "</span>";
+       $time .= "<span class='rec_monthly'>" . t("months") . "</span>";
+       $time .= "<span class='rec_yearly'>" . t("years") . "</span>";
+       $out .= "<label class='block'>" . t("Interval") . ":</label> " . str_replace(array("%select%", "%time%"), array($select, $time), t("All %select% %time%")) . "<br>";
+
+
+       $out .= "<div class='rec_daily'>";
+       $out .= "<label class='block'>" . t("Days") . ":</label>";
+       if ($recurrence && $recurrence->byDay) {
+               $byday = $recurrence->byDay;
+       } else {
+               $byday = array("MO", "TU", "WE", "TH", "FR", "SA", "SU");
+       }
+       if ($localization->getFirstDayOfWeek() == 0) {
+               $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SU' ";
+               if (in_array("SU", $byday)) $out .= "checked";
+               $out .= ">" . t("Sunday") . "</label> &nbsp; ";
+       }
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='MO' ";
+       if (in_array("MO", $byday)) $out .= "checked";
+       $out .= ">" . t("Monday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='TU' ";
+       if (in_array("TU", $byday)) $out .= "checked";
+       $out .= ">" . t("Tuesday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='WE' ";
+       if (in_array("WE", $byday)) $out .= "checked";
+       $out .= ">" . t("Wednesday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='TH' ";
+       if (in_array("TH", $byday)) $out .= "checked";
+       $out .= ">" . t("Thursday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='FR' ";
+       if (in_array("FR", $byday)) $out .= "checked";
+       $out .= ">" . t("Friday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SA' ";
+       if (in_array("SA", $byday)) $out .= "checked";
+       $out .= ">" . t("Saturday") . "</label> &nbsp; ";
+       if ($localization->getFirstDayOfWeek() != 0) {
+               $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SU' ";
+               if (in_array("SU", $byday)) $out .= "checked";
+               $out .= ">" . t("Sunday") . "</label> &nbsp; ";
+       }
+       $out .= "</div>";
+
+
+       $out .= "<div class='rec_weekly'>";
+       $out .= "<label class='block'>" . t("Days") . ":</label>";
+       if ($recurrence && $recurrence->byDay) {
+               $byday = $recurrence->byDay;
+       } else {
+               $days = array("MO", "TU", "WE", "TH", "FR", "SA", "SU");
+               $byday = array($days[date("N", $event["StartTime"]) - 1]);
+       }
+       if ($localization->getFirstDayOfWeek() == 0) {
+               $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SU' ";
+               if (in_array("SU", $byday)) $out .= "checked";
+               $out .= ">" . t("Sunday") . "</label> &nbsp; ";
+       }
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='MO' ";
+       if (in_array("MO", $byday)) $out .= "checked";
+       $out .= ">" . t("Monday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='TU' ";
+       if (in_array("TU", $byday)) $out .= "checked";
+       $out .= ">" . t("Tuesday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='WE' ";
+       if (in_array("WE", $byday)) $out .= "checked";
+       $out .= ">" . t("Wednesday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='TH' ";
+       if (in_array("TH", $byday)) $out .= "checked";
+       $out .= ">" . t("Thursday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='FR' ";
+       if (in_array("FR", $byday)) $out .= "checked";
+       $out .= ">" . t("Friday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SA' ";
+       if (in_array("SA", $byday)) $out .= "checked";
+       $out .= ">" . t("Saturday") . "</label> &nbsp; ";
+       if ($localization->getFirstDayOfWeek() != 0) {
+               $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SU' ";
+               if (in_array("SU", $byday)) $out .= "checked";
+               $out .= ">" . t("Sunday") . "</label> &nbsp; ";
+       }
+       $out .= "<br>";
+
+       $out .= "<label class='block'>" . t("First day of week:") . "</label>";
+       if ($recurrence && $recurrence->weekStart != "") $wkst = $recurrence->weekStart;
+       else {
+               if ($localization->getFirstDayOfWeek() == 0) $wkst = "SU";
+               else $wkst = "MO";
+       }
+       $out .= "<label class='plain'><input type='radio' name='rec_weekly_wkst' value='SU' ";
+       if ($wkst == "SU") $out .= "checked";
+       $out .= ">" . t("Sunday") . "</label> &nbsp; ";
+       $out .= "<label class='plain'><input type='radio' name='rec_weekly_wkst' value='MO' ";
+       if ($wkst == "MO") $out .= "checked";
+       $out .= ">" . t("Monday") . "</label><br>\n";
+
+       $out .= "</div>";
+
+       $monthly_rule = "";
+       if ($recurrence && ($recurrence->frequency == "monthly" || $recurrence->frequency == "yearly")) {
+               if (is_null($recurrence->byDay) && !is_null($recurrence->byMonthDay) && count($recurrence->byMonthDay) == 1) {
+                       $day = date("j", $event["StartTime"]);
+                       if ($recurrence->byMonthDay[0] == $day) $monthly_rule = "bymonthday";
+                       else {
+                               $lastday = date("t", $event["StartTime"]);
+                               if ($recurrence->byMonthDay[0] == -1 * ($lastday - $day + 1)) $monthly_rule = "bymonthday_neg";
+                       }
+               }
+               if (is_null($recurrence->byMonthDay) && !is_null($recurrence->byDay) && count($recurrence->byDay) == 1) {
+                       $num = IntVal($recurrence->byDay[0]);
+                       /*
+                       $dayMap = array(
+                               'SU' => 0,
+                               'MO' => 1,
+                               'TU' => 2,
+                               'WE' => 3,
+                               'TH' => 4,
+                               'FR' => 5,
+                               'SA' => 6,
+                       );
+                       if ($num == 0) {
+                               $num = 1;
+                               $weekday = $dayMap[$recurrence->byDay[0]];
+                       } else {
+                               $weekday = $dayMap[substr($recurrence->byDay[0], strlen($num))];
+                       }
+
+                       echo $num . " - " . $weekday;
+                       */
+                       if ($num > 0) $monthly_rule = "byday";
+                       if ($num < 0) $monthly_rule = "byday_neg";
+               }
+               if ($monthly_rule == "") notice("The recurrence of this event cannot be parsed");
+       }
+
+       $out .= "<div class='rec_monthly'>";
+       $out .= "<label class='block' for='rec_monthly_day'>" . t("Day of month") . ":</label>";
+       $out .= "<select id='rec_monthly_day' name='rec_monthly_day' size='1'>";
+       $out .= "<option value='bymonthday' ";
+       if ($monthly_rule == "bymonthday") $out .= "selected";
+       $out .= ">" . t("#num#th of each month") . "</option>\n";
+       $out .= "<option value='bymonthday_neg' ";
+       if ($monthly_rule == "bymonthday_neg") $out .= "selected";
+       $out .= ">" . t("#num#th-last of each month") . "</option>\n";
+       $out .= "<option value='byday' ";
+       if ($monthly_rule == "byday") $out .= "selected";
+       $out .= ">" . t("#num#th #wkday# of each month") . "</option>\n";
+       $out .= "<option value='byday_neg' ";
+       if ($monthly_rule == "byday_neg") $out .= "selected";
+       $out .= ">" . t("#num#th-last #wkday# of each month") . "</option>\n";
+       $out .= "</select>";
+       $out .= "</div>\n";
+
+       if ($recurrence && $recurrence->frequency == "yearly") {
+               if (count($recurrence->byMonth) != 1 || $recurrence->byMonth[0] != date("n", $event["StartTime"])) notice("The recurrence of this event cannot be parsed!");
+       }
+
+       $out .= "<div class='rec_yearly'>";
+       $out .= "<label class='block'>" . t("Month") . ":</label> <span class='rec_month_name'>#month#</span><br>\n";
+       $out .= "<label class='block' for='rec_yearly_day'>" . t("Day of month") . ":</label>";
+       $out .= "<select id='rec_yearly_day' name='rec_yearly_day' size='1'>";
+       $out .= "<option value='bymonthday' ";
+       if ($monthly_rule == "bymonthday") $out .= "selected";
+       $out .= ">" . t("#num#th of the given month") . "</option>\n";
+       $out .= "<option value='bymonthday_neg' ";
+       if ($monthly_rule == "bymonthday_neg") $out .= "selected";
+       $out .= ">" . t("#num#th-last of the given month") . "</option>\n";
+       $out .= "<option value='byday' ";
+       if ($monthly_rule == "byday") $out .= "selected";
+       $out .= ">" . t("#num#th #wkday# of the given month") . "</option>\n";
+       $out .= "<option value='byday_neg' ";
+       if ($monthly_rule == "byday_neg") $out .= "selected";
+       $out .= ">" . t("#num#th-last #wkday# of the given month") . "</option>\n";
+       $out .= "</select>";
+       $out .= "</div>\n";
+
+
+       if ($recurrence) {
+               $until = $recurrence->until;
+               $count = $recurrence->count;
+               if (is_a($until, "DateTime")) {
+                       /** @var DateTime $until */
+                       $rule_type        = "date";
+                       $rule_until_date  = $until->getTimestamp();
+                       $rule_until_count = 1;
+               } elseif ($count > 0) {
+                       $rule_type        = "count";
+                       $rule_until_date  = time();
+                       $rule_until_count = $count;
+               } else {
+                       $rule_type        = "infinite";
+                       $rule_until_date  = time();
+                       $rule_until_count = 1;
+               }
+       } else {
+               $rule_type        = "infinite";
+               $rule_until_date  = time();
+               $rule_until_count = 1;
+       }
+       $out .= "<label class='block' for='rec_until_type'>" . t("Repeat until") . ":</label> ";
+       $out .= "<select name='rec_until_type' id='rec_until_type' size='1'>";
+       $out .= "<option value='infinite' ";
+       if ($rule_type == "infinite") $out .= "selected";
+       $out .= ">" . t("Infinite") . "</option>\n";
+       $out .= "<option value='date' ";
+       if ($rule_type == "date") $out .= "selected";
+       $out .= ">" . t("Until the following date") . ":</option>\n";
+       $out .= "<option value='count' ";
+       if ($rule_type == "count") $out .= "selected";
+       $out .= ">" . t("Number of times") . ":</option>\n";
+       $out .= "</select>";
+
+       $out .= "<input name='rec_until_date' value='" . $localization->dateformat_datepicker_php($rule_until_date) . "' id='rec_until_date'>";
+       $out .= "<input name='rec_until_count' value='$rule_until_count' id='rec_until_count'><br>";
+
+       $out .= "<label class='block'>" . t("Exceptions") . ":</label><div class='rec_exceptions'>";
+       $out .= "<div class='rec_exceptions_none' ";
+       if (count($recurrentce_exdates) > 0) $out .= "style='display: none;'";
+       $out .= ">" . t("none") . "</div>";
+       $out .= "<div class='rec_exceptions_holder' ";
+       if (count($recurrentce_exdates) == 0) $out .= "style='display: none;'";
+       $out .= ">";
+
+       foreach ($recurrentce_exdates as $exdate) {
+               $out .= "<div data-timestamp='$exdate' class='except'><input type='hidden' class='rec_exception' name='rec_exceptions[]' value='$exdate'>";
+               $out .= "<a href='#' class='exception_remover'>[remove]</a> ";
+               $out .= $localization->date_timestamp2localDate($exdate);
+               $out .= "</div>\n";
+       }
+       $out .= "</div><div><a href='#' class='exception_adder'>[add]</a></div>";
+       $out .= "</div>\n";
+       $out .= "<br>\n";
+
+       $out .= "</div><br>";
+
+       $out .= "<h2>" . t("Notification") . "</h2>";
+
+       if (!$notifications) $notifications = array();
+       $notifications["new"] = array(
+               "action" => "email",
+               "trigger_value" => 60,
+               "trigger_unit" => "minute",
+               "rel" => "start",
+       );
+
+       foreach ($notifications as $index => $noti) {
+
+               $unparsable = false;
+               if (!in_array($noti["action"], array("email", "display"))) $unparsable = true;
+
+               $out .= "<div class='noti_holder' ";
+               if (!is_numeric($index) && $index == "new") $out .= "style='display: none;' id='noti_new_row'";
+               $out .= "><label class='block' for='noti_type_" . $index . "'>" . t("Notify by") . ":</label>";
+               $out .= "<select name='noti_type[$index]' size='1' id='noti_type_" . $index . "'>";
+               $out .= "<option value=''>- " . t("Remove") . " -</option>\n";
+               $out .= "<option value='email' "; if (!$unparsable && $noti["action"] == "email") $out .= "selected"; $out .= ">" . t("E-Mail") . "</option>\n";
+               $out .= "<option value='display' "; if (!$unparsable && $noti["action"] == "display") $out .= "selected"; $out .= ">" . t("On Friendica / Display") . "</option>\n";
+               //$out .= "<option value='other' "; if ($unparsable) $out .= "selected"; $out .= ">- " . t("other (leave it untouched)") . " -</option>\n"; // @TODO
+               $out .= "</select><br>";
+
+               $out .= "<label class='block'>" . t("Time") . ":</label>";
+               $out .= "<input name='noti_value[$index]' size='5' style='width: 5em;' value='" . $noti["trigger_value"] . "'>";
+
+               $out .= "<select name='noti_unit[$index]' size='1'>";
+               $out .= "<option value='H' "; if ($noti["trigger_unit"] == "hour") $out .= "selected"; $out .= ">" . t("Hours") . "</option>\n";
+               $out .= "<option value='M' "; if ($noti["trigger_unit"] == "minute") $out .= "selected"; $out .= ">" . t("Minutes") . "</option>\n";
+               $out .= "<option value='S' "; if ($noti["trigger_unit"] == "second") $out .= "selected"; $out .= ">" . t("Seconds") . "</option>\n";
+               $out .= "<option value='D' "; if ($noti["trigger_unit"] == "day") $out .= "selected"; $out .= ">" . t("Days") . "</option>\n";
+               $out .= "<option value='W' "; if ($noti["trigger_unit"] == "week") $out .= "selected"; $out .= ">" . t("Weeks") . "</option>\n";
+               $out .= "</select>";
+
+               $out .= " <label class='plain'>" . t("before the") . " <select name='noti_ref[$index]' size='1'>";
+               $out .= "<option value='start' "; if ($noti["rel"] == "start") $out .= "selected"; $out .= ">" . t("start of the event") . "</option>\n";
+               $out .= "<option value='end' "; if ($noti["rel"] == "end") $out .= "selected"; $out .= ">" . t("end of the event") . "</option>\n";
+               $out .= "</select></label>\n";
+
+               $out .= "</div>";
+       }
+       $out .= "<input type='hidden' name='new_alarm' id='new_alarm' value='0'><div id='new_alarm_adder'><a href='#'>" . t("Add a notification") . "</a></div>";
+
+       $out .= "<script>\$(function() {
+               wdcal_edit_init('" . $localization->dateformat_datepicker_js() . "', '${baseurl}/dav/');
+       });</script>";
+
+       $out .= "<br><input type='submit' name='save' value='Save'></form>";
+
+       return $out;
+}
+
+
+/**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param wdcal_local $localization
+ * @return int
+ */
+function wdcal_set_component_date(&$component, &$localization)
+{
+       if (isset($_REQUEST["allday"])) {
+               $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " 00:00");
+               $ts_end   = $localization->date_local2timestamp($_REQUEST["end_date"] . " 00:00");
+               $type     = Sabre\VObject\Property\DateTime::DATE;
+       } else {
+               $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " " . $_REQUEST["start_time"]);
+               $ts_end   = $localization->date_local2timestamp($_REQUEST["end_date"] . " " . $_REQUEST["end_time"]);
+               $type     = Sabre\VObject\Property\DateTime::LOCALTZ;
+       }
+       $datetime_start = new Sabre\VObject\Property\DateTime("DTSTART");
+       $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type);
+       $datetime_end = new Sabre\VObject\Property\DateTime("DTEND");
+       $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type);
+
+       $component->__unset("DTSTART");
+       $component->__unset("DTEND");
+       $component->add($datetime_start);
+       $component->add($datetime_end);
+
+       return $ts_start;
+}
+
+/**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param string $str
+ * @return string
+ */
+
+function wdcal_set_component_recurrence_special(&$component, $str) {
+       $ret = "";
+
+       /** @var Sabre\VObject\Property\DateTime $start  */
+       $start  = $component->__get("DTSTART");
+       $dayMap = array(
+               0 => 'SU',
+               1 => 'MO',
+               2 => 'TU',
+               3 => 'WE',
+               4 => 'TH',
+               5 => 'FR',
+               6 => 'SA',
+       );
+
+       switch ($str) {
+               case "bymonthday":
+                       $day = $start->getDateTime()->format("j");
+                       $ret = ";BYMONTHDAY=" . $day;
+                       break;
+               case "bymonthday_neg":
+                       $day     = $start->getDateTime()->format("j");
+                       $day_max = $start->getDateTime()->format("t");
+                       $ret = ";BYMONTHDAY=" . (-1 * ($day_max - $day + 1));
+                       break;
+               case "byday":
+                       $day     = $start->getDateTime()->format("j");
+                       $weekday = $dayMap[$start->getDateTime()->format("w")];
+                       $num     = IntVal(ceil($day / 7));
+                       $ret = ";BYDAY=${num}${weekday}";
+                       break;
+               case "byday_neg":
+                       $day     = $start->getDateTime()->format("j");
+                       $weekday = $dayMap[$start->getDateTime()->format("w")];
+                       $day_max = $start->getDateTime()->format("t");
+                       $day_last = ($day_max - $day + 1);
+                       $num     = IntVal(ceil($day_last / 7));
+                       $ret = ";BYDAY=-${num}${weekday}";
+                       break;
+       }
+       return $ret;
+}
+
+/**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param wdcal_local $localization
+ */
+function wdcal_set_component_recurrence(&$component, &$localization)
+{
+       $component->__unset("RRULE");
+       $component->__unset("EXRULE");
+       $component->__unset("EXDATE");
+       $component->__unset("RDATE");
+
+       $part_until = "";
+       switch ($_REQUEST["rec_until_type"]) {
+               case "date":
+                       $date           = $localization->date_local2timestamp($_REQUEST["rec_until_date"]);
+                       $part_until     = ";UNTIL=" . date("Ymd", $date);
+                       $datetime_until = new Sabre\VObject\Property\DateTime("UNTIL");
+                       $datetime_until->setDateTime(new DateTime(date("Y-m-d H:i:s", $date)), Sabre\VObject\Property\DateTime::DATE);
+                       break;
+               case "count":
+                       $part_until = ";COUNT=" . IntVal($_REQUEST["rec_until_count"]);
+                       break;
+       }
+
+       switch ($_REQUEST["rec_frequency"]) {
+               case "daily":
+                       $part_freq = "FREQ=DAILY";
+                       if (isset($_REQUEST["rec_daily_byday"])) {
+                               $days = array();
+                               foreach ($_REQUEST["rec_daily_byday"] as $x) if (in_array($x, array("MO", "TU", "WE", "TH", "FR", "SA", "SU"))) $days[] = $x;
+                               if (count($days) > 0) $part_freq .= ";BYDAY=" . implode(",", $days);
+                       }
+                       break;
+               case "weekly":
+                       $part_freq = "FREQ=WEEKLY";
+                       if (isset($_REQUEST["rec_weekly_wkst"]) && in_array($_REQUEST["rec_weekly_wkst"], array("MO", "SU"))) $part_freq .= ";WKST=" . $_REQUEST["rec_weekly_wkst"];
+                       if (isset($_REQUEST["rec_weekly_byday"])) {
+                               $days = array();
+                               foreach ($_REQUEST["rec_weekly_byday"] as $x) if (in_array($x, array("MO", "TU", "WE", "TH", "FR", "SA", "SU"))) $days[] = $x;
+                               if (count($days) > 0) $part_freq .= ";BYDAY=" . implode(",", $days);
+                       }
+                       break;
+               case "monthly":
+                       $part_freq = "FREQ=MONTHLY";
+                       $part_freq .= wdcal_set_component_recurrence_special($component, $_REQUEST["rec_monthly_day"]);
+                       break;
+               case "yearly":
+                       /** @var Sabre\VObject\Property\DateTime $start  */
+                       $start  = $component->__get("DTSTART");
+                       $part_freq = "FREQ=YEARLY";
+                       $part_freq .= ";BYMONTH=" . $start->getDateTime()->format("n");
+                       $part_freq .= wdcal_set_component_recurrence_special($component, $_REQUEST["rec_yearly_day"]);
+                       break;
+               default:
+                       $part_freq = "";
+       }
+
+       if ($part_freq == "") return;
+
+       if (isset($_REQUEST["rec_interval"])) $part_freq .= ";INTERVAL=" . InTVal($_REQUEST["rec_interval"]);
+
+       if (isset($_REQUEST["rec_exceptions"])) {
+               $arr = array();
+               foreach ($_REQUEST["rec_exceptions"] as $except) {
+                       $arr[] = new DateTime(date("Y-m-d H:i:s", $except));
+               }
+               /** @var Sabre\VObject\Property\MultiDateTime $prop */
+               $prop = Sabre\VObject\Property::create("EXDATE");
+               $prop->setDateTimes($arr);
+               $component->add($prop);
+       }
+
+       $rrule = $part_freq . $part_until;
+       $component->add(new Sabre\VObject\Property("RRULE", $rrule));
+
+}
+
+
+       /**
+        * @param Sabre\VObject\Component\VEvent $component
+        * @param wdcal_local $localization
+        * @param string $summary
+        * @param int $dtstart
+        */
+function wdcal_set_component_alerts(&$component, &$localization, $summary, $dtstart)
+{
+       $a = get_app();
+
+       $prev_alarms = $component->select("VALARM");
+       $component->__unset("VALARM");
+
+       foreach ($prev_alarms as $al) {
+               /** @var Sabre\VObject\Component\VAlarm $al */
+               // @TODO Parse notifications that have been there before; e.g. from Lightning
+       }
+
+       foreach (array_keys($_REQUEST["noti_type"]) as $key) if (is_numeric($key) || ($key == "new" && $_REQUEST["new_alarm"] == 1)) {
+               $alarm = new Sabre\VObject\Component\VAlarm("VALARM");
+
+               switch ($_REQUEST["noti_type"][$key]) {
+                       case "email":
+                               $mailtext = str_replace(array(
+                                       "#date#", "#name",
+                               ), array(
+                                       $localization->date_timestamp2local($dtstart), $summary,
+                               ), t("The event #name# will start at #date"));
+
+                               $alarm->add(new Sabre\VObject\Property("ACTION", "EMAIL"));
+                               $alarm->add(new Sabre\VObject\Property("SUMMARY", $summary));
+                               $alarm->add(new Sabre\VObject\Property("DESCRIPTION", $mailtext));
+                               $alarm->add(new Sabre\VObject\Property("ATTENDEE", "MAILTO:" . $a->user["email"]));
+                               break;
+                       case "display":
+                               $alarm->add(new Sabre\VObject\Property("ACTION", "DISPLAY"));
+                               $text = str_replace("#name#", $summary, t("#name# is about to begin."));
+                               $alarm->add(new Sabre\VObject\Property("DESCRIPTION", $text));
+                               break;
+                       default:
+                               continue;
+               }
+
+               $trigger_name = "TRIGGER";
+               $trigger_val = ""; // @TODO Bugfix : und ; sind evtl. vertauscht vgl. http://www.kanzaki.com/docs/ical/trigger.html
+               if ($_REQUEST["noti_ref"][$key] == "end") $trigger_name .= ";RELATED=END";
+               $trigger_val .= "-P";
+               if (in_array($_REQUEST["noti_unit"][$key], array("H", "M", "S"))) $trigger_val .= "T";
+               $trigger_val .= IntVal($_REQUEST["noti_value"][$key]) . $_REQUEST["noti_unit"][$key];
+               $alarm->add(new Sabre\VObject\Property($trigger_name, $trigger_val));
+
+               $component->add($alarm);
+       }
+
+}
+
+       /**
+ * @param string $uri
+ * @param int $uid
+ * @param string $timezone
+ * @param string $goaway_url
+ * @return array
+ */
+function wdcal_postEditPage($uri, $uid = 0, $timezone = "", $goaway_url = "")
+{
+       $uid          = IntVal($uid);
+       $localization = wdcal_local::getInstanceByUser($uid);
+
+       $server = dav_create_server(true, true, false);
+
+       if ($uri > 0) {
+               $calendar = dav_get_current_user_calendar_by_id($server, $_REQUEST["calendar"], DAV_ACL_READ);
+               $obj_uri  = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($uri);
+               $obj_uri  = $obj_uri["uri"];
+
+               $vObject   = dav_get_current_user_calendarobject($server, $calendar, $obj_uri, DAV_ACL_WRITE);
+               $component = dav_get_eventComponent($vObject);
+
+               if ($component == null) return array("ok" => false, "msg" => t('Could not open component for editing'));
+       } else {
+               $calendar  = dav_get_current_user_calendar_by_id($server, $_REQUEST["calendar"], DAV_ACL_WRITE);
+               $vObject   = dav_create_empty_vevent();
+               $component = dav_get_eventComponent($vObject);
+               $obj_uri   = $component->__get("UID");
+       }
+
+       $ts_start = wdcal_set_component_date($component, $localization);
+       wdcal_set_component_recurrence($component, $localization);
+       wdcal_set_component_alerts($component, $localization, icalendar_sanitize_string(dav_compat_parse_text_serverside("summary")), $ts_start);
+
+       $component->__unset("LOCATION");
+       $component->__unset("SUMMARY");
+       $component->__unset("DESCRIPTION");
+       $component->__unset("X-ANIMEXX-COLOR");
+       $component->add("SUMMARY", icalendar_sanitize_string(dav_compat_parse_text_serverside("summary")));
+       $component->add("LOCATION", icalendar_sanitize_string(dav_compat_parse_text_serverside("location")));
+       $component->add("DESCRIPTION", icalendar_sanitize_string(dav_compat_parse_text_serverside("wdcal_desc")));
+       if (isset($_REQUEST["color_override"])) {
+               $component->add("X-ANIMEXX-COLOR", $_REQUEST["color"]);
+       }
+
+       $data = $vObject->serialize();
+
+       if ($uri == 0) {
+               $calendar->createFile($obj_uri . ".ics", $data);
+       } else {
+               $obj = $calendar->getChild($obj_uri);
+               $obj->put($data);
+       }
+       return array("ok" => false, "msg" => t("Saved"));
+}
+
+
+/**
+ * @return string
+ */
+function wdcal_getEditPage_exception_selector()
+{
+       header("Content-type: application/json");
+
+       $a            = get_app();
+       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
+
+       $vObject = dav_create_empty_vevent();
+
+       foreach ($vObject->getComponents() as $component) {
+               if ($component->name !== 'VTIMEZONE') break;
+       }
+       /** @var Sabre\VObject\Component\VEvent $component */
+       wdcal_set_component_date($component, $localization);
+       wdcal_set_component_recurrence($component, $localization);
+
+
+       $it         = new Sabre\VObject\RecurrenceIterator($vObject, (string)$component->__get("UID"));
+       $max_ts     = mktime(0, 0, 0, 1, 1, CALDAV_MAX_YEAR + 1);
+       $last_start = 0;
+
+       $o = "<ul>";
+
+       $i = 0;
+       while ($it->valid() && $last_start < $max_ts && $i++ < 1000) {
+               $last_start = $it->getDtStart()->getTimestamp();
+               $o .= "<li><a href='#' class='exception_selector_link' data-timestamp='$last_start'>" . $localization->date_timestamp2localDate($last_start) . "</a></li>\n";
+               $it->next();
+       }
+       $o .= "</ul>\n";
+
+       return $o;
+}
\ No newline at end of file
diff --git a/dav/database-init.inc.php b/dav/database-init.inc.php
deleted file mode 100644 (file)
index 06e3abc..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-<?php
-
-
-/**
- * @return array
- */
-function dav_get_create_statements() {
-       $arr = array();
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` (
-               `uid` int(11) NOT NULL,
-  `ctag` int(11) unsigned NOT NULL DEFAULT '1',
-  PRIMARY KEY (`uid`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_phone` (
-               `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `uid` int(11) NOT NULL,
-  `principaluri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
-  `displayname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
-  `uri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
-  `description` text COLLATE utf8_unicode_ci,
-  `ctag` int(11) unsigned NOT NULL DEFAULT '1',
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `principaluri` (`principaluri`,`uri`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cache_synchronized` (
-               `uid` mediumint(8) unsigned NOT NULL,
-  `namespace` smallint(6) NOT NULL,
-  `namespace_id` int(11) NOT NULL,
-  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-  PRIMARY KEY (`uid`,`namespace`,`namespace_id`),
-  KEY `namespace` (`namespace`,`namespace_id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_caldav_log` (
-               `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-  `uid` mediumint(9) NOT NULL,
-  `ip` varchar(15) NOT NULL,
-  `user_agent` varchar(100) NOT NULL,
-  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-  `method` varchar(10) NOT NULL,
-  `url` varchar(100) NOT NULL,
-  PRIMARY KEY (`id`),
-  KEY `mitglied` (`uid`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendarobjects` (
-               `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-  `namespace` mediumint(9) NOT NULL,
-  `namespace_id` int(10) unsigned NOT NULL,
-  `calendardata` text,
-  `uri` varchar(200) NOT NULL,
-  `lastmodified` timestamp NULL DEFAULT NULL,
-  `etag` varchar(15) NOT NULL,
-  `size` int(10) unsigned NOT NULL,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `uri` (`uri`,`namespace`,`namespace_id`),
-  KEY `namespace` (`namespace`,`namespace_id`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendars` (
-               `namespace` mediumint(9) NOT NULL,
-  `namespace_id` int(10) unsigned NOT NULL,
-  `uid` mediumint(8) unsigned NOT NULL,
-  `calendarorder` int(11) NOT NULL DEFAULT '1',
-  `calendarcolor` varchar(20) NOT NULL DEFAULT '#5858FF',
-  `displayname` varchar(200) NOT NULL,
-  `timezone` text NOT NULL,
-  `description` varchar(500) NOT NULL,
-  `ctag` int(10) unsigned NOT NULL,
-  PRIMARY KEY (`namespace`,`namespace_id`),
-  KEY `uid` (`uid`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_cache` (
-               `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
-  `uid` mediumint(8) unsigned NOT NULL,
-  `namespace` mediumint(9) NOT NULL,
-  `namespace_id` int(11) NOT NULL DEFAULT '0',
-  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-  `data_uri` char(80) NOT NULL,
-  `data_subject` varchar(1000) NOT NULL,
-  `data_location` varchar(1000) NOT NULL,
-  `data_description` text NOT NULL,
-  `data_start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
-  `data_end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
-  `data_allday` tinyint(4) NOT NULL,
-  `data_type` varchar(20) NOT NULL,
-  `ical` text NOT NULL,
-  `ical_size` int(11) NOT NULL,
-  `ical_etag` varchar(15) NOT NULL,
-  PRIMARY KEY (`id`),
-  KEY `ref_type` (`namespace`,`namespace_id`),
-  KEY `mitglied` (`uid`,`data_end`),
-  KEY `data_uri` (`data_uri`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cards` (
-               `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-  `namespace` tinyint(3) unsigned NOT NULL,
-  `namespace_id` int(11) unsigned NOT NULL,
-  `contact` int(11) DEFAULT NULL,
-  `carddata` mediumtext COLLATE utf8_unicode_ci,
-  `uri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
-  `lastmodified` int(11) unsigned DEFAULT NULL,
-  `manually_edited` tinyint(4) NOT NULL DEFAULT '0',
-  `manually_deleted` tinyint(4) NOT NULL DEFAULT '0',
-  `etag` varchar(15) COLLATE utf8_unicode_ci NOT NULL,
-  `size` int(10) unsigned NOT NULL,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `namespace` (`namespace`,`namespace_id`,`contact`),
-  KEY `contact` (`contact`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_jqcalendar` (
-               `id` int(11) NOT NULL AUTO_INCREMENT,
-  `uid` int(10) unsigned NOT NULL,
-  `ical_uri` varchar(200) NOT NULL,
-  `ical_recurr_uri` varchar(100) NOT NULL,
-  `namespace` mediumint(9) NOT NULL,
-  `namespace_id` int(11) NOT NULL,
-  `permission_edit` tinyint(4) NOT NULL DEFAULT '1',
-  `Subject` varchar(1000) DEFAULT NULL,
-  `Location` varchar(1000) DEFAULT NULL,
-  `Description` text,
-  `StartTime` timestamp NULL DEFAULT NULL,
-  `EndTime` timestamp NULL DEFAULT NULL,
-  `IsAllDayEvent` smallint(6) NOT NULL,
-  `Color` varchar(20) DEFAULT NULL,
-  `RecurringRule` varchar(500) DEFAULT NULL,
-  PRIMARY KEY (`id`),
-  KEY `user` (`uid`,`StartTime`),
-  KEY `zuord_typ` (`namespace`,`namespace_id`),
-  KEY `ical_uri` (`ical_uri`,`ical_recurr_uri`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_locks` (
-               `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-  `owner` varchar(100) DEFAULT NULL,
-  `timeout` int(10) unsigned DEFAULT NULL,
-  `created` int(11) DEFAULT NULL,
-  `token` varchar(100) DEFAULT NULL,
-  `scope` tinyint(4) DEFAULT NULL,
-  `depth` tinyint(4) DEFAULT NULL,
-  `uri` text,
-  PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
-
-
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_notifications` (
-               `id` int(11) NOT NULL AUTO_INCREMENT,
-  `uid` int(10) unsigned NOT NULL,
-  `ical_uri` varchar(200) NOT NULL,
-  `ical_recurr_uri` varchar(100) NOT NULL,
-  `namespace` mediumint(8) unsigned NOT NULL,
-  `namespace_id` int(10) unsigned NOT NULL,
-  `alert_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-  `rel_type` enum('second','minute','hour','day','week','month','year') NOT NULL,
-  `rel_value` mediumint(9) NOT NULL,
-  `notified` tinyint(4) NOT NULL DEFAULT '0',
-  PRIMARY KEY (`id`),
-  KEY `notified` (`notified`,`alert_date`),
-  KEY `ical_uri` (`uid`,`ical_uri`,`ical_recurr_uri`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
-
-       return $arr;
-}
-
-/**
- * @return int
- */
-function dav_check_tables() {
-       $dbv = get_config("dav", "db_version");
-       if ($dbv == CALDAV_DB_VERSION) return 0; // Correct
-       if (is_numeric($dbv)) return 1; // Older version (update needed)
-       return -1; // Not installed
-}
-
-
-/**
- * @return array
- */
-function dav_create_tables()
-{
-       $stms = dav_get_create_statements();
-       $errors = array();
-
-       global $db;
-       foreach ($stms as $st) {
-               $db->q($st);
-               if ($db->error) $errors[] = $db->error;
-       }
-
-       if (count($errors) == 0) set_config("dav", "db_version", CALDAV_DB_VERSION);
-
-       return $errors;
-}
\ No newline at end of file
index 1f35b06ad5422c4f95211dfb364aa07acd27f590..f4d08f0282dc699597bba79a17883bf70a409e9d 100644 (file)
@@ -2,11 +2,11 @@
 /**
  * Name: Calendar with CalDAV Support
  * Description: A web-based calendar system with CalDAV-support. Also brings your Friendica-Contacts to your CardDAV-capable mobile phone. Requires PHP >= 5.3.
- * Version: 0.1.1
+ * Version: 0.3.0
  * Author: Tobias Hößl <https://github.com/CatoTH/>
  */
 
 $_v = explode(".", phpversion());
 if ($_v[0] > 5 || ($_v[0] == 5 && $_v[1] >= 3)) {
-       require(__DIR__ . "/main.php");
+       require(__DIR__ . "/friendica/main.php");
 }
\ No newline at end of file
diff --git a/dav/dav_caldav_backend_friendica.inc.php b/dav/dav_caldav_backend_friendica.inc.php
deleted file mode 100644 (file)
index 11b27ea..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-
-class Sabre_CalDAV_Backend_Friendica extends Sabre_CalDAV_Backend_Common
-{
-
-       public function getNamespace() {
-               return CALDAV_NAMESPACE_FRIENDICA_NATIVE;
-       }
-
-       public function getCalUrlPrefix() {
-               return "friendica";
-       }
-
-
-       /**
-        * Creates a new calendar for a principal.
-        *
-        * If the creation was a success, an id must be returned that can be used to reference
-        * this calendar in other methods, such as updateCalendar.
-        *
-        * @param string $principalUri
-        * @param string $calendarUri
-        * @param array $properties
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       function createCalendar($principalUri, $calendarUri, array $properties)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Delete a calendar and all it's objects
-        *
-        * @param string $calendarId
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       function deleteCalendar($calendarId)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * @param string $calendarId
-        * @return array
-        */
-       function getCalendarObjects($calendarId)
-       {
-               $a = get_app();
-               $user_id = $a->user["uid"];
-               $x = explode("-", $calendarId);
-
-               $ret = array();
-               $objs = FriendicaVirtualCalSourceBackend::getItemsByTime($user_id, $x[1]);
-               foreach ($objs as $obj) {
-                       $ret[] = array(
-                               "id" => IntVal($obj["data_uri"]),
-                               "calendardata" => $obj["ical"],
-                               "uri" => $obj["data_uri"],
-                               "lastmodified" => $obj["date"],
-                               "calendarid" => $calendarId,
-                               "etag" => $obj["ical_etag"],
-                               "size" => IntVal($obj["ical_size"]),
-                       );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * Returns information from a single calendar object, based on it's object
-        * uri.
-        *
-        * The returned array must have the same keys as getCalendarObjects. The
-        * 'calendardata' object is required here though, while it's not required
-        * for getCalendarObjects.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @throws Sabre_DAV_Exception_FileNotFound
-        * @return array
-        */
-       function getCalendarObject($calendarId, $objectUri)
-       {
-               $a = get_app();
-               $user_id = $a->user["uid"];
-               $obj = FriendicaVirtualCalSourceBackend::getItemsByUri($user_id, $objectUri);
-
-               return array(
-                       "id" => IntVal($obj["data_uri"]),
-                       "calendardata" => $obj["ical"],
-                       "uri" => $obj["data_uri"],
-                       "lastmodified" => $obj["date"],
-                       "calendarid" => $calendarId,
-                       "etag" => $obj["ical_etag"],
-                       "size" => IntVal($obj["ical_size"]),
-               );
-       }
-
-       /**
-        * Creates a new calendar object.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @param string $calendarData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return null|string|void
-        */
-       function createCalendarObject($calendarId, $objectUri, $calendarData)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Updates an existing calendarobject, based on it's uri.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @param string $calendarData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return null|string|void
-        */
-       function updateCalendarObject($calendarId, $objectUri, $calendarData)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Deletes an existing calendar object.
-        *
-        * @param string $calendarId
-        * @param string $objectUri
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       function deleteCalendarObject($calendarId, $objectUri)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-}
diff --git a/dav/dav_carddav_backend_friendica_community.inc.php b/dav/dav_carddav_backend_friendica_community.inc.php
deleted file mode 100644 (file)
index cf8b2ff..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-<?php
-
-class Sabre_CardDAV_Backend_FriendicaCommunity extends Sabre_CardDAV_Backend_Abstract
-{
-
-       /**
-        * Sets up the object
-        */
-       public function __construct()
-       {
-
-       }
-
-       /**
-        * Returns the list of addressbooks for a specific user.
-        *
-        * @param string $principalUri
-        * @return array
-        */
-       public function getAddressBooksForUser($principalUri)
-       {
-               $uid = dav_compat_principal2uid($principalUri);
-
-               $addressBooks = array();
-
-               $books = q("SELECT ctag FROM %s%saddressbooks_community WHERE uid = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid));
-               if (count($books) == 0) {
-                       q("INSERT INTO %s%saddressbooks_community (uid, ctag) VALUES (%d, 1)", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid));
-                       $ctag = 1;
-               } else {
-                       $ctag = $books[0]["ctag"];
-               }
-               $addressBooks[] = array(
-                       'id'                                                                => CARDDAV_NAMESPACE_COMMUNITYCONTACTS . "-" . $uid,
-                       'uri'                                                               => "friendica",
-                       'principaluri'                                                      => $principalUri,
-                       '{DAV:}displayname'                                                 => t("Friendica-Contacts"),
-                       '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => t("Your Friendica-Contacts"),
-                       '{http://calendarserver.org/ns/}getctag'                            => $ctag,
-                       '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data'  =>
-                       new Sabre_CardDAV_Property_SupportedAddressData(),
-               );
-
-               return $addressBooks;
-
-       }
-
-
-       /**
-        * Updates an addressbook's properties
-        *
-        * See Sabre_DAV_IProperties for a description of the mutations array, as
-        * well as the return value.
-        *
-        * @param string $addressBookId
-        * @param array $mutations
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @see Sabre_DAV_IProperties::updateProperties
-        * @return bool|array
-        */
-       public function updateAddressBook($addressBookId, array $mutations)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Creates a new address book
-        *
-        * @param string $principalUri
-        * @param string $url Just the 'basename' of the url.
-        * @param array $properties
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       public function createAddressBook($principalUri, $url, array $properties)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Deletes an entire addressbook and all its contents
-        *
-        * @param int $addressBookId
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return void
-        */
-       public function deleteAddressBook($addressBookId)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-
-       /**
-        * @param array $contact
-        * @return array
-        */
-       private function dav_contactarr2vcardsource($contact)
-       {
-               $name        = explode(" ", $contact["name"]);
-               $first_name  = $last_name = "";
-               $middle_name = array();
-               $num         = count($name);
-               for ($i = 0; $i < $num && $first_name == ""; $i++) if ($name[$i] != "") {
-                       $first_name = $name[$i];
-                       unset($name[$i]);
-               }
-               for ($i = $num - 1; $i >= 0 && $last_name == ""; $i--) if (isset($name[$i]) && $name[$i] != "") {
-                       $last_name = $name[$i];
-                       unset($name[$i]);
-               }
-               foreach ($name as $n) if ($n != "") $middle_name[] = $n;
-               $vcarddata              = new vcard_source_data($first_name, implode(" ", $middle_name), $last_name);
-               $vcarddata->homepages[] = new vcard_source_data_homepage("pref", $contact["url"]);
-               $vcarddata->last_update = ($contact["last-update"] > 0 ? $contact["last-update"] : $contact["created"]);
-
-               $photo = q("SELECT * FROM photo WHERE `contact-id` = %d ORDER BY scale DESC", $contact["id"]); //prefer size 80x80
-               if ($photo && count($photo) > 0) {
-                       $photodata             = new vcard_source_data_photo();
-                       $photodata->width      = $photo[0]["width"];
-                       $photodata->height     = $photo[0]["height"];
-                       $photodata->type       = "JPEG";
-                       $photodata->binarydata = $photo[0]["data"];
-                       $vcarddata->photo      = $photodata;
-               }
-
-               switch ($contact["network"]) {
-                       case "face":
-                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("facebook", $contact["notify"], "http://www.facebook.com/" . $contact["notify"]);
-                               break;
-                       case "dfrn":
-                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("dfrn", $contact["nick"], $contact["url"]);
-                               break;
-                       case "twitter":
-                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("twitter", $contact["nick"], "http://twitter.com/" . $contact["nick"]); // @TODO Stimmt das?
-                               break;
-               }
-
-               $vcard = vcard_source_compile($vcarddata);
-               return array(
-                       "id"           => $contact["id"],
-                       "carddata"     => $vcard,
-                       "uri"          => $contact["id"] . ".vcf",
-                       "lastmodified" => wdcal_mySql2PhpTime($vcarddata->last_update),
-                       "etag"         => md5($vcard),
-                       "size"         => strlen($vcard),
-               );
-
-       }
-
-       /**
-        * @param int $uid
-        * @param array|int[] $exclude_ids
-        * @return array
-        */
-       private function dav_getCommunityContactsVCards($uid = 0, $exclude_ids = array())
-       {
-               $notin    = (count($exclude_ids) > 0 ? " AND id NOT IN (" . implode(", ", $exclude_ids) . ") " : "");
-               $uid      = IntVal($uid);
-               $contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0 $notin ORDER BY `name` ASC", $uid);
-
-               $retdata = array();
-               foreach ($contacts as $contact) {
-                       $x            = $this->dav_contactarr2vcardsource($contact);
-                       $x["contact"] = $contact["id"];
-                       $retdata[]    = $x;
-               }
-               return $retdata;
-       }
-
-
-       /**
-        * Returns all cards for a specific addressbook id.
-        *
-        * This method should return the following properties for each card:
-        *   * carddata - raw vcard data
-        *   * uri - Some unique url
-        *   * lastmodified - A unix timestamp
-        *
-        * It's recommended to also return the following properties:
-        *   * etag - A unique etag. This must change every time the card changes.
-        *   * size - The size of the card in bytes.
-        *
-        * If these last two properties are provided, less time will be spent
-        * calculating them. If they are specified, you can also ommit carddata.
-        * This may speed up certain requests, especially with large cards.
-        *
-        * @param string $addressbookId
-        * @return array
-        */
-       public function getCards($addressbookId)
-       {
-               $add = explode("-", $addressbookId);
-
-               $indb           = q('SELECT id, carddata, uri, lastmodified, etag, size, contact, manually_deleted FROM %s%scards WHERE namespace = %d AND namespace_id = %d',
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($add[0]), IntVal($add[1])
-               );
-               $found_contacts = array();
-               $contacts       = array();
-               foreach ($indb as $x) {
-                       if ($x["manually_deleted"] == 0) $contacts[] = $x;
-                       $found_contacts[] = IntVal($x["contact"]);
-               }
-               $new_found = $this->dav_getCommunityContactsVCards($add[1], $found_contacts);
-               foreach ($new_found as $new) {
-                       q("INSERT INTO %s%scards (namespace, namespace_id, contact, carddata, uri, lastmodified, manually_edited, manually_deleted, etag, size)
-                                       VALUES (%d, %d, %d, '%s', '%s', %d, 0, 0, '%s', %d)", CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
-                               IntVal($add[0]), IntVal($add[1]), IntVal($new["contact"]), dbesc($new["carddata"]), dbesc($new["uri"]), time(), md5($new["carddata"]), strlen($new["carddata"])
-                       );
-               }
-               return array_merge($contacts, $new_found);
-       }
-
-       /**
-        * Returns a specfic card.
-        *
-        * The same set of properties must be returned as with getCards. The only
-        * exception is that 'carddata' is absolutely required.
-        *
-        * @param mixed $addressBookId
-        * @param string $cardUri
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return array
-        */
-       public function getCard($addressBookId, $cardUri)
-       {
-               $x = explode("-", $addressBookId);
-               $x = q("SELECT id, carddata, uri, lastmodified, etag, size FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND uri = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
-               if (count($x) == 0) throw new Sabre_DAV_Exception_NotFound();
-               return $x[0];
-       }
-
-       /**
-        * Creates a new card.
-        *
-        * The addressbook id will be passed as the first argument. This is the
-        * same id as it is returned from the getAddressbooksForUser method.
-        *
-        * The cardUri is a base uri, and doesn't include the full path. The
-        * cardData argument is the vcard body, and is passed as a string.
-        *
-        * It is possible to return an ETag from this method. This ETag is for the
-        * newly created resource, and must be enclosed with double quotes (that
-        * is, the string itself must contain the double quotes).
-        *
-        * You should only return the ETag if you store the carddata as-is. If a
-        * subsequent GET request on the same card does not have the same body,
-        * byte-by-byte and you did return an ETag here, clients tend to get
-        * confused.
-        *
-        * If you don't return an ETag, you can just return null.
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @param string $cardData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return string
-        */
-       public function createCard($addressBookId, $cardUri, $cardData)
-       {
-               throw new Sabre_DAV_Exception_Forbidden();
-       }
-
-       /**
-        * Updates a card.
-        *
-        * The addressbook id will be passed as the first argument. This is the
-        * same id as it is returned from the getAddressbooksForUser method.
-        *
-        * The cardUri is a base uri, and doesn't include the full path. The
-        * cardData argument is the vcard body, and is passed as a string.
-        *
-        * It is possible to return an ETag from this method. This ETag should
-        * match that of the updated resource, and must be enclosed with double
-        * quotes (that is: the string itself must contain the actual quotes).
-        *
-        * You should only return the ETag if you store the carddata as-is. If a
-        * subsequent GET request on the same card does not have the same body,
-        * byte-by-byte and you did return an ETag here, clients tend to get
-        * confused.
-        *
-        * If you don't return an ETag, you can just return null.
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @param string $cardData
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return string|null
-        */
-       public function updateCard($addressBookId, $cardUri, $cardData)
-       {
-               $x = explode("-", $addressBookId);
-
-               $etag = md5($cardData);
-               q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1])
-               );
-               q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-
-               return '"' . $etag . '"';
-       }
-
-       /**
-        * Deletes a card
-        *
-        * @param string $addressBookId
-        * @param string $cardUri
-        * @throws Sabre_DAV_Exception_Forbidden
-        * @return bool
-        */
-       public function deleteCard($addressBookId, $cardUri)
-       {
-               $x = explode("-", $addressBookId);
-
-               q("UPDATE %s%scards SET manually_deleted = 1 WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
-               q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
-
-               return true;
-       }
-}
diff --git a/dav/dav_friendica_auth.inc.php b/dav/dav_friendica_auth.inc.php
deleted file mode 100644 (file)
index 5082a2f..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBasic {
-
-    public function __construct() {
-    }
-
-
-    public function getUsers() {
-        return array($this->currentUser);
-    }
-    
-    public function getCurrentUser() {
-        return $this->currentUser;
-    }
-
-       /**
-        * Authenticates the user based on the current request.
-        *
-        * If authentication is successful, true must be returned.
-        * If authentication fails, an exception must be thrown.
-        *
-        * @param Sabre_DAV_Server $server
-        * @param string $realm
-        * @throws Sabre_DAV_Exception_NotAuthenticated
-        * @return bool
-        */
-       public function authenticate(Sabre_DAV_Server $server, $realm) {
-
-               $auth = new Sabre_HTTP_BasicAuth();
-               $auth->setHTTPRequest($server->httpRequest);
-               $auth->setHTTPResponse($server->httpResponse);
-               $auth->setRealm($realm);
-               $userpass = $auth->getUserPass();
-               if (!$userpass) {
-                       $auth->requireLogin();
-                       throw new Sabre_DAV_Exception_NotAuthenticated('No basic authentication headers were found');
-               }
-
-               // Authenticates the user
-               if (!$this->validateUserPass($userpass[0],$userpass[1])) {
-                       $auth->requireLogin();
-                       throw new Sabre_DAV_Exception_NotAuthenticated('Username or password does not match');
-               }
-               $this->currentUser = strtolower($userpass[0]);
-               return true;
-       }
-
-
-       protected function validateUserPass($username, $password) {
-
-               $user = array(
-                   'uri' => "/" . 'principals/users/' . strtolower($username),
-               );
-               return $user;
-    }
-    
-}
diff --git a/dav/dav_friendica_principal.inc.php b/dav/dav_friendica_principal.inc.php
deleted file mode 100644 (file)
index eba31c2..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-<?php
-
-
-class Sabre_DAVACL_PrincipalBackend_Friendica implements Sabre_DAVACL_IPrincipalBackend
-{
-
-       /**
-        * Principals prefix
-        *
-        * @var string
-        */
-       public $prefix = 'principals/users';
-
-       /**
-        * @var Sabre_DAV_Auth_Backend_AbstractBasic $authBackend;
-        */
-       protected $authBackend;
-
-       public function __construct(&$authBackend)
-       {
-
-               $this->authBackend = &$authBackend;
-
-       }
-
-
-       /**
-        * Returns a list of principals based on a prefix.
-        *
-        * This prefix will often contain something like 'principals'. You are only
-        * expected to return principals that are in this base path.
-        *
-        * You are expected to return at least a 'uri' for every user, you can
-        * return any additional properties if you wish so. Common properties are:
-        *   {DAV:}displayname
-        *   {http://sabredav.org/ns}email-address - This is a custom SabreDAV
-        *     field that's actualy injected in a number of other properties. If
-        *     you have an email address, use this property.
-        *
-        * @param string $prefixPath
-        * @return array
-        */
-       public function getPrincipalsByPrefix($prefixPath)
-       {
-
-               // This backend only support principals in one collection
-               if ($prefixPath !== $this->prefix) return array();
-
-               $users = array();
-
-               $r = q("SELECT `nickname` FROM `user` WHERE `nickname` = '%s'", escape_tags($this->authBackend->getCurrentUser()) );
-               foreach ($r as $t) {
-                       $users[] = array(
-                               'uri'               => $this->prefix . '/' . strtolower($t['nickname']),
-                               '{DAV:}displayname' => $t['nickname'],
-                       );
-               }
-
-               return $users;
-
-       }
-
-       /**
-        * Returns a specific principal, specified by it's path.
-        * The returned structure should be the exact same as from
-        * getPrincipalsByPrefix.
-        *
-        * @param string $path
-        * @return array
-        */
-       public function getPrincipalByPath($path)
-       {
-
-               list($prefixPath, $userName) = Sabre_DAV_URLUtil::splitPath($path);
-
-               // This backend only support principals in one collection
-               if ($prefixPath !== $this->prefix) return null;
-
-               $r = q("SELECT `nickname` FROM `user` WHERE `nickname` = '%s'", escape_tags($userName) );
-               if (count($r) == 0) return array();
-
-               return array(
-                       'uri'               => $this->prefix . '/' . strtolower($r[0]['nickname']),
-                       '{DAV:}displayname' => $r[0]['nickname'],
-               );
-
-       }
-
-
-       function getGroupMemberSet($principal)
-       {
-               return array();
-       }
-
-       function getGroupMembership($principal)
-       {
-               return array();
-       }
-
-
-       /**
-        * Updates the list of group members for a group principal.
-        *
-        * The principals should be passed as a list of uri's.
-        *
-        * @param string $principal
-        * @param array $members
-        * @throws Sabre_DAV_Exception
-        * @return void
-        */
-       public function setGroupMemberSet($principal, array $members)
-       {
-               throw new Sabre_DAV_Exception('Operation not supported');
-       }
-
-       /**
-        * Updates one ore more webdav properties on a principal.
-        *
-        * The list of mutations is supplied as an array. Each key in the array is
-        * a propertyname, such as {DAV:}displayname.
-        *
-        * Each value is the actual value to be updated. If a value is null, it
-        * must be deleted.
-        *
-        * This method should be atomic. It must either completely succeed, or
-        * completely fail. Success and failure can simply be returned as 'true' or
-        * 'false'.
-        *
-        * It is also possible to return detailed failure information. In that case
-        * an array such as this should be returned:
-        *
-        * array(
-        *   200 => array(
-        *      '{DAV:}prop1' => null,
-        *   ),
-        *   201 => array(
-        *      '{DAV:}prop2' => null,
-        *   ),
-        *   403 => array(
-        *      '{DAV:}prop3' => null,
-        *   ),
-        *   424 => array(
-        *      '{DAV:}prop4' => null,
-        *   ),
-        * );
-        *
-        * In this previous example prop1 was successfully updated or deleted, and
-        * prop2 was succesfully created.
-        *
-        * prop3 failed to update due to '403 Forbidden' and because of this prop4
-        * also could not be updated with '424 Failed dependency'.
-        *
-        * This last example was actually incorrect. While 200 and 201 could appear
-        * in 1 response, if there's any error (403) the other properties should
-        * always fail with 423 (failed dependency).
-        *
-        * But anyway, if you don't want to scratch your head over this, just
-        * return true or false.
-        *
-        * @param string $path
-        * @param array $mutations
-        * @return array|bool
-        */
-       function updatePrincipal($path, $mutations)
-       {
-               // TODO: Implement updatePrincipal() method.
-       }
-
-       /**
-        * This method is used to search for principals matching a set of
-        * properties.
-        *
-        * This search is specifically used by RFC3744's principal-property-search
-        * REPORT. You should at least allow searching on
-        * http://sabredav.org/ns}email-address.
-        *
-        * The actual search should be a unicode-non-case-sensitive search. The
-        * keys in searchProperties are the WebDAV property names, while the values
-        * are the property values to search on.
-        *
-        * If multiple properties are being searched on, the search should be
-        * AND'ed.
-        *
-        * This method should simply return an array with full principal uri's.
-        *
-        * If somebody attempted to search on a property the backend does not
-        * support, you should simply return 0 results.
-        *
-        * You can also just return 0 results if you choose to not support
-        * searching at all, but keep in mind that this may stop certain features
-        * from working.
-        *
-        * @param string $prefixPath
-        * @param array $searchProperties
-        * @return array
-        */
-       function searchPrincipals($prefixPath, array $searchProperties)
-       {
-               // TODO: Implement searchPrincipals() method.
-       }
-}
diff --git a/dav/friendica/FriendicaACLPlugin.inc.php b/dav/friendica/FriendicaACLPlugin.inc.php
new file mode 100644 (file)
index 0000000..6bb8fc9
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+class Sabre_DAVACL_Plugin_Friendica extends Sabre_DAVACL_Plugin {
+
+       /*
+        * A dirty hack to make iOS CalDAV work with subdirectorys.
+        * When using a Root URL like /dav/ (as it is necessary for friendica), iOS does not evaluate the current-user-principal property,
+        * but only principal-URL. Actually principal-URL is not allowed in /dav/, only for Principal collections, but this seems
+        * to be the only way to force iOS to look at the right location.
+        */
+
+       public function beforeGetProperties($uri, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
+
+               parent::beforeGetProperties($uri, $node, $requestedProperties, $returnedProperties);
+
+               if (false !== ($index = array_search('{DAV:}principal-URL', $requestedProperties))) {
+
+                       unset($requestedProperties[$index]);
+                       $returnedProperties[200]['{DAV:}principal-URL'] = new Sabre_DAV_Property_Href('principals/users/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
+
+               }
+               if (false !== ($index = array_search('{urn:ietf:params:xml:ns:caldav}calendar-home-set', $requestedProperties))) {
+
+                       unset($requestedProperties[$index]);
+                       $returnedProperties[200]['{urn:ietf:params:xml:ns:caldav}calendar-home-set'] = new Sabre_DAV_Property_Href('calendars/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
+
+               }
+
+       }
+
+}
\ No newline at end of file
diff --git a/dav/friendica/calendar.friendica.fnk.php b/dav/friendica/calendar.friendica.fnk.php
new file mode 100644 (file)
index 0000000..60dd9c6
--- /dev/null
@@ -0,0 +1,274 @@
+<?php
+
+$a    = get_app();
+$uri  = parse_url($a->get_baseurl());
+$path = "/";
+if (isset($uri["path"]) && strlen($uri["path"]) > 1) {
+       $path = $uri["path"] . "/";
+}
+
+define("CALDAV_SQL_DB", "");
+define("CALDAV_SQL_PREFIX", "dav_");
+define("CALDAV_URL_PREFIX", $path . "dav/");
+define("DAV_APPNAME", "Friendica");
+
+define("CALDAV_NAMESPACE_PRIVATE", 1);
+define("CALDAV_FRIENDICA_MINE", "friendica-mine");
+define("CALDAV_FRIENDICA_CONTACTS", "friendica-contacts");
+
+$GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"] = array(CALDAV_FRIENDICA_MINE, CALDAV_FRIENDICA_CONTACTS);
+$GLOBALS["CALDAV_PRIVATE_SYSTEM_BACKENDS"] = array("Sabre_CalDAV_Backend_Friendica");
+
+define("CARDDAV_NAMESPACE_PRIVATE", 1);
+define("CARDDAV_FRIENDICA_CONTACT", "friendica");
+$GLOBALS["CARDDAV_PRIVATE_SYSTEM_ADDRESSBOOKS"] = array(CARDDAV_FRIENDICA_CONTACT);
+$GLOBALS["CARDDAV_PRIVATE_SYSTEM_BACKENDS"] = array("Sabre_CardDAV_Backend_Friendica");
+
+$GLOBALS["CALDAV_ACL_PLUGIN_CLASS"] = "Sabre_DAVACL_Plugin_Friendica";
+
+define("CALDAV_MAX_YEAR", date("Y") + 5);
+
+/**
+ * @return int
+ */
+function getCurMicrotime()
+{
+       list($usec, $sec) = explode(" ", microtime());
+       return sprintf("%14.0f", $sec * 10000 + $usec * 10000);
+} // function getCurMicrotime
+
+/**
+ *
+ */
+function debug_time()
+{
+       $cur = getCurMicrotime();
+       if ($GLOBALS["debug_time_last"] > 0) {
+               echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "<br>\n";
+       }
+       $GLOBALS["debug_time_last"] = $cur;
+}
+
+
+/**
+ * @param string $username
+ * @return int|null
+ */
+function dav_compat_username2id($username = "")
+{
+       $x = q("SELECT `uid` FROM `user` WHERE `nickname`='%s' AND `account_removed` = 0 AND `account_expired` = 0", dbesc($username));
+       if (count($x) == 1) return $x[0]["uid"];
+       return null;
+}
+
+/**
+ * @param int $id
+ * @return string
+ */
+function dav_compat_id2username($id = 0)
+{
+       $x = q("SELECT `nickname` FROM `user` WHERE `uid` = %i AND `account_removed` = 0 AND `account_expired` = 0", IntVal($id));
+       if (count($x) == 1) return $x[0]["nickname"];
+       return "";
+}
+
+/**
+ * @return int
+ */
+function dav_compat_get_curr_user_id()
+{
+       $a = get_app();
+       return IntVal($a->user["uid"]);
+}
+
+
+/**
+ * @param string $principalUri
+ * @return int|null
+ */
+function dav_compat_principal2uid($principalUri = "")
+{
+       if (strlen($principalUri) == 0) return null;
+       if ($principalUri[0] == "/") $principalUri = substr($principalUri, 1);
+       if (strpos($principalUri, "principals/users/") !== 0) return null;
+       $username = substr($principalUri, strlen("principals/users/"));
+       return dav_compat_username2id($username);
+}
+
+/**
+ * @param string $principalUri
+ * @return array|null
+ */
+function dav_compat_principal2namespace($principalUri = "")
+{
+       if (strlen($principalUri) == 0) return null;
+       if ($principalUri[0] == "/") $principalUri = substr($principalUri, 1);
+
+       if (strpos($principalUri, "principals/users/") !== 0) return null;
+       $username = substr($principalUri, strlen("principals/users/"));
+       return array("namespace" => CALDAV_NAMESPACE_PRIVATE, "namespace_id" => dav_compat_username2id($username));
+}
+
+
+/**
+ * @return string
+ */
+function dav_compat_currentUserPrincipal()
+{
+       $a = get_app();
+       return "principals/users/" . strtolower($a->user["nickname"]);
+}
+
+
+/**
+ * @param string $name
+ * @return null|string
+ */
+function dav_compat_getRequestVar($name = "")
+{
+       if (isset($_REQUEST[$name])) return $_REQUEST[$name];
+       else return null;
+}
+
+/**
+ * @param $text
+ * @return null|string
+ */
+function dav_compat_parse_text_serverside($text)
+{
+       return dav_compat_getRequestVar($text);
+}
+
+/**
+ * @param string $uri
+ */
+function dav_compat_redirect($uri = "")
+{
+       goaway($uri);
+}
+
+
+/**
+ * @return null|int
+ */
+function dav_compat_get_max_private_calendars()
+{
+       return null;
+}
+
+/**
+ * @return string
+ */
+function dav_compat_get_hostname() {
+       $a = get_app();
+       return $a->get_hostname();
+}
+
+/**
+ * @param int $namespace
+ * @param int $namespace_id
+ * @param string $uri
+ * @param array $calendar
+ * @return Sabre_CalDAV_Backend_Common
+ * @throws Exception
+ */
+function wdcal_calendar_factory($namespace, $namespace_id, $uri, $calendar = null)
+{
+       switch ($namespace) {
+               case CALDAV_NAMESPACE_PRIVATE:
+                       if ($uri == CALDAV_FRIENDICA_MINE || $uri == CALDAV_FRIENDICA_CONTACTS) return Sabre_CalDAV_Backend_Friendica::getInstance();
+                       else return Sabre_CalDAV_Backend_Private::getInstance();
+       }
+       throw new Exception("Calendar Namespace not found");
+}
+
+/**
+ * @param int $calendar_id
+ * @return Sabre_CalDAV_Backend_Common
+ * @throws Exception
+ */
+function wdcal_calendar_factory_by_id($calendar_id)
+{
+       $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendar_id);
+       return wdcal_calendar_factory($calendar["namespace"], $calendar["namespace_id"], $calendar["uri"], $calendar);
+}
+
+/**
+ * @param int $user_id
+ * @param bool $withcheck
+ * @return array
+ */
+function wdcal_create_std_calendars_get_statements($user_id, $withcheck = true)
+{
+       $stms = array();
+       $a = get_app();
+       $uris = array(
+               'private'                 => t("Private Calendar"),
+               CALDAV_FRIENDICA_MINE     => t("Friendica Events: Mine"),
+               CALDAV_FRIENDICA_CONTACTS => t("Friendica Events: Contacts"),
+       );
+       foreach ($uris as $uri => $name) {
+               $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($uri));
+               if (count($cals) == 0 || !$withcheck) $stms[] =
+                       sprintf("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `displayname`, `timezone`, `ctag`, `uri`, `has_vevent`, `has_vtodo`)
+                               VALUES (%d, %d, '%s', '%s', 1, '%s', 1, 0)",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($name), dbesc($a->timezone), dbesc($uri));
+       }
+       return $stms;
+}
+
+/**
+ */
+function wdcal_create_std_calendars()
+{
+       $a = get_app();
+       if (!local_user()) return;
+
+       $privates = q("SELECT COUNT(*) num FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+       if ($privates[0]["num"] > 0) return;
+
+       $stms = wdcal_create_std_calendars_get_statements($a->user["uid"]);
+       foreach ($stms as $stmt) q($stmt);
+}
+
+
+
+
+/**
+ * @param int $user_id
+ * @param bool $withcheck
+ * @return array
+ */
+function wdcal_create_std_addressbooks_get_statements($user_id, $withcheck = true)
+{
+       $stms = array();
+       $a = get_app();
+       $uris = array(
+               'private'                 => t("Private Addresses"),
+               CARDDAV_FRIENDICA_CONTACT     => t("Friendica Contacts"),
+       );
+       foreach ($uris as $uri => $name) {
+               $cals = q("SELECT * FROM %s%saddressbooks WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($uri));
+               if (count($cals) == 0 || !$withcheck) $stms[] =
+                       sprintf("INSERT INTO %s%saddressbooks (`namespace`, `namespace_id`, `displayname`, `ctag`, `uri`)
+                               VALUES (%d, %d, '%s', 1, '%s')",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CARDDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($name), dbesc($uri));
+       }
+       return $stms;
+}
+
+/**
+ */
+function wdcal_create_std_addressbooks()
+{
+       $a = get_app();
+       if (!local_user()) return;
+
+       $privates = q("SELECT COUNT(*) num FROM %s%addressbooks WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CARDDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+       if ($privates[0]["num"] > 0) return;
+
+       $stms = wdcal_create_std_addressbooks_get_statements($a->user["uid"]);
+       foreach ($stms as $stmt) q($stmt);
+}
diff --git a/dav/friendica/database-init.inc.php b/dav/friendica/database-init.inc.php
new file mode 100644 (file)
index 0000000..f91ddb2
--- /dev/null
@@ -0,0 +1,271 @@
+<?php
+
+
+/**
+ * @param int $from_version
+ * @return array|string[]
+ */
+function dav_get_update_statements($from_version)
+{
+       $stms = array();
+
+       if ($from_version == 1) {
+               $stms[] = "ALTER TABLE `dav_calendarobjects`
+                       ADD `calendar_id` INT NOT NULL AFTER `namespace_id` ,
+                       ADD `user_temp` INT NOT NULL AFTER `calendar_id` ";
+               $stms[] = "ALTER TABLE `dav_calendarobjects`
+                       ADD `componentType` ENUM( 'VEVENT', 'VTODO' ) NOT NULL DEFAULT 'VEVENT' AFTER `lastmodified` ,
+                       ADD `firstOccurence` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `componentType` ,
+                       ADD `lastOccurence` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `firstOccurence`";
+               $stms[] = "UPDATE dav_calendarobjects a JOIN dav_calendars b ON a.namespace = b.namespace AND a.namespace_id = b.namespace_id SET a.user_temp = b.uid";
+               $stms[] = "DROP TABLE IF EXISTS
+                       `dav_addressbooks_community` ,
+                       `dav_addressbooks_phone` ,
+                       `dav_cache_synchronized` ,
+                       `dav_caldav_log` ,
+                       `dav_calendars` ,
+                       `dav_cal_virtual_object_cache` ,
+                       `dav_cards` ,
+                       `dav_jqcalendar` ,
+                       `dav_locks` ,
+                       `dav_notifications` ;";
+
+               $stms = array_merge($stms, dav_get_create_statements(array("dav_calendarobjects")));
+
+               $user_ids = q("SELECT DISTINCT `uid` FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+               foreach ($user_ids as $user) $stms = array_merge($stms, wdcal_create_std_calendars_get_statements($user["uid"], false));
+
+               $stms[] = "UPDATE dav_calendarobjects a JOIN dav_calendars b
+                       ON b.`namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND a.`user_temp` = b.`namespace_id` AND b.`uri` = 'private'
+                       SET a.`calendar_id` = b.`id`";
+
+               $stms[] = "ALTER TABLE `dav_calendarobjects` DROP `namespace`, DROP `namespace_id`, DROP `user_temp`";
+
+       }
+
+       if (in_array($from_version, array(1, 2))) {
+               $stms[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `namespace` mediumint(9) NOT NULL,
+  `namespace_id` int(11) unsigned NOT NULL,
+  `displayname` varchar(200) NOT NULL,
+  `description` varchar(500) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `needs_rebuild` TINYINT NOT NULL DEFAULT '1',
+  `uri` varchar(50) NOT NULL,
+  `ctag` int(11) unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;";
+
+               $stms[] = "CREATE TABLE IF NOT EXISTS `dav_addressbookobjects` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `addressbook_id` int(11) unsigned NOT NULL,
+  `contact` int(11) DEFAULT NULL,
+  `carddata` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
+  `uri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `lastmodified` timestamp NULL DEFAULT NULL,
+  `needs_rebuild` tinyint(4) NOT NULL DEFAULT '0',
+  `manually_deleted` tinyint(4) NOT NULL DEFAULT '0',
+  `etag` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `size` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `namespace` (`addressbook_id`,`contact`),
+  KEY `contact` (`contact`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;";
+       }
+
+       return $stms;
+}
+
+/**
+ * @param array $except
+ * @return array
+ */
+function dav_get_create_statements($except = array())
+{
+       $arr = array();
+
+       if (!in_array("dav_caldav_log", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_caldav_log` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `uid` mediumint(9) NOT NULL,
+  `ip` varchar(15) NOT NULL,
+  `user_agent` varchar(100) NOT NULL,
+  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `method` varchar(10) NOT NULL,
+  `url` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `mitglied` (`uid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_calendarobjects", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendarobjects` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `calendar_id` int(11) NOT NULL,
+  `calendardata` text,
+  `uri` varchar(200) NOT NULL,
+  `lastmodified` timestamp NULL DEFAULT NULL,
+  `componentType` enum('VEVENT','VTODO') NOT NULL DEFAULT 'VEVENT',
+  `firstOccurence` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `lastOccurence` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `etag` varchar(15) NOT NULL,
+  `size` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uri` (`uri`),
+  KEY `calendar_id` (`calendar_id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_calendars", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendars` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `namespace` mediumint(9) NOT NULL,
+  `namespace_id` int(10) unsigned NOT NULL,
+  `calendarorder` int(11) NOT NULL DEFAULT '1',
+  `calendarcolor` char(6) NOT NULL DEFAULT '5858FF',
+  `displayname` varchar(200) NOT NULL,
+  `timezone` text NOT NULL,
+  `description` varchar(500) NOT NULL,
+  `uri` varchar(50) NOT NULL DEFAULT '',
+  `has_vevent` tinyint(4) NOT NULL DEFAULT '1',
+  `has_vtodo` tinyint(4) NOT NULL DEFAULT '1',
+  `ctag` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY (`namespace` , `namespace_id` , `uri`),
+  KEY `uri` (`uri`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_cal_virtual_object_cache", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_cache` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `calendar_id` int(10) unsigned NOT NULL,
+  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `data_uri` char(80) NOT NULL,
+  `data_summary` varchar(1000) NOT NULL,
+  `data_location` varchar(1000) NOT NULL,
+  `data_start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `data_end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `data_allday` tinyint(4) NOT NULL,
+  `data_type` varchar(20) NOT NULL,
+  `calendardata` text NOT NULL,
+  `size` int(11) NOT NULL,
+  `etag` varchar(15) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `data_uri` (`data_uri`),
+  KEY `ref_type` (`calendar_id`,`data_end`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_cal_virtual_object_sync", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_sync` (
+  `calendar_id` int(10) unsigned NOT NULL,
+  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`calendar_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_jqcalendar", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_jqcalendar` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `ical_recurr_uri` varchar(100) DEFAULT NULL,
+  `calendar_id` int(10) unsigned NOT NULL,
+  `calendarobject_id` int(10) unsigned NOT NULL,
+  `Summary` varchar(100) NOT NULL,
+  `StartTime` timestamp NULL DEFAULT NULL,
+  `EndTime` timestamp NULL DEFAULT NULL,
+  `IsEditable` tinyint(3) unsigned NOT NULL,
+  `IsAllDayEvent` tinyint(4) NOT NULL,
+  `IsRecurring` tinyint(4) NOT NULL,
+  `Color` char(6) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `calendarByStart` (`calendar_id`,`StartTime`),
+  KEY `calendarobject_id` (`calendarobject_id`,`ical_recurr_uri`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_notifications", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_notifications` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `ical_recurr_uri` varchar(100) DEFAULT NULL,
+  `calendar_id` int(11) NOT NULL,
+  `calendarobject_id` int(10) unsigned NOT NULL,
+  `action` enum('email','display') NOT NULL DEFAULT 'email',
+  `alert_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `notified` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `notified` (`notified`,`alert_date`),
+  KEY `calendar_id` (`calendar_id`,`calendarobject_id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
+
+       if (!in_array("dav_addressbooks", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `namespace` mediumint(9) NOT NULL,
+  `namespace_id` int(11) unsigned NOT NULL,
+  `displayname` varchar(200) NOT NULL,
+  `description` varchar(500) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `needs_rebuild` TINYINT NOT NULL DEFAULT '1',
+  `uri` varchar(50) NOT NULL,
+  `ctag` int(11) unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;";
+
+       if (!in_array("dav_addressbookobjects", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbookobjects` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `addressbook_id` int(11) unsigned NOT NULL,
+  `contact` int(11) DEFAULT NULL,
+  `carddata` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
+  `uri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `lastmodified` timestamp NULL DEFAULT NULL,
+  `needs_rebuild` tinyint(4) NOT NULL DEFAULT '0',
+  `manually_deleted` tinyint(4) NOT NULL DEFAULT '0',
+  `etag` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
+  `size` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `namespace` (`addressbook_id`,`contact`),
+  KEY `contact` (`contact`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;";
+
+       return $arr;
+}
+
+/**
+ * @return int
+ */
+function dav_check_tables()
+{
+       $x = q("DESCRIBE %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       if (!$x) return -1;
+       if (count($x) == 9) return 1; // Version 0.1
+
+       $x2 = q("show tables like '%s%saddressbooks'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       if (!$x2 || count($x2) == 0) return 2; // Version 0.2
+
+       if (count($x) == 12) return 0; // Correct
+
+       return -2; // Unknown version
+}
+
+
+/**
+ * @return array
+ */
+function dav_create_tables()
+{
+       $stms   = dav_get_create_statements();
+       $errors = array();
+
+       global $db;
+       foreach ($stms as $st) { // @TODO Friendica-dependent
+               $db->q($st);
+               if ($db->error) $errors[] = $db->error;
+       }
+
+       return $errors;
+}
+
+/**
+ * @return array
+ */
+function dav_upgrade_tables()
+{
+       $ver = dav_check_tables();
+       if (!in_array($ver, array(1, 2))) return array("Unknown error");
+       $stms   = dav_get_update_statements($ver);
+
+       $errors = array();
+       global $db;
+       foreach ($stms as $st) { // @TODO Friendica-dependent
+               $db->q($st);
+               if ($db->error) $errors[] = $db->error;
+       }
+
+       return $errors;
+}
diff --git a/dav/friendica/dav_caldav_backend_virtual_friendica.inc.php b/dav/friendica/dav_caldav_backend_virtual_friendica.inc.php
new file mode 100644 (file)
index 0000000..0afc03b
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+
+class Sabre_CalDAV_Backend_Friendica extends Sabre_CalDAV_Backend_Virtual
+{
+
+       /**
+        * @var null|Sabre_CalDAV_Backend_Friendica
+        */
+       private static $instance = null;
+
+       /**
+        * @static
+        * @return Sabre_CalDAV_Backend_Friendica
+        */
+       public static function getInstance()
+       {
+               if (self::$instance == null) {
+                       self::$instance = new Sabre_CalDAV_Backend_Friendica();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * @return int
+        */
+       public function getNamespace()
+       {
+               return CALDAV_NAMESPACE_PRIVATE;
+       }
+
+       /**
+        * @static
+        * @return string
+        */
+       public static function getBackendTypeName() {
+               return t("Friendica-Native events");
+       }
+
+       /**
+        * @static
+        * @param int $calendarId
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return void
+        */
+       protected static function createCache_internal($calendarId)
+       {
+               $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+
+               switch ($calendar["uri"]) {
+                       case CALDAV_FRIENDICA_MINE:
+                               $sql_where = " AND cid = 0";
+                               break;
+                       case CALDAV_FRIENDICA_CONTACTS:
+                               $sql_where = " AND cid > 0";
+                               break;
+                       default:
+                               throw new Sabre_DAV_Exception_NotFound();
+               }
+
+               $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"]));
+
+               foreach ($r as $row) {
+                       $uid       = $calendar["uri"] . "-" . $row["id"];
+                       $vevent    = dav_create_empty_vevent($uid);
+                       $component = dav_get_eventComponent($vevent);
+
+                       if ($row["adjust"]) {
+                               $start  = datetime_convert('UTC', date_default_timezone_get(), $row["start"]);
+                               $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]);
+                       } else {
+                               $start  = $row["start"];
+                               $finish = $row["finish"];
+                       }
+
+                       $summary = ($row["summary"] != "" ? $row["summary"] : $row["desc"]);
+                       $desc    = ($row["summary"] != "" ? $row["desc"] : "");
+                       $component->add("SUMMARY", icalendar_sanitize_string($summary));
+                       $component->add("LOCATION", icalendar_sanitize_string($row["location"]));
+                       $component->add("DESCRIPTION", icalendar_sanitize_string($desc));
+
+                       $ts_start = wdcal_mySql2PhpTime($start);
+                       $ts_end   = wdcal_mySql2PhpTime($start);
+
+                       $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false);
+                       $type           = ($allday ? Sabre\VObject\Property\DateTime::DATE : Sabre\VObject\Property\DateTime::LOCALTZ);
+
+                       $datetime_start = new Sabre\VObject\Property\DateTime("DTSTART");
+                       $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type);
+                       $datetime_end = new Sabre\VObject\Property\DateTime("DTEND");
+                       $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type);
+
+                       $component->add($datetime_start);
+                       $component->add($datetime_end);
+
+                       $data = $vevent->serialize();
+
+                       q("INSERT INTO %s%scal_virtual_object_cache (`calendar_id`, `data_uri`, `data_summary`, `data_location`, `data_start`, `data_end`, `data_allday`, `data_type`,
+                               `calendardata`, `size`, `etag`) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', %d, '%s')",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId, dbesc($uid), dbesc($summary), dbesc($row["location"]), dbesc($row["start"]), dbesc($row["finish"]),
+                                       ($allday ? 1 : 0), dbesc(($row["type"] == "birthday" ? "birthday" : "")), dbesc($data), strlen($data), md5($data));
+
+               }
+
+       }
+
+
+       /**
+        * @param array $row
+        * @param array $calendar
+        * @param string $base_path
+        * @return array
+        */
+       private function jqcal2wdcal($row, $calendar, $base_path)
+       {
+               if ($row["adjust"]) {
+                       $start  = datetime_convert('UTC', date_default_timezone_get(), $row["start"]);
+                       $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]);
+               } else {
+                       $start  = $row["start"];
+                       $finish = $row["finish"];
+               }
+
+               $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false);
+
+               $summary = (($row["summary"]) ? $row["summary"] : substr(preg_replace("/\[[^\]]*\]/", "", $row["desc"]), 0, 100));
+
+               return array(
+                       "jq_id"             => $row["id"],
+                       "ev_id"             => $row["id"],
+                       "summary"           => escape_tags($summary),
+                       "start"             => wdcal_mySql2PhpTime($start),
+                       "end"               => wdcal_mySql2PhpTime($finish),
+                       "is_allday"         => ($allday ? 1 : 0),
+                       "is_moredays"       => (substr($start, 0, 10) != substr($finish, 0, 10)),
+                       "is_recurring"      => ($row["type"] == "birthday"),
+                       "color"             => "7878ff",
+                       "is_editable"       => 0,
+                       "is_editable_quick" => 0,
+                       "location"          => $row["location"],
+                       "attendees"         => '',
+                       "has_notification"  => 0,
+                       "url_detail"        => $base_path . "/events/event/" . $row["id"],
+                       "url_edit"          => "",
+                       "special_type"      => ($row["type"] == "birthday" ? "birthday" : ""),
+               );
+       }
+
+
+       /**
+        * @param int $calendarId
+        * @param string $date_from
+        * @param string $date_to
+        * @param string $base_path
+        * @throws Sabre_DAV_Exception_NotFound
+        * @return array
+        */
+       public function listItemsByRange($calendarId, $date_from, $date_to, $base_path)
+       {
+               $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+
+               if ($calendar["namespace"] != CALDAV_NAMESPACE_PRIVATE) throw new Sabre_DAV_Exception_NotFound();
+
+               switch ($calendar["uri"]) {
+                       case CALDAV_FRIENDICA_MINE:
+                               $sql_where = " AND cid = 0";
+                               break;
+                       case CALDAV_FRIENDICA_CONTACTS:
+                               $sql_where = " AND cid > 0";
+                               break;
+                       default:
+                               throw new Sabre_DAV_Exception_NotFound();
+               }
+
+               if ($date_from != "") {
+                       if (is_numeric($date_from)) $sql_where .= " AND `finish` >= '" . date("Y-m-d H:i:s", $date_from) . "'";
+                       else $sql_where .= " AND `finish` >= '" . dbesc($date_from) . "'";
+               }
+               if ($date_to != "") {
+                       if (is_numeric($date_to)) $sql_where .= " AND `start` <= '" . date("Y-m-d H:i:s", $date_to) . "'";
+                       else $sql_where .= " AND `start` <= '" . dbesc($date_to) . "'";
+               }
+               $ret = array();
+
+               $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"]));
+
+               $a = get_app();
+               foreach ($r as $row) {
+                       $r                = $this->jqcal2wdcal($row, $calendar, $a->get_baseurl());
+                       $r["calendar_id"] = $calendar["id"];
+                       $ret[]            = $r;
+               }
+
+               return $ret;
+       }
+
+
+       /**
+        * Returns a list of calendars for a principal.
+        *
+        * Every project is an array with the following keys:
+        *  * id, a unique id that will be used by other functions to modify the
+        *    calendar. This can be the same as the uri or a database key.
+        *  * uri, which the basename of the uri with which the calendar is
+        *    accessed.
+        *  * principaluri. The owner of the calendar. Almost always the same as
+        *    principalUri passed to this method.
+        *
+        * Furthermore it can contain webdav properties in clark notation. A very
+        * common one is '{DAV:}displayname'.
+        *
+        * @param string $principalUri
+        * @return array
+        */
+       public function getCalendarsForUser($principalUri)
+       {
+               $n = dav_compat_principal2namespace($principalUri);
+               if ($n["namespace"] != $this->getNamespace()) return array();
+
+               $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"]));
+               $ret  = array();
+               foreach ($cals as $cal) {
+                       if (!in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue;
+
+                       $dat = array(
+                               "id"                                                      => $cal["id"],
+                               "uri"                                                     => $cal["uri"],
+                               "principaluri"                                            => $principalUri,
+                               '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag'] ? $cal['ctag'] : '0',
+                               '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array("VEVENT")),
+                               "calendar_class"                                          => "Sabre_CalDAV_Calendar_Virtual",
+                       );
+                       foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field];
+
+                       $ret[] = $dat;
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param int $calendar_id
+        * @param int $calendarobject_id
+        * @return string
+        */
+       function getItemDetailRedirect($calendar_id, $calendarobject_id)
+       {
+               $a    = get_app();
+               $item = q("SELECT `id` FROM `item` WHERE `event-id` = %d AND `uid` = %d AND deleted = 0", IntVal($calendarobject_id), $a->user["uid"]);
+               if (count($item) == 0) return "/events/";
+               return "/display/" . $a->user["nickname"] . "/" . IntVal($item[0]["id"]);
+
+       }
+}
diff --git a/dav/friendica/dav_carddav_backend_virtual_friendica.inc.php b/dav/friendica/dav_carddav_backend_virtual_friendica.inc.php
new file mode 100644 (file)
index 0000000..3a06d04
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+
+class Sabre_CardDAV_Backend_Friendica extends Sabre_CardDAV_Backend_Virtual
+{
+
+       /**
+        * @var null|Sabre_CardDAV_Backend_Friendica
+        */
+       private static $instance = null;
+
+       /**
+        * @static
+        * @return Sabre_CardDAV_Backend_Friendica
+        */
+       public static function getInstance() {
+               if (self::$instance == null) {
+                       self::$instance = new Sabre_CardDAV_Backend_Friendica();
+               }
+               return self::$instance;
+       }
+
+
+       /**
+        * @return int
+        */
+       public function getNamespace()
+       {
+               return CARDDAV_NAMESPACE_PRIVATE;
+       }
+
+       /**
+        * @static
+        * @return string
+        */
+       public static function getBackendTypeName() {
+               return t("Friendica-Contacts");
+       }
+
+       /**
+        * Returns the list of addressbooks for a specific user.
+        *
+        * @param string $principalUri
+        * @return array
+        */
+       public function getAddressBooksForUser($principalUri)
+       {
+               $uid = dav_compat_principal2uid($principalUri);
+
+               $addressBooks = array();
+
+               $books = q("SELECT id, ctag FROM %s%saddressbooks WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CARDDAV_NAMESPACE_PRIVATE, IntVal($uid), dbesc(CARDDAV_FRIENDICA_CONTACT));
+               $ctag = $books[0]["ctag"];
+
+               $addressBooks[] = array(
+                       'id'                                                                => $books[0]["id"],
+                       'uri'                                                               => "friendica",
+                       'principaluri'                                                      => $principalUri,
+                       '{DAV:}displayname'                                                 => t("Friendica-Contacts"),
+                       '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => t("Your Friendica-Contacts"),
+                       '{http://calendarserver.org/ns/}getctag'                            => $ctag,
+                       '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data'  =>
+                       new Sabre_CardDAV_Property_SupportedAddressData(),
+               );
+
+               return $addressBooks;
+
+       }
+
+       /**
+        * @static
+        * @param array $contact
+        * @return array
+        */
+       private static function dav_contactarr2vcardsource($contact)
+       {
+               $name        = explode(" ", $contact["name"]);
+               $first_name  = $last_name = "";
+               $middle_name = array();
+               $num         = count($name);
+               for ($i = 0; $i < $num && $first_name == ""; $i++) if ($name[$i] != "") {
+                       $first_name = $name[$i];
+                       unset($name[$i]);
+               }
+               for ($i = $num - 1; $i >= 0 && $last_name == ""; $i--) if (isset($name[$i]) && $name[$i] != "") {
+                       $last_name = $name[$i];
+                       unset($name[$i]);
+               }
+               foreach ($name as $n) if ($n != "") $middle_name[] = $n;
+               $vcarddata              = new vcard_source_data($first_name, implode(" ", $middle_name), $last_name);
+               $vcarddata->homepages[] = new vcard_source_data_homepage("pref", $contact["url"]);
+               $vcarddata->last_update = ($contact["last-update"] > 0 ? $contact["last-update"] : $contact["created"]);
+
+               $photo = q("SELECT * FROM photo WHERE `contact-id` = %d ORDER BY scale DESC", $contact["id"]); //prefer size 80x80
+               if ($photo && count($photo) > 0) {
+                       $photodata             = new vcard_source_data_photo();
+                       $photodata->width      = $photo[0]["width"];
+                       $photodata->height     = $photo[0]["height"];
+                       $photodata->type       = "JPEG";
+                       $photodata->binarydata = $photo[0]["data"];
+                       $vcarddata->photo      = $photodata;
+               }
+
+               switch ($contact["network"]) {
+                       case "face":
+                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("facebook", $contact["notify"], "http://www.facebook.com/" . $contact["notify"]);
+                               break;
+                       case "dfrn":
+                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("dfrn", $contact["nick"], $contact["url"]);
+                               break;
+                       case "twitter":
+                               $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("twitter", $contact["nick"], "http://twitter.com/" . $contact["nick"]); // @TODO Stimmt das?
+                               break;
+               }
+
+               $vcard = vcard_source_compile($vcarddata);
+               return array(
+                       "id"           => $contact["id"],
+                       "carddata"     => $vcard,
+                       "uri"          => $contact["id"] . ".vcf",
+                       "lastmodified" => wdcal_mySql2PhpTime($vcarddata->last_update),
+                       "etag"         => md5($vcard),
+                       "size"         => strlen($vcard),
+               );
+
+       }
+
+       /**
+        * @static
+        * @param int $addressbookId
+        * @param bool $force
+        * @throws Sabre_DAV_Exception_NotFound
+        */
+       static protected function createCache_internal($addressbookId, $force = false) {
+               //$notin    = (count($exclude_ids) > 0 ? " AND id NOT IN (" . implode(", ", $exclude_ids) . ") " : "");
+               $addressbook = q("SELECT * FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId));
+               if (count($addressbook) != 1 || $addressbook[0]["namespace"] != CARDDAV_NAMESPACE_PRIVATE) throw new Sabre_DAV_Exception_NotFound();
+               $contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0 ORDER BY `name` ASC", $addressbook[0]["namespace_id"]);
+
+               foreach ($contacts as $contact) {
+                       $x            = static::dav_contactarr2vcardsource($contact);
+                       q("INSERT INTO %s%saddressbookobjects (`addressbook_id`, `contact`, `carddata`, `uri`, `lastmodified`, `etag`, `size`) VALUES (%d, %d, '%s', '%s', NOW(), '%s', %d)",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId, $contact["id"], dbesc($x["carddata"]), dbesc($x["uri"]), dbesc($x["etag"]), $x["size"]
+                       );
+               }
+       }
+
+
+       /**
+        * @static
+        * @param int $addressbookId
+        * @param int $contactId
+        * @param bool $force
+        * @throws Sabre_DAV_Exception_NotFound
+        */
+       static protected function createCardCache($addressbookId, $contactId, $force = false)
+       {
+               $addressbook = q("SELECT * FROM %s%saddressbooks WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($addressbookId));
+               if (count($addressbook) != 1 || $addressbook[0]["namespace"] != CARDDAV_NAMESPACE_PRIVATE) throw new Sabre_DAV_Exception_NotFound();
+
+               $contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0 AND `id` = %d ORDER BY `name` ASC",
+                       $addressbook[0]["namespace_id"], IntVal($contactId));
+               $contact = $contacts[0];
+
+               $x            = static::dav_contactarr2vcardsource($contact);
+               q("INSERT INTO %s%saddressbookobjects (`addressbook_id`, `contact`, `carddata`, `uri`, `lastmodified`, `etag`, `size`) VALUES (%d, %d, '%s', '%s', NOW(), '%s', %d)",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $addressbookId, $contact["id"], dbesc($x["carddata"]), dbesc($x["uri"]), dbesc($x["etag"]), $x["size"]
+               );
+       }
+       /**
+        * Updates a card.
+        *
+        * The addressbook id will be passed as the first argument. This is the
+        * same id as it is returned from the getAddressbooksForUser method.
+        *
+        * The cardUri is a base uri, and doesn't include the full path. The
+        * cardData argument is the vcard body, and is passed as a string.
+        *
+        * It is possible to return an ETag from this method. This ETag should
+        * match that of the updated resource, and must be enclosed with double
+        * quotes (that is: the string itself must contain the actual quotes).
+        *
+        * You should only return the ETag if you store the carddata as-is. If a
+        * subsequent GET request on the same card does not have the same body,
+        * byte-by-byte and you did return an ETag here, clients tend to get
+        * confused.
+        *
+        * If you don't return an ETag, you can just return null.
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @param string $cardData
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return string|null
+        */
+       public function updateCard($addressBookId, $cardUri, $cardData)
+       {
+               $x = explode("-", $addressBookId);
+
+               $etag = md5($cardData);
+               q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1])
+               );
+               q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
+
+               return '"' . $etag . '"';
+       }
+
+       /**
+        * Deletes a card
+        *
+        * @param string $addressBookId
+        * @param string $cardUri
+        * @throws Sabre_DAV_Exception_Forbidden
+        * @return bool
+        */
+       public function deleteCard($addressBookId, $cardUri)
+       {
+               $x = explode("-", $addressBookId);
+
+               q("UPDATE %s%scards SET manually_deleted = 1 WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
+               q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
+
+               return true;
+       }
+
+}
diff --git a/dav/friendica/dav_friendica_auth.inc.php b/dav/friendica/dav_friendica_auth.inc.php
new file mode 100644 (file)
index 0000000..acc33fa
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+class Sabre_DAV_Auth_Backend_Std extends Sabre_DAV_Auth_Backend_AbstractBasic {
+
+    public function __construct() {
+    }
+
+
+       /**
+        * @var Sabre_DAV_Auth_Backend_Std|null
+        */
+       private static $intstance = null;
+
+       /**
+        * @static
+        * @return Sabre_DAV_Auth_Backend_Std
+        */
+       public static function &getInstance() {
+               if (is_null(self::$intstance)) {
+                       self::$intstance = new Sabre_DAV_Auth_Backend_Std();
+               }
+               return self::$intstance;
+       }
+
+
+       /**
+        * @return array
+        */
+       public function getUsers() {
+        return array($this->currentUser);
+    }
+
+       /**
+        * @return null|string
+        */
+       public function getCurrentUser() {
+        return $this->currentUser;
+    }
+
+       /**
+        * Authenticates the user based on the current request.
+        *
+        * If authentication is successful, true must be returned.
+        * If authentication fails, an exception must be thrown.
+        *
+        * @param Sabre_DAV_Server $server
+        * @param string $realm
+        * @throws Sabre_DAV_Exception_NotAuthenticated
+        * @return bool
+        */
+       public function authenticate(Sabre_DAV_Server $server, $realm) {
+
+               $a = get_app();
+               if (isset($a->user["uid"])) {
+                       $this->currentUser = strtolower($a->user["nickname"]);
+                       return true;
+               }
+
+               $auth = new Sabre_HTTP_BasicAuth();
+               $auth->setHTTPRequest($server->httpRequest);
+               $auth->setHTTPResponse($server->httpResponse);
+               $auth->setRealm($realm);
+               $userpass = $auth->getUserPass();
+               if (!$userpass) {
+                       $auth->requireLogin();
+                       throw new Sabre_DAV_Exception_NotAuthenticated('No basic authentication headers were found');
+               }
+
+               // Authenticates the user
+               if (!$this->validateUserPass($userpass[0],$userpass[1])) {
+                       $auth->requireLogin();
+                       throw new Sabre_DAV_Exception_NotAuthenticated('Username or password does not match');
+               }
+               $this->currentUser = strtolower($userpass[0]);
+               return true;
+       }
+
+
+       /**
+        * @param string $username
+        * @param string $password
+        * @return bool
+        */
+       protected function validateUserPass($username, $password) {
+               $encrypted = hash('whirlpool',trim($password));
+               $r = q("SELECT COUNT(*) anz FROM `user` WHERE `nickname` = '%s' AND `password` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1",
+                       dbesc(trim($username)),
+                       dbesc($encrypted)
+               );
+               return ($r[0]["anz"] == 1);
+    }
+    
+}
diff --git a/dav/friendica/dav_friendica_principal.inc.php b/dav/friendica/dav_friendica_principal.inc.php
new file mode 100644 (file)
index 0000000..780bcd2
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+
+
+class Sabre_DAVACL_PrincipalBackend_Std implements Sabre_DAVACL_IPrincipalBackend
+{
+
+       /**
+        * Principals prefix
+        *
+        * @var string
+        */
+       public $prefix = 'principals/users';
+
+       /**
+        * @var Sabre_DAV_Auth_Backend_AbstractBasic $authBackend;
+        */
+       protected $authBackend;
+
+       public function __construct(&$authBackend)
+       {
+
+               $this->authBackend = &$authBackend;
+
+       }
+
+
+       /**
+        * @var Sabre_DAVACL_IPrincipalBackend|null
+        */
+       private static $intstance = null;
+
+       /**
+        * @static
+        * @return Sabre_DAVACL_IPrincipalBackend
+        */
+       public static function &getInstance() {
+               if (is_null(self::$intstance)) {
+                       $authBackend              = Sabre_DAV_Auth_Backend_Std::getInstance();
+                       self::$intstance = new Sabre_DAVACL_PrincipalBackend_Std($authBackend);
+               }
+               return self::$intstance;
+       }
+
+       /**
+        * Returns a list of principals based on a prefix.
+        *
+        * This prefix will often contain something like 'principals'. You are only
+        * expected to return principals that are in this base path.
+        *
+        * You are expected to return at least a 'uri' for every user, you can
+        * return any additional properties if you wish so. Common properties are:
+        *   {DAV:}displayname
+        *   {http://sabredav.org/ns}email-address - This is a custom SabreDAV
+        *     field that's actualy injected in a number of other properties. If
+        *     you have an email address, use this property.
+        *
+        * @param string $prefixPath
+        * @return array
+        */
+       public function getPrincipalsByPrefix($prefixPath)
+       {
+
+               // This backend only support principals in one collection
+               if ($prefixPath !== $this->prefix) return array();
+
+               $users = array();
+
+               $r = q("SELECT `nickname` FROM `user` WHERE `nickname` = '%s'", escape_tags($this->authBackend->getCurrentUser()) );
+               foreach ($r as $t) {
+                       $users[] = array(
+                               'uri'               => $this->prefix . '/' . strtolower($t['nickname']),
+                               '{DAV:}displayname' => $t['nickname'],
+                       );
+               }
+
+               return $users;
+
+       }
+
+       /**
+        * Returns a specific principal, specified by it's path.
+        * The returned structure should be the exact same as from
+        * getPrincipalsByPrefix.
+        *
+        * @param string $path
+        * @return array
+        */
+       public function getPrincipalByPath($path)
+       {
+
+               list($prefixPath, $userName) = Sabre_DAV_URLUtil::splitPath($path);
+
+               // This backend only support principals in one collection
+               if ($prefixPath !== $this->prefix) return null;
+
+               $r = q("SELECT `nickname` FROM `user` WHERE `nickname` = '%s'", escape_tags($userName) );
+               if (count($r) == 0) return array();
+
+               return array(
+                       'uri'               => $this->prefix . '/' . strtolower($r[0]['nickname']),
+                       '{DAV:}displayname' => $r[0]['nickname'],
+               );
+
+       }
+
+
+       function getGroupMemberSet($principal)
+       {
+               return array();
+       }
+
+       function getGroupMembership($principal)
+       {
+               return array();
+       }
+
+
+       /**
+        * Updates the list of group members for a group principal.
+        *
+        * The principals should be passed as a list of uri's.
+        *
+        * @param string $principal
+        * @param array $members
+        * @throws Sabre_DAV_Exception
+        * @return void
+        */
+       public function setGroupMemberSet($principal, array $members)
+       {
+               throw new Sabre_DAV_Exception('Operation not supported');
+       }
+
+       /**
+        * Updates one ore more webdav properties on a principal.
+        *
+        * The list of mutations is supplied as an array. Each key in the array is
+        * a propertyname, such as {DAV:}displayname.
+        *
+        * Each value is the actual value to be updated. If a value is null, it
+        * must be deleted.
+        *
+        * This method should be atomic. It must either completely succeed, or
+        * completely fail. Success and failure can simply be returned as 'true' or
+        * 'false'.
+        *
+        * It is also possible to return detailed failure information. In that case
+        * an array such as this should be returned:
+        *
+        * array(
+        *   200 => array(
+        *      '{DAV:}prop1' => null,
+        *   ),
+        *   201 => array(
+        *      '{DAV:}prop2' => null,
+        *   ),
+        *   403 => array(
+        *      '{DAV:}prop3' => null,
+        *   ),
+        *   424 => array(
+        *      '{DAV:}prop4' => null,
+        *   ),
+        * );
+        *
+        * In this previous example prop1 was successfully updated or deleted, and
+        * prop2 was succesfully created.
+        *
+        * prop3 failed to update due to '403 Forbidden' and because of this prop4
+        * also could not be updated with '424 Failed dependency'.
+        *
+        * This last example was actually incorrect. While 200 and 201 could appear
+        * in 1 response, if there's any error (403) the other properties should
+        * always fail with 423 (failed dependency).
+        *
+        * But anyway, if you don't want to scratch your head over this, just
+        * return true or false.
+        *
+        * @param string $path
+        * @param array $mutations
+        * @return array|bool
+        */
+       function updatePrincipal($path, $mutations)
+       {
+               // TODO: Implement updatePrincipal() method.
+       }
+
+       /**
+        * This method is used to search for principals matching a set of
+        * properties.
+        *
+        * This search is specifically used by RFC3744's principal-property-search
+        * REPORT. You should at least allow searching on
+        * http://sabredav.org/ns}email-address.
+        *
+        * The actual search should be a unicode-non-case-sensitive search. The
+        * keys in searchProperties are the WebDAV property names, while the values
+        * are the property values to search on.
+        *
+        * If multiple properties are being searched on, the search should be
+        * AND'ed.
+        *
+        * This method should simply return an array with full principal uri's.
+        *
+        * If somebody attempted to search on a property the backend does not
+        * support, you should simply return 0 results.
+        *
+        * You can also just return 0 results if you choose to not support
+        * searching at all, but keep in mind that this may stop certain features
+        * from working.
+        *
+        * @param string $prefixPath
+        * @param array $searchProperties
+        * @return array
+        */
+       function searchPrincipals($prefixPath, array $searchProperties)
+       {
+               // TODO: Implement searchPrincipals() method.
+       }
+}
diff --git a/dav/friendica/layout.fnk.php b/dav/friendica/layout.fnk.php
new file mode 100644 (file)
index 0000000..80a9ae2
--- /dev/null
@@ -0,0 +1,535 @@
+<?php
+
+
+/**
+ *
+ */
+function wdcal_addRequiredHeaders()
+{
+       $a = get_app();
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.css' . '" media="all" />' . "\r\n";
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.min.js"></script>' . "\r\n";
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/colorpicker/colorPicker.css' . '" media="all" />' . "\r\n";
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/colorpicker/jquery.colorPicker.min.js"></script>' . "\r\n";
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/timepicker/timePicker.css' . '" media="all" />' . "\r\n";
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/timepicker/jquery.timePicker.min.js"></script>' . "\r\n";
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/friendica/wdcal.css' . '" media="all" />' . "\r\n";
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal.js"></script>' . "\r\n";
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal/css/calendar.css' . '" media="all" />' . "\r\n";
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal/css/main.css' . '" media="all" />' . "\r\n";
+
+       switch (get_config("system", "language")) {
+               case "de":
+                       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_DE.js"></script>' . "\r\n";
+                       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery.ui.datepicker-de.js"></script>' . "\r\n";
+                       break;
+               default:
+                       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_EN.js"></script>' . "\r\n";
+       }
+
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/jquery.calendar.js"></script>' . "\r\n";
+       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/main.js"></script>' . "\r\n";
+}
+
+
+
+/**
+ * @param int $calendar_id
+ */
+function wdcal_print_user_ics($calendar_id)
+{
+       $calendar_id = IntVal($calendar_id);
+
+       $a = get_app();
+       header("Content-type: text/plain");
+
+       $str  = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\n";
+       $cals = q("SELECT * FROM %s%scalendars WHERE `id` = %d AND `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id, CALDAV_NAMESPACE_PRIVATE, $a->user["uid"]);
+       if (count($cals) > 0) {
+               $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d ORDER BY `firstOccurence`", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id);
+
+               foreach ($objs as $obj) {
+                       preg_match("/BEGIN:VEVENT(.*)END:VEVENT/siu", $obj["calendardata"], $matches);
+                       $str2 = preg_replace("/([^\\r])\\n/siu", "\\1\r\n", $matches[0]);
+                       $str2 = preg_replace("/MAILTO:.*[^:a-z0-9_\+äöüß\\n\\n@-]+.*(:|\\r\\n[^ ])/siU", "\\1", $str2);
+                       $str .= $str2 . "\r\n";
+               }
+       }
+       $str .= "END:VCALENDAR\r\n";
+
+       echo $str;
+       killme();
+}
+
+
+/**
+ * @param int $calendar_id
+ * @return string
+ */
+function wdcal_import_user_ics($calendar_id) {
+       $a = get_app();
+       $calendar_id = IntVal($calendar_id);
+       $o = "";
+
+       $server = dav_create_server(true, true, false);
+       $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+       if (!$calendar) goaway($a->get_baseurl() . "/dav/wdcal/");
+
+       if (isset($_REQUEST["save"])) {
+               check_form_security_token_redirectOnErr('/dav/settings/', 'icsimport');
+
+               if ($_FILES["ics_file"]["tmp_name"] != "" && is_uploaded_file($_FILES["ics_file"]["tmp_name"])) try {
+                       $text = file_get_contents($_FILES["ics_file"]["tmp_name"]);
+
+                       /** @var Sabre\VObject\Component\VCalendar $vObject  */
+                       $vObject        = Sabre\VObject\Reader::read($text);
+                       $comp = $vObject->getComponents();
+                       $imported = array();
+                       foreach ($comp as $c) try {
+                               /** @var Sabre\VObject\Component\VEvent $c */
+                               $uid = $c->__get("UID")->value;
+                               if (!isset($imported[$uid])) $imported[$uid] = "";
+                               $imported[$uid] .= $c->serialize();
+                       } catch (Exception $e) {
+                               notice(t("Something went wrong when trying to import the file. Sorry. Maybe some events were imported anyway."));
+                       }
+
+                       if (isset($_REQUEST["overwrite"])) {
+                               $children = $calendar->getChildren();
+                               foreach ($children as $child) {
+                                       /** @var Sabre_CalDAV_CalendarObject $child */
+                                       $child->delete();
+                               }
+                               $i = 1;
+                       } else {
+                               $i = 0;
+                               $children = $calendar->getChildren();
+                               foreach ($children as $child) {
+                                       /** @var Sabre_CalDAV_CalendarObject $child */
+                                       $name = $child->getName();
+                                       if (preg_match("/import\-([0-9]+)\.ics/siu", $name, $matches)) {
+                                               if ($matches[1] > $i) $i = $matches[1];
+                                       };
+                               }
+                               $i++;
+                       }
+
+                       foreach ($imported as $object) try {
+
+                               $str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\n";
+                               $str .= trim($object);
+                               $str .= "\r\nEND:VCALENDAR\r\n";
+
+                               $calendar->createFile("import-" . $i . ".ics", $str);
+                               $i++;
+                       } catch (Exception $e) {
+                               notice(t("Something went wrong when trying to import the file. Sorry."));
+                       }
+
+                       $o = t("The ICS-File has been imported.");
+               } catch (Exception $e) {
+                       notice(t("Something went wrong when trying to import the file. Sorry. Maybe some events were imported anyway."));
+               } else {
+                       notice(t("No file was uploaded."));
+               }
+       }
+
+
+       $o .= "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
+
+       $num = q("SELECT COUNT(*) num FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id);
+
+       $o .= "<h2>" . t("Import a ICS-file") . "</h2>";
+       $o .= '<form method="POST" action="' . $a->get_baseurl() . '/dav/wdcal/' . $calendar_id . '/ics-import/" enctype="multipart/form-data">';
+       $o .= "<input type='hidden' name='form_security_token' value='" . get_form_security_token('icsimport') . "'>\n";
+       $o .= "<label for='ics_file'>" . t("ICS-File") . "</label><input type='file' name='ics_file' id='ics_file'><br>\n";
+       if ($num[0]["num"] > 0) $o .= "<label for='overwrite'>" . str_replace("#num#", $num[0]["num"], t("Overwrite all #num# existing events")) . "</label> <input name='overwrite' id='overwrite' type='checkbox'><br>\n";
+       $o .= "<input type='submit' name='save' value='" . t("Upload") . "'>";
+       $o .= '</form>';
+
+       return $o;
+}
+
+
+/**
+ * @param array|Sabre_CalDAV_Calendar[] $calendars
+ * @param array|int[] $calendars_selected
+ * @param string $data_feed_url
+ * @param string $view
+ * @param int $theme
+ * @param int $height_diff
+ * @param bool $readonly
+ * @param string $curr_day
+ * @param array $add_params
+ * @param bool $show_nav
+ * @return string
+ */
+function wdcal_printCalendar($calendars, $calendars_selected, $data_feed_url, $view = "week", $theme = 0, $height_diff = 175, $readonly = false, $curr_day = "", $add_params = array(), $show_nav = true)
+{
+
+       $a            = get_app();
+       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
+
+       if (count($calendars_selected) == 0) foreach ($calendars as $c) {
+               $prop                 = $c->getProperties(array("id"));
+               $calendars_selected[] = $prop["id"];
+       }
+
+       $opts = array(
+               "view"             => $view,
+               "theme"            => $theme,
+               "readonly"         => $readonly,
+               "height_diff"      => $height_diff,
+               "weekstartday"     => $localization->getFirstDayOfWeek(),
+               "data_feed_url"    => $data_feed_url,
+               "date_format_dm1"  => $localization->dateformat_js_dm1(),
+               "date_format_dm2"  => $localization->dateformat_js_dm2(),
+               "date_format_dm3"  => $localization->dateformat_js_dm3(),
+               "date_format_full" => $localization->dateformat_datepicker_js(),
+               "baseurl"          => $a->get_baseurl() . "/dav/wdcal/",
+       );
+
+       $x = '
+<script>
+       $(function() {
+               $("#animexxcalendar").animexxCalendar(' . json_encode($opts) . ');
+       });
+</script>
+
+<div id="animexxcalendar" class="animexxcalendar">
+       <div class="calselect"><strong>Available Calendars:</strong>';
+
+       foreach ($calendars as $cal) {
+               $cal_id = $cal->getProperties(array("id", DAV_DISPLAYNAME));
+               $x .= '<label style="margin-left: 10px; margin-right: 10px;"><input type="checkbox" name="cals[]" value="' . $cal_id["id"] . '"';
+               $found = false;
+               foreach ($calendars_selected as $pre) if ($pre["id"] == $cal_id["id"]) $found = true;
+               if ($found) $x .= ' checked';
+               $x .= '> ' . escape_tags($cal_id[DAV_DISPLAYNAME]) . '</label> ';
+       }
+
+       $x .= '</div>
+       <div class="calhead" style="padding-left:1px;padding-right:1px;">
+               <div class="ptogtitle loaderror" style="display: none;">Sorry, could not load your data, please try again later</div>
+       </div>';
+
+       if ($show_nav) {
+
+               $x .= '<div class="ctoolbar">
+               <div class="fbutton faddbtn" style="float: right;">
+                       <div><a href="' . $a->get_baseurl() . '/dav/settings/"><span>' . t("Settings") . ' / ' . t("Help") . '</span></a></div>
+               </div>
+               <div class="fbutton addcal">
+                       <div><a href="' . $a->get_baseurl() . '/dav/wdcal/new/" class="addcal">' . t("New event") . '</a></div>
+               </div>
+               <div class="btnseparator"></div>
+               <div class="fbutton showtodaybtn">
+                       <div><span class="showtoday">' . t("Today") . '</span></div>
+               </div>
+               <div class="btnseparator"></div>
+
+               <div class="fbutton showdaybtn">
+                       <div><span title="Day" class="showdayview ';
+
+               if ($view == "day") $x .= 'fcurrent';
+
+               $x .= '">' . t("Day") . '</span></div>
+               </div>
+               <div class="fbutton showweekbtn ';
+
+               if ($view == "week") $x .= "fcurrent";
+
+               $x .= '">
+                       <div><span title="Week" class="showweekview">' . t("Week") . '</span></div>
+               </div>
+               <div class="showmonthbtn fbutton ';
+
+               if ($view == "month") $x .= 'fcurrent';
+
+               $x .= '">
+                       <div><span title="Month" class="showmonthview">' . t("Month") . '</span></div>
+
+               </div>
+               <div class="btnseparator"></div>
+               <div class="fbutton showreflashbtn">
+                       <div><span class="showdayflash">' . t("Reload") . '</span></div>
+               </div>
+               <div class="btnseparator"></div>
+               <div title="' . t("Previous") . '"  class="fbutton sfprevbtn">
+                       <span class="fprev"></span>
+               </div>
+               <div title="' . t("Next") . '" class="fbutton sfnextbtn">
+                       <span class="fnext"></span>
+               </div>
+               <div class="fshowdatep fbutton" style="white-space: nowrap; position: relative;">
+                       <input name="txtshow" class="hdtxtshow" style="position: absolute; bottom: 0; left: 0; width: 0; height: 0; border: 0; padding: 0; margin: 0;">
+                       <span class="txtdatetimeshow">' . t("Date") . '</span>
+               </div>
+               <div style="float: right;">
+                       <div class="clear"></div>
+               </div>
+       </div>';
+       }
+       $x .= '
+       <div style="padding:1px;">
+               <div class="calmain printborder">
+                       <div class="gridcontainer" style="overflow-y: visible;"></div>
+               </div>
+       </div>
+</div>';
+
+       return $x;
+}
+
+
+/**
+ * @param int $calendar_id
+ * @param int $calendarobject_id
+ * @return string
+ */
+function wdcal_getDetailPage($calendar_id, $calendarobject_id)
+{
+       $a = get_app();
+
+       try {
+               $details = null;
+               $server  = dav_create_server(true, true, false);
+               $cal     = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_READ);
+               $obj     = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($calendarobject_id);
+               dav_get_current_user_calendarobject($server, $cal, $obj["uri"], DAV_ACL_READ); // Check permissions
+
+               $calbackend = wdcal_calendar_factory_by_id($calendar_id);
+               $redirect   = $calbackend->getItemDetailRedirect($calendar_id, $calendarobject_id);
+
+               if ($redirect !== null) goaway($a->get_baseurl() . $redirect);
+
+               $details = $obj;
+       } catch (Exception $e) {
+               info(t("Error") . ": " . $e);
+               goaway($a->get_baseurl() . "/dav/wdcal/");
+       }
+
+       return print_r($details, true);
+}
+
+
+/**
+ * @param int $calendar_id
+ * @param int $uri
+ * @return string
+ */
+function wdcal_getEditPage($calendar_id, $uri)
+{
+       $a            = get_app();
+       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
+
+       return wdcal_getEditPage_str($localization, $a->get_baseurl(), $calendar_id, $uri);
+}
+
+/**
+ * @return string
+ */
+function wdcal_getNewPage()
+{
+       $a            = get_app();
+       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
+
+       return wdcal_getEditPage_str($localization, $a->get_baseurl(), 0, 0);
+}
+
+
+/**
+ * @param App $a
+ * @return string
+ */
+function wdcal_getSettingsPage(&$a)
+{
+
+       if (!local_user()) {
+               notice(t('Permission denied.') . EOL);
+               return '';
+       }
+
+       if (isset($_REQUEST["save"])) {
+               check_form_security_token_redirectOnErr('/dav/settings/', 'calprop');
+               set_pconfig($a->user["uid"], "dav", "dateformat", $_REQUEST["wdcal_date_format"]);
+               info(t('The new values have been saved.'));
+       }
+
+       if (isset($_REQUEST["save_cals"])) {
+               check_form_security_token_redirectOnErr('/dav/settings/', 'calprop');
+
+               $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($a->user["uid"]));
+               foreach ($r as $cal) {
+                       $backend = wdcal_calendar_factory($cal["namespace"], $cal["namespace_id"], $cal["uri"], $cal);
+                       $change_sql = "";
+                       $col = substr($_REQUEST["color"][$cal["id"]], 1);
+                       if (strtolower($col) != strtolower($cal["calendarcolor"])) $change_sql .= ", `calendarcolor` = '" . dbesc($col) . "'";
+                       if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual")) {
+                               if ($_REQUEST["uri"][$cal["id"]] != $cal["uri"]) $change_sql .= ", `uri` = '" . dbesc($_REQUEST["uri"][$cal["id"]]) . "'";
+                               if ($_REQUEST["name"][$cal["id"]] != $cal["displayname"]) $change_sql .= ", `displayname` = '" . dbesc($_REQUEST["name"][$cal["id"]]) . "'";
+                       }
+                       if ($change_sql != "") {
+                               q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 $change_sql WHERE `id` = %d AND `namespace_id` = %d AND `namespace_id` = %d",
+                                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $cal["id"], CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+                               info(t('The calendar has been updated.'));
+                       }
+               }
+
+               if (isset($_REQUEST["uri"]["new"]) && $_REQUEST["uri"]["new"] != "" && $_REQUEST["name"]["new"] && $_REQUEST["name"]["new"] != "") {
+                       $order = q("SELECT MAX(`calendarorder`) ord FROM %s%scalendars WHERE `namespace_id` = %d AND `namespace_id` = %d",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+                       $neworder = $order[0]["ord"] + 1;
+                       q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `calendarorder`, `calendarcolor`, `displayname`, `timezone`, `uri`, `has_vevent`, `ctag`)
+                               VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', 1, 1)",
+                               CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]), $neworder, dbesc(strtolower(substr($_REQUEST["color"]["new"], 1))),
+                               dbesc($_REQUEST["name"]["new"]), dbesc($a->timezone), dbesc($_REQUEST["uri"]["new"])
+                       );
+                       info(t('The new calendar has been created.'));
+               }
+       }
+
+       if (isset($_REQUEST["remove_cal"])) {
+               check_form_security_token_redirectOnErr('/dav/settings/', 'del_cal', 't');
+
+               $c = q("SELECT * FROM %s%scalendars WHERE `id` = %d AND `namespace_id` = %d AND `namespace_id` = %d",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]), CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+               if (count($c) != 1) killme();
+
+               $calobjs = q("SELECT `id` FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]));
+
+               $newcal = q("SELECT * FROM %s%scalendars WHERE `id` != %d AND `namespace_id` = %d AND `namespace_id` = %d ORDER BY `calendarcolor` LIMIT 0,1",
+                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]), CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]));
+               if (count($newcal) != 1) killme();
+
+               q("UPDATE %s%scalendarobjects SET `calendar_id` = %d WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($newcal[0]["id"]), IntVal($c[0]["id"]));
+
+               foreach ($calobjs as $calobj) renderCalDavEntry_calobj_id($calobj["id"]);
+
+               q("DELETE FROM %s%scalendars WHERE `id` = %s", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]));
+               q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `id` = " . CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $newcal[0]["id"]);
+
+               info(t('The calendar has been deleted.'));
+       }
+
+       $o = "";
+
+       $o .= "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
+
+       $o .= '<h3>' . t('Calendar Settings') . '</h3>';
+
+       $current_format = wdcal_local::getInstanceByUser($a->user["uid"]);
+       $o .= '<form method="POST" action="' . $a->get_baseurl() . '/dav/settings/">';
+       $o .= "<input type='hidden' name='form_security_token' value='" . get_form_security_token('calprop') . "'>\n";
+
+       $o .= '<label for="wdcal_date_format">' . t('Date format') . ':</label><select name="wdcal_date_format" id="wdcal_date_format" size="1">';
+       $classes = wdcal_local::getInstanceClasses();
+       foreach ($classes as $c) {
+               $o .= '<option value="' . $c::getID() . '" ';
+               if ($c::getID() == $current_format::getID()) $o .= 'selected';
+               $o .= '>' . escape_tags($c::getName()) . '</option>';
+       }
+       $o .= '</select><br>';
+
+       $o .= '<label for="wdcal_time_zone">' . t('Time zone') . ':</label><input id="wdcal_time_zone" value="' . $a->timezone . '" disabled><br>';
+
+       $o .= '<input type="submit" name="save" value="' . t('Save') . '">';
+       $o .= '</form>';
+
+
+       $o .= '<br><br><h3>' . t('Calendars') . '</h3>';
+       $o .= '<form method="POST" action="' . $a->get_baseurl() . '/dav/settings/">';
+       $o .= "<input type='hidden' name='form_security_token' value='" . get_form_security_token('calprop') . "'>\n";
+       $o .= "<table><tr><th>Type</th><th>Color</th><th>Name</th><th>URI (for CalDAV)</th><th>ICS</th></tr>";
+
+       $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($a->user["uid"]));
+       $private_max = 0;
+       $num_non_virtual = 0;
+       foreach ($r as $x) {
+               $backend = wdcal_calendar_factory($x["namespace"], $x["namespace_id"], $x["uri"], $x);
+               if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual")) $num_non_virtual++;
+       }
+       foreach ($r as $x) {
+               $p = explode("private-", $x["uri"]);
+               if (count($p) == 2 && $p[1] > $private_max) $private_max = $p[1];
+
+               $backend = wdcal_calendar_factory($x["namespace"], $x["namespace_id"], $x["uri"], $x);
+               $disabled = (is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") ? "disabled" : "");
+               $o .= "<tr>";
+               $o .= "<td style='padding: 2px;'>" . escape_tags($backend->getBackendTypeName()) . "</td>";
+               $o .= "<td style='padding: 2px; text-align: center;'><input style='margin-left: 10px; width: 70px;' class='cal_color' name='color[" . $x["id"] . "]' id='cal_color_" . $x["id"] . "' value='#" . (strlen($x["calendarcolor"]) != 6 ? "5858ff" : escape_tags($x["calendarcolor"])) . "'></td>";
+               $o .= "<td style='padding: 2px;'><input style='margin-left: 10px;' name='name[" . $x["id"] . "]' value='" . escape_tags($x["displayname"]) . "' $disabled></td>";
+               $o .= "<td style='padding: 2px;'><input style='margin-left: 10px; width: 150px;' name='uri[" . $x["id"] . "]' value='" . escape_tags($x["uri"]) . "' $disabled></td>";
+               $o .= "<td style='padding: 2px;'><a href='" . $a->get_baseurl() . "/dav/wdcal/" . $x["id"] . "/ics-export/'>Export</a>";
+               if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") && $num_non_virtual > 1) $o .= " / <a href='" . $a->get_baseurl() . "/dav/wdcal/" . $x["id"] . "/ics-import/'>Import</a>";
+               $o .= "</td>";
+               $o .= "<td style='padding: 2px; padding-left: 50px;'>";
+               if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") && $num_non_virtual > 1) $o .= "<a href='" . $a->get_baseurl() . "/dav/settings/?remove_cal=" . $x["id"] . "&amp;t=" . get_form_security_token("del_cal") . "' class='delete_cal'>Delete</a>";
+               $o .= "</td>\n";
+               $o .= "</tr>\n";
+       }
+
+       $private_max++;
+       $o .= "<tr class='cal_add_row' style='display: none;'>";
+       $o .= "<td style='padding: 2px;'>" . escape_tags(Sabre_CalDAV_Backend_Private::getBackendTypeName()) . "</td>";
+       $o .= "<td style='padding: 2px; text-align: center;'><input style='margin-left: 10px; width: 70px;' class='cal_color' name='color[new]' id='cal_color_new' value='#5858ff'></td>";
+       $o .= "<td style='padding: 2px;'><input style='margin-left: 10px;' name='name[new]' value='Another calendar'></td>";
+       $o .= "<td style='padding: 2px;'><input style='margin-left: 10px; width: 150px;' name='uri[new]' value='private-${private_max}'></td>";
+       $o .= "<td></td><td></td>";
+       $o .= "</tr>\n";
+
+       $o .= "</table>";
+       $o .= "<div style='text-align: center;'>[<a href='#' class='calendar_add_caller'>" . t("Create a new calendar") . "</a>]</div>";
+       $o .= '<input type="submit" name="save_cals" value="' . t('Save') . '">';
+       $o .= '</form>';
+       $baseurl = $a->get_baseurl();
+       $o .= "<script>\$(function() {
+               wdcal_edit_calendars_start('" . $current_format->dateformat_datepicker_js() . "', '${baseurl}/dav/');
+       });</script>";
+
+
+       $o .= "<br><h3>" . t("Limitations") . "</h3>";
+
+       $o .= "- The native friendica events are embedded as read-only, half-transparent in the calendar.<br>";
+
+       $o .= "<br><h3>" . t("Warning") . "</h3>";
+
+       $o .= "This plugin still is in a very early stage of development. Expect major bugs!<br>";
+
+       $o .= "<br><h3>" . t("Synchronization (iPhone, Thunderbird Lightning, Android, ...)") . "</h3>";
+
+       $o .= 'This plugin enables synchronization of your dates and contacts with CalDAV- and CardDAV-enabled programs or devices.<br>
+               As an example, the instructions how to set up two-way synchronization with an iPhone/iPodTouch are provided below.<br>
+               Unfortunately, Android does not have native support for CalDAV or CardDAV, so an app has to be installed.<br>
+               On desktops, the Lightning-extension to Mozilla Thunderbird should be able to use this plugin as a backend.<br><br>';
+
+       $o .= '<h4>' . t('Synchronizing this calendar with the iPhone') . '</h4>';
+
+       $o .= "<ul>
+       <li>Go to the settings</li>
+       <li>Mail, contacts, settings</li>
+       <li>Add a new account</li>
+       <li>Other...</li>
+       <li>Calendar -> CalDAV-Account</li>
+       <li><b>Server:</b> " . $a->get_baseurl() . "/dav/ / <b>Username/Password:</b> <em>the same as your friendica-login</em></li>
+       </ul>";
+
+       $o .= '<h4>' . t('Synchronizing your Friendica-Contacts with the iPhone') . '</h4>';
+
+       $o .= "<ul>
+       <li>Go to the settings</li>
+       <li>Mail, contacts, settings</li>
+       <li>Add a new account</li>
+       <li>Other...</li>
+       <li>Contacts -> CardDAV-Account</li>
+       <li><b>Server:</b> " . $a->get_baseurl() . "/dav/ / <b>Username/Password:</b> <em>the same as your friendica-login</em></li>
+       </ul>";
+
+       return $o;
+}
+
diff --git a/dav/friendica/main.php b/dav/friendica/main.php
new file mode 100644 (file)
index 0000000..0fe939e
--- /dev/null
@@ -0,0 +1,369 @@
+<?php
+
+require_once('include/security.php');
+
+function dav_install()
+{
+       register_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook');
+       register_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook');
+       register_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook');
+       register_hook('cron', 'addon/dav/dav.php', 'dav_cron');
+}
+
+
+function dav_uninstall()
+{
+       unregister_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook');
+       unregister_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook');
+       unregister_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook');
+       unregister_hook('cron', 'addon/dav/dav.php', 'dav_cron');
+}
+
+
+function dav_module()
+{
+}
+
+function dav_include_files()
+{
+       require_once (__DIR__ . "/../SabreDAV/lib/Sabre/autoload.php");
+
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Node.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Element.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/DateTimeParser.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/ElementList.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/FreeBusyGenerator.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Parameter.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/ParseException.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Property.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Reader.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/RecurrenceIterator.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/TimeZoneUtil.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Version.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Property/DateTime.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Property/MultiDateTime.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component/VAlarm.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component/VCalendar.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component/VEvent.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component/VJournal.php");
+       require_once (__DIR__ . "/../sabre-vobject/lib/Sabre/VObject/Component/VTodo.php");
+
+       require_once (__DIR__ . "/../common/calendar.fnk.php");
+       require_once (__DIR__ . "/../common/calendar_rendering.fnk.php");
+
+       require_once (__DIR__ . "/../common/dav_caldav_backend_common.inc.php");
+       require_once (__DIR__ . "/../common/dav_caldav_backend_private.inc.php");
+       require_once (__DIR__ . "/../common/dav_caldav_backend_virtual.inc.php");
+       require_once (__DIR__ . "/../common/dav_caldav_root.inc.php");
+       require_once (__DIR__ . "/../common/dav_user_calendars.inc.php");
+       require_once (__DIR__ . "/../common/dav_caldav_calendar_virtual.inc.php");
+       require_once (__DIR__ . "/../common/dav_caldav_calendar_private.inc.php");
+
+       require_once (__DIR__ . "/../common/dav_carddav_root.inc.php");
+       require_once (__DIR__ . "/../common/dav_carddav_backend_common.inc.php");
+       require_once (__DIR__ . "/../common/dav_carddav_backend_virtual.inc.php");
+       require_once (__DIR__ . "/../common/dav_carddav_backend_private.inc.php");
+       require_once (__DIR__ . "/../common/dav_user_addressbooks.inc.php");
+
+       require_once (__DIR__ . "/../common/wdcal_configuration.php");
+       require_once (__DIR__ . "/../common/wdcal_backend.inc.php");
+
+       require_once (__DIR__ . "/dav_friendica_principal.inc.php");
+       require_once (__DIR__ . "/dav_friendica_auth.inc.php");
+       require_once (__DIR__ . "/dav_carddav_backend_virtual_friendica.inc.php");
+       require_once (__DIR__ . "/dav_caldav_backend_virtual_friendica.inc.php");
+       require_once (__DIR__ . "/FriendicaACLPlugin.inc.php");
+
+       require_once (__DIR__ . "/../common/wdcal_edit.inc.php");
+       require_once (__DIR__ . "/calendar.friendica.fnk.php");
+       require_once (__DIR__ . "/layout.fnk.php");
+}
+
+
+/**
+ * @param App $a
+ */
+function dav_init(&$a)
+{
+
+       /*
+        * Recommended settings:
+        * ALTER TABLE `photo` ADD INDEX ( `contact-id` )
+        */
+
+       ini_set("display_errors", 1);
+       error_reporting(E_ALL);
+
+       dav_include_files();
+
+       if (false) {
+               dbg(true);
+               error_reporting(E_ALL);
+               ini_set("display_errors", 1);
+       }
+
+       wdcal_create_std_calendars();
+       wdcal_create_std_addressbooks();
+       wdcal_addRequiredHeaders();
+
+       if ($a->argc >= 2 && $a->argv[1] == "wdcal") {
+
+               if ($a->argc >= 3 && $a->argv[2] == "feed") {
+                       wdcal_print_feed($a->get_baseurl() . "/dav/wdcal/");
+                       killme();
+               }
+               return;
+       }
+       if ($a->argc >= 2 && $a->argv[1] == "getExceptionDates") {
+               echo wdcal_getEditPage_exception_selector();
+               killme();
+       }
+
+       if ($a->argc >= 2 && $a->argv[1] == "settings") {
+               return;
+       }
+
+
+       if (isset($_REQUEST["test"])) {
+               renderAllCalDavEntries();
+       }
+
+
+       $server = dav_create_server();
+
+       $browser = new Sabre_DAV_Browser_Plugin();
+       $server->addPlugin($browser);
+
+       $server->exec();
+
+       killme();
+}
+
+/**
+ * @return string
+ */
+function dav_content()
+{
+       $a = get_app();
+       if (!isset($a->user["uid"]) || $a->user["uid"] == 0) {
+               return login();
+       }
+
+       $x = "";
+       try {
+               if ($a->argv[1] == "settings") {
+                       return wdcal_getSettingsPage($a);
+               } elseif ($a->argv[1] == "wdcal") {
+                       if (isset($a->argv[2]) && strlen($a->argv[2]) > 0) {
+                               if ($a->argv[2] == "new") {
+                                       $o = "";
+                                       if (isset($_REQUEST["save"])) {
+                                               check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit");
+                                               $ret = wdcal_postEditPage("new", "", $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
+                                               if ($ret["ok"]) notice($ret["msg"]);
+                                               else info($ret["msg"]);
+                                               goaway($a->get_baseurl() . "/dav/wdcal/");
+                                       }
+                                       $o .= wdcal_getNewPage();
+                                       return $o;
+                               } else {
+                                       $calendar_id = IntVal($a->argv[2]);
+                                       if (isset($a->argv[3]) && $a->argv[3] == "ics-export") {
+                                               wdcal_print_user_ics($calendar_id);
+                                       } elseif (isset($a->argv[3]) && $a->argv[3] == "ics-import") {
+                                               return wdcal_import_user_ics($calendar_id);
+                                       } elseif (isset($a->argv[3]) && $a->argv[3] > 0) {
+                                               if (isset($a->argv[4]) && $a->argv[4] == "edit") {
+                                                       $o = "";
+                                                       if (isset($_REQUEST["save"])) {
+                                                               check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit");
+                                                               $ret = wdcal_postEditPage($a->argv[3], $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
+                                                               if ($ret["ok"]) notice($ret["msg"]);
+                                                               else info($ret["msg"]);
+                                                               goaway($a->get_baseurl() . "/dav/wdcal/");
+                                                       }
+                                                       $o .= wdcal_getEditPage($calendar_id, $a->argv[3]);
+                                                       return $o;
+                                               } else {
+                                                       return wdcal_getDetailPage($calendar_id, $a->argv[3]);
+                                               }
+                                       } else {
+                                               // @TODO Edit Calendar
+                                       }
+                               }
+                       } else {
+                               $server = dav_create_server(true, true, false);
+                               $cals   = dav_get_current_user_calendars($server, DAV_ACL_READ);
+                               $x      = wdcal_printCalendar($cals, array(), $a->get_baseurl() . "/dav/wdcal/feed/", "week", 0, 200);
+                       }
+               }
+       } catch (DAVVersionMismatchException $e) {
+               $x = t("The current version of this plugin has not been set up correctly. Please contact the system administrator of your installation of friendica to fix this.");
+       }
+       return $x;
+}
+
+
+/**
+ * @param App $a
+ * @param object $b
+ */
+function dav_event_created_hook(&$a, &$b)
+{
+       dav_include_files();
+       // @TODO Updating the cache instead of completely invalidating and rebuilding it
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
+}
+
+/**
+ * @param App $a
+ * @param object $b
+ */
+function dav_event_updated_hook(&$a, &$b)
+{
+       dav_include_files();
+       // @TODO Updating the cache instead of completely invalidating and rebuilding it
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
+}
+
+/**
+ * @param App $a
+ * @param object $b
+ */
+function dav_profile_tabs_hook(&$a, &$b)
+{
+       $b["tabs"][] = array(
+               "label" => t('Calendar'),
+               "url"   => $a->get_baseurl() . "/dav/wdcal/",
+               "sel"   => "",
+               "title" => t('Extended calendar with CalDAV-support'),
+       );
+}
+
+
+/**
+ * @param App $a
+ * @param object $b
+ */
+function dav_cron(&$a, &$b)
+{
+       dav_include_files();
+
+       $r = q("SELECT * FROM %s%snotifications WHERE `notified` = 0 AND `alert_date` <= NOW()", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+       foreach ($r as $not) {
+               q("UPDATE %s%snotifications SET `notified` = 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["id"]);
+               $event    = q("SELECT * FROM %s%sjqcalendar WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["calendarobject_id"]);
+               $calendar = q("SELECT * FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["calendar_id"]);
+               $users    = array();
+               if (count($calendar) != 1 || count($event) == 0) continue;
+               switch ($calendar[0]["namespace"]) {
+                       case CALDAV_NAMESPACE_PRIVATE:
+                               $user = q("SELECT * FROM user WHERE `uid` = %d AND `blocked` = 0", $calendar[0]["namespace_id"]);
+                               if (count($user) != 1) continue;
+                               $users[] = $user[0];
+                               break;
+               }
+               switch ($not["action"]) {
+                       case "email":
+                       case "display": // @TODO implement "Display"
+                               foreach ($users as $user) {
+                                       $find      = array("%to%", "%event%", "%url%");
+                                       $repl      = array($user["username"], $event[0]["Summary"], $a->get_baseurl() . "/dav/wdcal/" . $calendar[0]["id"] . "/" . $not["calendarobject_id"] . "/");
+                                       $text_text = str_replace($find, $repl, "Hi %to%!\n\nThe event \"%event%\" is about to begin:\n%url%");
+                                       $text_html = str_replace($find, $repl, "Hi %to%!<br>\n<br>\nThe event \"%event%\" is about to begin:<br>\n<a href='" . "%url%" . "'>%url%</a>");
+                                       $params    = array(
+                                               'fromName'             => FRIENDICA_PLATFORM,
+                                               'fromEmail'            => t('noreply') . '@' . $a->get_hostname(),
+                                               'replyTo'              => t('noreply') . '@' . $a->get_hostname(),
+                                               'toEmail'              => $user["email"],
+                                               'messageSubject'       => t("Notification: " . $event[0]["Summary"]),
+                                               'htmlVersion'          => $text_html,
+                                               'textVersion'          => $text_text,
+                                               'additionalMailHeader' => "",
+                                       );
+                                       require_once('include/enotify.php');
+                                       enotify::send($params);
+                               }
+                               break;
+               }
+       }
+}
+
+
+/**
+ * @param App $a
+ * @param null|object $o
+ */
+function dav_plugin_admin_post(&$a = null, &$o = null)
+{
+       check_form_security_token_redirectOnErr('/admin/plugins/dav', 'dav_admin_save');
+
+       dav_include_files();
+       require_once(__DIR__ . "/database-init.inc.php");
+
+       if (isset($_REQUEST["install"])) {
+               $errs = dav_create_tables();
+               if (count($errs) == 0) info(t('The database tables have been installed.') . EOL);
+               else notice(t("An error occurred during the installation.") . EOL);
+       }
+       if (isset($_REQUEST["upgrade"])) {
+               $errs = dav_upgrade_tables();
+               if (count($errs) == 0) {
+                       renderAllCalDavEntries();
+                       info(t('The database tables have been updated.') . EOL);
+               } else notice(t("An error occurred during the update.") . EOL);
+       }
+}
+
+/**
+ * @param App $a
+ * @param string $o
+ */
+function dav_plugin_admin(&$a, &$o)
+{
+       dav_include_files();
+       require_once(__DIR__ . "/database-init.inc.php");
+
+       $dbstatus = dav_check_tables();
+
+       $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("dav_admin_save") . '">';
+       $o .= '<i>' . t("No system-wide settings yet.") . '</i><br><br>';
+
+
+       $o .= '<h3>' . t('Database status') . '</h3>';
+       switch ($dbstatus) {
+               case 0:
+                       $o .= t('Installed');
+                       break;
+               case 1:
+               case 2:
+                       $o .= "<strong>" . t('Upgrade needed') . "</strong><br>" . t("Please back up all calendar data (the tables beginning with dav_*) before proceeding. While all calendar events <i>should</i> be converted to the new database structure, it's always safe to have a backup. Below, you can have a look at the database-queries that will be made when pressing the 'update'-button.") . "<br><br><input type='submit' name='upgrade' value='" . t('Upgrade') . "'>";
+                       break;
+               case -1:
+                       $o .= t('Not installed') . "<br><br><input type='submit' name='install' value='" . t('Install') . "'>";
+                       break;
+               case -2:
+               default:
+                       $o .= t('Unknown') . "<br><br>" . t("Something really went wrong. I cannot recover from this state automatically, sorry. Please go to the database backend, back up the data, and delete all tables beginning with 'dav_' manually. Afterwards, this installation routine should be able to reinitialize the tables automatically.");
+                       break;
+       }
+       $o .= "<br><br>";
+
+       $o .= "<h3>" . t("Troubleshooting") . "</h3>";
+       $o .= "<h4>" . t("Manual creation of the database tables:") . "</h4>";
+       $o .= "<a href='#' onClick='\$(\"#sqlstatements\").show(); return false;'>" . t("Show SQL-statements") . "</a><blockquote style='display: none;' id='sqlstatements'><pre>";
+       switch ($dbstatus) {
+               case 1: case 2:
+                       $tables = dav_get_update_statements($dbstatus);
+                       foreach ($tables as $t) $o .= escape_tags($t . ";\n\n");
+                       break;
+               default:
+                       $tables = dav_get_create_statements();
+                       foreach ($tables as $t) $o .= escape_tags($t . ";\n\n");
+                       break;
+       }
+       $o .= "</pre></blockquote>";
+}
diff --git a/dav/friendica/wdcal.css b/dav/friendica/wdcal.css
new file mode 100644 (file)
index 0000000..18c25c5
--- /dev/null
@@ -0,0 +1,43 @@
+
+div.colorPicker-picker { display: inline-block; }
+.colorPicker-palette { font-size: 12px; }
+.animexxcalendar label, .colorPicker-palette label { background: none; border: none; padding: 0; margin: 0; box-shadow: none; display: inline; font-size: 14px; }
+.animexxcalendar input, .colorPicker-palette input { font-size: 14px; }
+
+
+.ui-datepicker { width: 200px; }
+.ui-datepicker * { font-size: 12px; line-height: 12px; }
+.ui-datepicker th { padding: 10px 2px; }
+.ui-datepicker select.ui-datepicker-year { min-width: 0; width: 50px !important; }
+#cal_start_time, #cal_end_time { width: 5em; margin-left: 1em; }
+#cal_start_date, #cal_end_date { width: 6em;}
+
+
+label.block {
+       background: none repeat scroll 0 0 #CCCCCC;
+       border: 1px solid #EEEEEC;
+       box-shadow: 3px 3px 5px 0 #111111;
+       color: #111111;
+       display: inline-block;
+       font-size: small;
+       margin: 0 10px 1em 0;
+       padding: 3px 5px;
+       width: 38%;
+       vertical-align: top;
+}
+
+label.plain {
+       background: none;
+       border: none;
+       box-shadow: none;
+       color: black;
+       display: inline;
+       margin: 0;
+       padding: 0;
+}
+
+.rec_exceptions { display: inline-block; }
+.rec_exceptions_holder { display: inline-block; }
+.rec_exceptions .remover { }
+#rec_until_count { width: 50px; }
+#rec_until_date { width: 100px; }
\ No newline at end of file
diff --git a/dav/iCalcreator/iCalcreator.class.php b/dav/iCalcreator/iCalcreator.class.php
deleted file mode 100755 (executable)
index d3b8476..0000000
+++ /dev/null
@@ -1,10181 +0,0 @@
-<?php
-/*********************************************************************************/
-/**
- * iCalcreator v2.12
- * copyright (c) 2007-2011 Kjell-Inge Gustafsson kigkonsult
- * kigkonsult.se/iCalcreator/index.php
- * ical@kigkonsult.se
- *
- * Description:
- * This file is a PHP implementation of RFC 2445.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-/*********************************************************************************/
-/*********************************************************************************/
-/*         A little setup                                                        */
-/*********************************************************************************/
-            /* your local language code */
-// define( 'ICAL_LANG', 'sv' );
-            // alt. autosetting
-/*
-$langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
-$pos         = strpos( $langstr, ';' );
-if ($pos   !== false) {
-  $langstr   = substr( $langstr, 0, $pos );
-  $pos       = strpos( $langstr, ',' );
-  if ($pos !== false) {
-    $pos     = strpos( $langstr, ',' );
-    $langstr = substr( $langstr, 0, $pos );
-  }
-  define( 'ICAL_LANG', $langstr );
-}
-*/
-/*********************************************************************************/
-/*         only for phpversion 5.1 and later,                                    */
-/*         date management, default timezone setting                             */
-/*         since 2.6.36 - 2010-12-31 */
-if( substr( phpversion(), 0, 3 ) >= '5.1' )
-  // && ( 'UTC' == date_default_timezone_get()))
-  date_default_timezone_set( 'Europe/Stockholm' );
-/*********************************************************************************/
-/*         version, do NOT remove!!                                              */
-define( 'ICALCREATOR_VERSION', 'iCalcreator 2.12' );
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * vcalendar class
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- */
-class vcalendar {
-            //  calendar property variables
-  var $calscale;
-  var $method;
-  var $prodid;
-  var $version;
-  var $xprop;
-            //  container for calendar components
-  var $components;
-            //  component config variables
-  var $allowEmpty;
-  var $unique_id;
-  var $language;
-  var $directory;
-  var $filename;
-  var $url;
-  var $delimiter;
-  var $nl;
-  var $format;
-  var $dtzid;
-            //  component internal variables
-  var $attributeDelimiter;
-  var $valueInit;
-            //  component xCal declaration container
-  var $xcaldecl;
-/**
- * constructor for calendar object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- * @param array $config
- * @return void
- */
-  function vcalendar ( $config = array()) {
-    $this->_makeVersion();
-    $this->calscale   = null;
-    $this->method     = null;
-    $this->_makeUnique_id();
-    $this->prodid     = null;
-    $this->xprop      = array();
-    $this->language   = null;
-    $this->directory  = null;
-    $this->filename   = null;
-    $this->url        = null;
-    $this->dtzid      = null;
-/**
- *   language = <Text identifying a language, as defined in [RFC 1766]>
- */
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-    $this->xcaldecl   = array();
-    $this->components = array();
-  }
-/*********************************************************************************/
-/**
- * Property Name: CALSCALE
- */
-/**
- * creates formatted output for calendar property calscale
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @return string
- */
-  function createCalscale() {
-    if( empty( $this->calscale )) return FALSE;
-    switch( $this->format ) {
-      case 'xcal':
-        return $this->nl.' calscale="'.$this->calscale.'"';
-        break;
-      default:
-        return 'CALSCALE:'.$this->calscale.$this->nl;
-        break;
-    }
-  }
-/**
- * set calendar property calscale
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @param string $value
- * @return void
- */
-  function setCalscale( $value ) {
-    if( empty( $value )) return FALSE;
-    $this->calscale = $value;
-  }
-/*********************************************************************************/
-/**
- * Property Name: METHOD
- */
-/**
- * creates formatted output for calendar property method
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @return string
- */
-  function createMethod() {
-    if( empty( $this->method )) return FALSE;
-    switch( $this->format ) {
-      case 'xcal':
-        return $this->nl.' method="'.$this->method.'"';
-        break;
-      default:
-        return 'METHOD:'.$this->method.$this->nl;
-        break;
-    }
-  }
-/**
- * set calendar property method
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-20-23
- * @param string $value
- * @return bool
- */
-  function setMethod( $value ) {
-    if( empty( $value )) return FALSE;
-    $this->method = $value;
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: PRODID
- *
- *  The identifier is RECOMMENDED to be the identical syntax to the
- * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
- * domain name or a domain literal IP address of the host on which.. .
- */
-/**
- * creates formatted output for calendar property prodid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @return string
- */
-  function createProdid() {
-    if( !isset( $this->prodid ))
-      $this->_makeProdid();
-    switch( $this->format ) {
-      case 'xcal':
-        return $this->nl.' prodid="'.$this->prodid.'"';
-        break;
-      default:
-        return 'PRODID:'.$this->prodid.$this->nl;
-        break;
-    }
-  }
-/**
- * make default value for calendar prodid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.8 - 2009-12-30
- * @return void
- */
-  function _makeProdid() {
-    $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
-  }
-/**
- * Conformance: The property MUST be specified once in an iCalendar object.
- * Description: The vendor of the implementation SHOULD assure that this
- * is a globally unique identifier; using some technique such as an FPI
- * value, as defined in [ISO 9070].
- */
-/**
- * make default unique_id for calendar prodid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 0.3.0 - 2006-08-10
- * @return void
- */
-  function _makeUnique_id() {
-    $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
-  }
-/*********************************************************************************/
-/**
- * Property Name: VERSION
- *
- * Description: A value of "2.0" corresponds to this memo.
- */
-/**
- * creates formatted output for calendar property version
-
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @return string
- */
-  function createVersion() {
-    if( empty( $this->version ))
-      $this->_makeVersion();
-    switch( $this->format ) {
-      case 'xcal':
-        return $this->nl.' version="'.$this->version.'"';
-        break;
-      default:
-        return 'VERSION:'.$this->version.$this->nl;
-        break;
-    }
-  }
-/**
- * set default calendar version
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 0.3.0 - 2006-08-10
- * @return void
- */
-  function _makeVersion() {
-    $this->version = '2.0';
-  }
-/**
- * set calendar version
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @param string $value
- * @return void
- */
-  function setVersion( $value ) {
-    if( empty( $value )) return FALSE;
-    $this->version = $value;
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: x-prop
- */
-/**
- * creates formatted output for calendar property x-prop, iCal format only
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-11-01
- * @return string
- */
-  function createXprop() {
-    if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
-    $output = null;
-    $toolbox = new calendarComponent();
-    $toolbox->setConfig( $this->getConfig());
-    foreach( $this->xprop as $label => $xpropPart ) {
-      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
-        $output  .= $toolbox->_createElement( $label );
-        continue;
-      }
-      $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
-      if( is_array( $xpropPart['value'] )) {
-        foreach( $xpropPart['value'] as $pix => $theXpart )
-          $xpropPart['value'][$pix] = $toolbox->_strrep( $theXpart );
-        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
-      }
-      else
-        $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] );
-      $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
-      if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
-        foreach( $toolbox->xcaldecl as $localxcaldecl )
-          $this->xcaldecl[] = $localxcaldecl;
-      }
-    }
-    return $output;
-  }
-/**
- * set calendar property x-prop
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.9 - 2012-01-16
- * @param string $label
- * @param string $value
- * @param array $params optional
- * @return bool
- */
-  function setXprop( $label, $value, $params=FALSE ) {
-    if( empty( $label ))
-      return FALSE;
-    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
-      return FALSE;
-    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $xprop           = array( 'value' => $value );
-    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
-    if( !is_array( $this->xprop )) $this->xprop = array();
-    $this->xprop[strtoupper( $label )] = $xprop;
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * delete calendar property value
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param mixed $propName, bool FALSE => X-property
- * @param int $propix, optional, if specific property is wanted in case of multiply occurences
- * @return bool, if successfull delete
- */
-  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
-    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
-    if( !$propix )
-      $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
-    $this->propdelix[$propName] = --$propix;
-    $return = FALSE;
-    switch( $propName ) {
-      case 'CALSCALE':
-        if( isset( $this->calscale )) {
-          $this->calscale = null;
-          $return = TRUE;
-        }
-        break;
-      case 'METHOD':
-        if( isset( $this->method )) {
-          $this->method   = null;
-          $return = TRUE;
-        }
-        break;
-      default:
-        $reduced = array();
-        if( $propName != 'X-PROP' ) {
-          if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
-          foreach( $this->xprop as $k => $a ) {
-            if(( $k != $propName ) && !empty( $a ))
-              $reduced[$k] = $a;
-          }
-        }
-        else {
-          if( count( $this->xprop ) <= $propix )  return FALSE;
-          $xpropno = 0;
-          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
-            if( $propix != $xpropno )
-              $reduced[$xpropkey] = $xpropvalue;
-            $xpropno++;
-          }
-        }
-        $this->xprop = $reduced;
-        if( empty( $this->xprop )) {
-          unset( $this->propdelix[$propName] );
-          return FALSE;
-        }
-        return TRUE;
-    }
-    return $return;
-  }
-/**
- * get calendar property value/params
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-04-16
- * @param string $propName, optional
- * @param int $propix, optional, if specific property is wanted in case of multiply occurences
- * @param bool $inclParam=FALSE
- * @return mixed
- */
-  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
-    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
-    if( 'X-PROP' == $propName ) {
-      if( !$propix )
-        $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
-      $this->propix[$propName] = --$propix;
-    }
-    switch( $propName ) {
-      case 'ATTENDEE':
-      case 'CATEGORIES':
-      case 'DTSTART':
-      case 'LOCATION':
-      case 'ORGANIZER':
-      case 'PRIORITY':
-      case 'RESOURCES':
-      case 'STATUS':
-      case 'SUMMARY':
-      case 'RECURRENCE-ID-UID':
-      case 'R-UID':
-      case 'UID':
-        $output = array();
-        foreach ( $this->components as $cix => $component) {
-          if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
-            continue;
-          if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
-            $component->_getProperties( $propName, $output );
-            continue;
-          }
-          elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
-            if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
-              $content = $component->getProperty( 'UID' );
-          }
-          elseif( FALSE === ( $content = $component->getProperty( $propName )))
-            continue;
-          if( FALSE === $content )
-            continue;
-          elseif( is_array( $content )) {
-            if( isset( $content['year'] )) {
-              $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
-              if( !isset( $output[$key] ))
-                $output[$key] = 1;
-              else
-                $output[$key] += 1;
-            }
-            else {
-              foreach( $content as $partValue => $partCount ) {
-                if( !isset( $output[$partValue] ))
-                  $output[$partValue] = $partCount;
-                else
-                  $output[$partValue] += $partCount;
-              }
-            }
-          } // end elseif( is_array( $content )) {
-          elseif( !isset( $output[$content] ))
-            $output[$content] = 1;
-          else
-            $output[$content] += 1;
-        } // end foreach ( $this->components as $cix => $component)
-        if( !empty( $output ))
-          ksort( $output );
-        return $output;
-        break;
-
-      case 'CALSCALE':
-        return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
-        break;
-      case 'METHOD':
-        return ( !empty( $this->method )) ? $this->method : FALSE;
-        break;
-      case 'PRODID':
-        if( empty( $this->prodid ))
-          $this->_makeProdid();
-        return $this->prodid;
-        break;
-      case 'VERSION':
-        return ( !empty( $this->version )) ? $this->version : FALSE;
-        break;
-      default:
-        if( $propName != 'X-PROP' ) {
-          if( !isset( $this->xprop[$propName] )) return FALSE;
-          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
-                                : array( $propName, $this->xprop[$propName]['value'] );
-        }
-        else {
-          if( empty( $this->xprop )) return FALSE;
-          $xpropno = 0;
-          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
-            if( $propix == $xpropno )
-              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
-                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
-            else
-              $xpropno++;
-          }
-          unset( $this->propix[$propName] );
-          return FALSE; // not found ??
-        }
-    }
-    return FALSE;
-  }
-/**
- * general vcalendar property setting
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.2.13 - 2007-11-04
- * @param mixed $args variable number of function arguments,
- *                    first argument is ALWAYS component name,
- *                    second ALWAYS component value!
- * @return bool
- */
-  function setProperty () {
-    $numargs    = func_num_args();
-    if( 1 > $numargs )
-      return FALSE;
-    $arglist    = func_get_args();
-    $arglist[0] = strtoupper( $arglist[0] );
-    switch( $arglist[0] ) {
-      case 'CALSCALE':
-        return $this->setCalscale( $arglist[1] );
-      case 'METHOD':
-        return $this->setMethod( $arglist[1] );
-      case 'VERSION':
-        return $this->setVersion( $arglist[1] );
-      default:
-        if( !isset( $arglist[1] )) $arglist[1] = null;
-        if( !isset( $arglist[2] )) $arglist[2] = null;
-        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
-    }
-    return FALSE;
-  }
-/*********************************************************************************/
-/**
- * get vcalendar config values or * calendar components
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.7 - 2012-01-12
- * @param mixed $config
- * @return value
- */
-  function getConfig( $config = FALSE ) {
-    if( !$config ) {
-      $return = array();
-      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
-      $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
-      $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
-      $return['FILENAME']    = $this->getConfig( 'FILENAME' );
-      $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
-      $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
-      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
-      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
-        $return['LANGUAGE']  = $lang;
-      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
-      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
-      if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
-        $return['URL']       = $url;
-      $return['TZID']        = $this->getConfig( 'TZID' );
-      return $return;
-    }
-    switch( strtoupper( $config )) {
-      case 'ALLOWEMPTY':
-        return $this->allowEmpty;
-        break;
-      case 'COMPSINFO':
-        unset( $this->compix );
-        $info = array();
-        foreach( $this->components as $cix => $component ) {
-          if( empty( $component )) continue;
-          $info[$cix]['ordno'] = $cix + 1;
-          $info[$cix]['type']  = $component->objName;
-          $info[$cix]['uid']   = $component->getProperty( 'uid' );
-          $info[$cix]['props'] = $component->getConfig( 'propinfo' );
-          $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
-        }
-        return $info;
-        break;
-      case 'DELIMITER':
-        return $this->delimiter;
-        break;
-      case 'DIRECTORY':
-        if( empty( $this->directory ) && ( '0' != $this->directory ))
-          $this->directory = '.';
-        return $this->directory;
-        break;
-      case 'DIRFILE':
-        return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
-        break;
-      case 'FILEINFO':
-        return array( $this->getConfig( 'directory' )
-                    , $this->getConfig( 'filename' )
-                    , $this->getConfig( 'filesize' ));
-        break;
-      case 'FILENAME':
-        if( empty( $this->filename ) && ( '0' != $this->filename )) {
-          if( 'xcal' == $this->format )
-            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
-          else
-            $this->filename = date( 'YmdHis' ).'.ics';
-        }
-        return $this->filename;
-        break;
-      case 'FILESIZE':
-        $size    = 0;
-        if( empty( $this->url )) {
-          $dirfile = $this->getConfig( 'dirfile' );
-          if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
-            $size = 0;
-          clearstatcache();
-        }
-        return $size;
-        break;
-      case 'FORMAT':
-        return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
-        break;
-      case 'LANGUAGE':
-         /* get language for calendar component as defined in [RFC 1766] */
-        return $this->language;
-        break;
-      case 'NL':
-      case 'NEWLINECHAR':
-        return $this->nl;
-        break;
-      case 'TZID':
-        return $this->dtzid;
-        break;
-      case 'UNIQUE_ID':
-        return $this->unique_id;
-        break;
-      case 'URL':
-        if( !empty( $this->url ))
-          return $this->url;
-        else
-          return FALSE;
-        break;
-    }
-  }
-/**
- * general vcalendar config setting
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.11 - 2011-01-16
- * @param mixed  $config
- * @param string $value
- * @return void
- */
-  function setConfig( $config, $value = FALSE) {
-    if( is_array( $config )) {
-      $ak = array_keys( $config );
-      foreach( $ak as $k ) {
-        if( 'DIRECTORY' == strtoupper( $k )) {
-          if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
-            return FALSE;
-          unset( $config[$k] );
-        }
-        elseif( 'NEWLINECHAR' == strtoupper( $k )) {
-          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
-            return FALSE;
-          unset( $config[$k] );
-        }
-      }
-      foreach( $config as $cKey => $cValue ) {
-        if( FALSE === $this->setConfig( $cKey, $cValue ))
-          return FALSE;
-      }
-      return TRUE;
-    }
-    $res = FALSE;
-    switch( strtoupper( $config )) {
-      case 'ALLOWEMPTY':
-        $this->allowEmpty = $value;
-        $subcfg  = array( 'ALLOWEMPTY' => $value );
-        $res = TRUE;
-        break;
-      case 'DELIMITER':
-        $this->delimiter = $value;
-        return TRUE;
-        break;
-      case 'DIRECTORY':
-        $value   = trim( $value );
-        $del     = $this->getConfig('delimiter');
-        if( $del == substr( $value, ( 0 - strlen( $del ))))
-          $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
-        if( is_dir( $value )) {
-            /* local directory */
-          clearstatcache();
-          $this->directory = $value;
-          $this->url       = null;
-          return TRUE;
-        }
-        else
-          return FALSE;
-        break;
-      case 'FILENAME':
-        $value   = trim( $value );
-        if( !empty( $this->url )) {
-            /* remote directory+file -> URL */
-          $this->filename = $value;
-          return TRUE;
-        }
-        $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
-        if( file_exists( $dirfile )) {
-            /* local file exists */
-          if( is_readable( $dirfile ) || is_writable( $dirfile )) {
-            clearstatcache();
-            $this->filename = $value;
-            return TRUE;
-          }
-          else
-            return FALSE;
-        }
-        elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
-            /* read- or writable directory */
-          $this->filename = $value;
-          return TRUE;
-        }
-        else
-          return FALSE;
-        break;
-      case 'FORMAT':
-        $value   = trim( strtolower( $value ));
-        if( 'xcal' == $value ) {
-          $this->format             = 'xcal';
-          $this->attributeDelimiter = $this->nl;
-          $this->valueInit          = null;
-        }
-        else {
-          $this->format             = null;
-          $this->attributeDelimiter = ';';
-          $this->valueInit          = ':';
-        }
-        $subcfg  = array( 'FORMAT' => $value );
-        $res = TRUE;
-        break;
-      case 'LANGUAGE':
-         // set language for calendar component as defined in [RFC 1766]
-        $value   = trim( $value );
-        $this->language = $value;
-        $subcfg  = array( 'LANGUAGE' => $value );
-        $res = TRUE;
-        break;
-      case 'NL':
-      case 'NEWLINECHAR':
-        $this->nl = $value;
-        if( 'xcal' == $value ) {
-          $this->attributeDelimiter = $this->nl;
-          $this->valueInit          = null;
-        }
-        else {
-          $this->attributeDelimiter = ';';
-          $this->valueInit          = ':';
-        }
-        $subcfg  = array( 'NL' => $value );
-        $res = TRUE;
-        break;
-      case 'TZID':
-        $this->dtzid = $value;
-        $subcfg  = array( 'TZID' => $value );
-        $res = TRUE;
-        break;
-      case 'UNIQUE_ID':
-        $value   = trim( $value );
-        $this->unique_id = $value;
-        $this->_makeProdid();
-        $subcfg  = array( 'UNIQUE_ID' => $value );
-        $res = TRUE;
-        break;
-      case 'URL':
-            /* remote file - URL */
-        $value     = trim( $value );
-        $value     = str_replace( 'HTTP://',   'http://', $value );
-        $value     = str_replace( 'WEBCAL://', 'http://', $value );
-        $value     = str_replace( 'webcal://', 'http://', $value );
-        $this->url = $value;
-        $this->directory = null;
-        $parts     = pathinfo( $value );
-        return $this->setConfig( 'filename',  $parts['basename'] );
-        break;
-      default:  // any unvalid config key.. .
-        return TRUE;
-    }
-    if( !$res ) return FALSE;
-    if( isset( $subcfg ) && !empty( $this->components )) {
-      foreach( $subcfg as $cfgkey => $cfgvalue ) {
-        foreach( $this->components as $cix => $component ) {
-          $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
-          if( !$res )
-            break 2;
-          $this->components[$cix] = $component->copy(); // PHP4 compliant
-        }
-      }
-    }
-    return $res;
-  }
-/*********************************************************************************/
-/**
- * add calendar component to container
- *
- * alias to setComponent
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 1.x.x - 2007-04-24
- * @param object $component calendar component
- * @return void
- */
-  function addComponent( $component ) {
-    $this->setComponent( $component );
-  }
-/**
- * delete calendar component from container
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param mixed $arg1 ordno / component type / component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return void
- */
-  function deleteComponent( $arg1, $arg2=FALSE  ) {
-    $argType = $index = null;
-    if ( ctype_digit( (string) $arg1 )) {
-      $argType = 'INDEX';
-      $index   = (int) $arg1 - 1;
-    }
-    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
-      $argType = strtolower( $arg1 );
-      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
-    }
-    $cix1dC = 0;
-    foreach ( $this->components as $cix => $component) {
-      if( empty( $component )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
-        unset( $this->components[$cix] );
-        return TRUE;
-      }
-      elseif( $argType == $component->objName ) {
-        if( $index == $cix1dC ) {
-          unset( $this->components[$cix] );
-          return TRUE;
-        }
-        $cix1dC++;
-      }
-      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
-        unset( $this->components[$cix] );
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-/**
- * get calendar component from container
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.1 - 2011-04-16
- * @param mixed $arg1 optional, ordno/component type/ component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return object
- */
-  function getComponent( $arg1=FALSE, $arg2=FALSE ) {
-    $index = $argType = null;
-    if ( !$arg1 ) { // first or next in component chain
-      $argType = 'INDEX';
-      $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
-    }
-    elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
-      $argType = 'INDEX';
-      $index   = (int) $arg1;
-      unset( $this->compix );
-    }
-    elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
-      $arg2  = implode( '-', array_keys( $arg1 ));
-      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
-      $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
-      $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' );
-    }
-    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
-      unset( $this->compix['INDEX'] );
-      $argType = strtolower( $arg1 );
-      if( !$arg2 )
-        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
-      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
-        $index = (int) $arg2;
-    }
-    elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
-      if( !$arg2 )
-        $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
-      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
-        $index = (int) $arg2;
-    }
-    if( isset( $index ))
-      $index  -= 1;
-    $ckeys = array_keys( $this->components );
-    if( !empty( $index) && ( $index > end(  $ckeys )))
-      return FALSE;
-    $cix1gC = 0;
-    foreach ( $this->components as $cix => $component) {
-      if( empty( $component )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix ))
-        return $component->copy();
-      elseif( $argType == $component->objName ) {
-        if( $index == $cix1gC )
-          return $component->copy();
-        $cix1gC++;
-      }
-      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
-        $hit = FALSE;
-        foreach( $arg1 as $pName => $pValue ) {
-          $pName = strtoupper( $pName );
-          if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
-            continue;
-          if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur
-            $propValues = array();
-            $component->_getProperties( $pName, $propValues );
-            $propValues = array_keys( $propValues );
-            $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
-            continue;
-          } // end   if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur
-          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency
-            $hit = FALSE; // missing property
-            continue;
-          }
-          if( 'SUMMARY' == $pName ) { // exists within (any case)
-            $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE;
-            continue;
-          }
-          if( in_array( strtoupper( $pName ), $dateProps )) {
-            $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
-            if( 8 < strlen( $pValue )) {
-              if( isset( $value['hour'] )) {
-                if( 'T' == substr( $pValue, 8, 1 ))
-                  $pValue = str_replace( 'T', '', $pValue );
-                $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
-              }
-              else
-                $pValue = substr( $pValue, 0, 8 );
-            }
-            $hit = ( $pValue == $valuedate ) ? TRUE : FALSE;
-            continue;
-          }
-          elseif( !is_array( $value ))
-            $value = array( $value );
-          foreach( $value as $part ) {
-            $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
-            foreach( $part as $subPart ) {
-              if( $pValue == $subPart ) {
-                $hit = TRUE;
-                continue 2;
-              }
-            }
-          }
-          $hit = FALSE; // no hit in property
-        } // end  foreach( $arg1 as $pName => $pValue )
-        if( $hit ) {
-          if( $index == $cix1gC )
-            return $component->copy();
-          $cix1gC++;
-        }
-      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
-      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
-        if( $index == $cix1gC )
-          return $component->copy();
-        $cix1gC++;
-      }
-    } // end foreach ( $this->components.. .
-            /* not found.. . */
-    unset( $this->compix );
-    return FALSE;
-  }
-/**
- * create new calendar component, already included within calendar
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.33 - 2011-01-03
- * @param string $compType component type
- * @return object (reference)
- */
-  function & newComponent( $compType ) {
-    $config = $this->getConfig();
-    $keys   = array_keys( $this->components );
-    $ix     = end( $keys) + 1;
-    switch( strtoupper( $compType )) {
-      case 'EVENT':
-      case 'VEVENT':
-        $this->components[$ix] = new vevent( $config );
-        break;
-      case 'TODO':
-      case 'VTODO':
-        $this->components[$ix] = new vtodo( $config );
-        break;
-      case 'JOURNAL':
-      case 'VJOURNAL':
-        $this->components[$ix] = new vjournal( $config );
-        break;
-      case 'FREEBUSY':
-      case 'VFREEBUSY':
-        $this->components[$ix] = new vfreebusy( $config );
-        break;
-      case 'TIMEZONE':
-      case 'VTIMEZONE':
-        array_unshift( $this->components, new vtimezone( $config ));
-        $ix = 0;
-        break;
-      default:
-        return FALSE;
-    }
-    return $this->components[$ix];
-  }
-/**
- * select components from calendar on date or selectOption basis
- *
- * Ensure DTSTART is set for every component.
- * No date controls occurs.
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.22 - 2012-02-13
- * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
- * @param int   $startM optional, start Month, default current Month
- * @param int   $startD optional, start Day,   default current Day
- * @param int   $endY   optional, end   Year,  default $startY
- * @param int   $endY   optional, end   Month, default $startM
- * @param int   $endY   optional, end   Day,   default $startD
- * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
- * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
- *                                TRUE            => output : array[] (ignores split)
- * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
- *                                FALSE          - only component(-s) that starts within period
- * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
- *                                                 period (implies flat=FALSE)
- *                                FALSE          - one occurance of component only in output array
- * @return array or FALSE
- */
-  function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
-            /* check  if empty calendar */
-    if( 0 >= count( $this->components )) return FALSE;
-    if( is_array( $startY ))
-      return $this->selectComponents2( $startY );
-            /* check default dates */
-    if( !$startY ) $startY = date( 'Y' );
-    if( !$startM ) $startM = date( 'm' );
-    if( !$startD ) $startD = date( 'd' );
-    $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
-    if( !$endY )   $endY   = $startY;
-    if( !$endM )   $endM   = $startM;
-    if( !$endD )   $endD   = $startD;
-    $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
-//echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
-            /* check component types */
-    $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
-    if( is_array( $cType )) {
-      foreach( $cType as $cix => $theType ) {
-        $cType[$cix] = $theType = strtolower( $theType );
-        if( !in_array( $theType, $validTypes ))
-          $cType[$cix] = 'vevent';
-      }
-      $cType = array_unique( $cType );
-    }
-    elseif( !empty( $cType )) {
-      $cType = strtolower( $cType );
-      if( !in_array( $cType, $validTypes ))
-        $cType = array( 'vevent' );
-      else
-        $cType = array( $cType );
-    }
-    else
-      $cType = $validTypes;
-    if( 0 >= count( $cType ))
-      $cType = $validTypes;
-    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
-      $split = FALSE;
-    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
-      $split = FALSE;
-            /* iterate components */
-    $result = array();
-    foreach ( $this->components as $cix => $component ) {
-      if( empty( $component )) continue;
-      unset( $start );
-            /* deselect unvalid type components */
-      if( !in_array( $component->objName, $cType ))
-        continue;
-      $start = $component->getProperty( 'dtstart' );
-            /* select due when dtstart is missing */
-      if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
-        continue;
-      if( empty( $start ))
-        continue;
-      $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
-      unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up
-      $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
-      $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
-            /* get end date from dtend/due/duration properties */
-      $end = $component->getProperty( 'dtend' );
-      if( !empty( $end )) {
-        $dtendExist = TRUE;
-        $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
-      }
-      if( empty( $end ) && ( $component->objName == 'vtodo' )) {
-        $end = $component->getProperty( 'due' );
-        if( !empty( $end )) {
-          $dueExist = TRUE;
-          $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
-        }
-      }
-      if( !empty( $end ) && !isset( $end['hour'] )) {
-          /* a DTEND without time part regards an event that ends the day before,
-             for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
-        $endAllDayEvent = TRUE;
-        $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
-        $end['year']  = date( 'Y', $endWdate );
-        $end['month'] = date( 'm', $endWdate );
-        $end['day']   = date( 'd', $endWdate );
-        $end['hour']  = 23;
-        $end['min']   = $end['sec'] = 59;
-      }
-      if( empty( $end )) {
-        $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
-        if( !empty( $end ))
-          $durationExist = TRUE;
-          $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
-// if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
-      }
-      if( empty( $end )) { // assume one day duration if missing end date
-        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
-      }
-// if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
-      $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
-      if( $endWdate < $startWdate ) { // MUST be after start date!!
-        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
-        $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
-      }
-      $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
-            /* make a list of optional exclude dates for component occurence from exrule and exdate */
-      $exdatelist = array();
-      $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
-      $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
-      while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
-        iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
-      while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
-        foreach( $exdate as $theExdate ) {
-          $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
-          $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
-          if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
-            $exdatelist[$exWdate] = TRUE;
-        } // end - foreach( $exdate as $theExdate )
-      }  // end - check exdate
-      $compUID    = $component->getProperty( 'UID' );
-            /* check recurrence-id (with sequence), remove hit with reccurr-id date */
-      if(( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) &&
-         ( FALSE !== ( $sequence = $component->getProperty( 'sequence' )))   ) {
-        $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
-        $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
-        $endD     = $recurrid + $rdurWsecs;
-        do {
-          if( date( 'Ymd', $startWdate ) != date( 'Ymd', $recurrid ))
-            $exdatelist[$recurrid] = TRUE; // exclude all other days than startdate
-          $wd = getdate( $recurrid );
-          if( isset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ))
-              unset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ); // remove from output, dtstart etc added below
-          if( $split && ( $recurrid <= $endD ))
-            $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ) + 1, date( 'Y', $recurrid )); // step one day
-          else
-            break;
-        } while( TRUE );
-      } // end recurrence-id test
-            /* select only components with.. . */
-      if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
-         (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
-            /* add the selected component (WITHIN valid dates) to output array */
-        if( $flat ) { // any=true/false, ignores split
-          if( !$recurrid )
-            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
-        }
-        elseif( $split ) { // split the original component
-          if( $endWdate > $endDate )
-            $endWdate = $endDate;     // use period end date
-          $rstart   = $startWdate;
-          if( $rstart < $startDate )
-            $rstart = $startDate; // use period start date
-          $startYMD = date( 'Ymd', $rstart );
-          $endYMD   = date( 'Ymd', $endWdate );
-          $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-          while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate
-            $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-            if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
-              $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
-              continue;
-            }
-            if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
-              $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
-            else
-              $datestring = date( $startDateFormat, $rstart );
-            if( isset( $start['tz'] ))
-              $datestring .= ' '.$start['tz'];
-// echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component->setProperty( 'X-CNT', $tcnt ); // test ###
-            $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
-            if( $dtendExist || $dueExist || $durationExist ) {
-              if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
-                $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
-              else
-                $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-              if( $endAllDayEvent && $dtendExist )
-                $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
-              $datestring = date( $endDateFormat, $tend );
-              if( isset( $end['tz'] ))
-                $datestring .= ' '.$end['tz'];
-              $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
-              $component->setProperty( $propName, $datestring );
-            } // end if( $dtendExist || $dueExist || $durationExist )
-            $wd = getdate( $rstart );
-            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
-            $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
-          } // end while( $rstart <= $endWdate )
-        } // end if( $split )   -  else use component date
-        elseif( $recurrid && !$flat && !$any && !$split )
-          $continue = TRUE;
-        else { // !$flat && !$split, i.e. no flat array and DTSTART within period
-          $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
-          if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
-            $wd = getdate( $startWdate );
-            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
-          }
-        }
-      } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
-
-            /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
-      if( TRUE === $any ) {
-            /* make a list of optional repeating dates for component occurence, rrule, rdate */
-        $recurlist = array();
-        while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
-          iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
-        foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
-          $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
-        while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
-          foreach( $rdate as $theRdate ) {
-            if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
-                   array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
-              $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
-              if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
-                continue;
-              if( isset( $theRdate[1]['year'] )) // date-date period
-                $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
-              else {                             // date-duration period
-                $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
-                $rend = iCalUtilityFunctions::_date2timestamp( $rend );
-              }
-              while( $rstart < $rend ) {
-                $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
-                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
-              }
-            } // PERIOD end
-            else { // single date
-              $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
-              if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
-                $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
-            }
-          }
-        }  // end - check rdate
-        if( 0 < count( $recurlist )) {
-          ksort( $recurlist );
-          $xRecurrence = 1;
-          $component2  = $component->copy();
-          $compUID     = $component2->getProperty( 'UID' );
-          foreach( $recurlist as $recurkey => $durvalue ) {
-// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
-            if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
-              continue;
-            $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
-            if( isset( $exdatelist[$checkDate] )) // check excluded dates
-              continue;
-            if( $startWdate >= $recurkey ) // exclude component start date
-              continue;
-            $rstart = $recurkey;
-            $rend   = $recurkey + $durvalue;
-           /* add repeating components within valid dates to output array, only start date set */
-            if( $flat ) {
-              if( !isset( $result[$compUID] )) // only one comp
-                $result[$compUID] = $component2->copy(); // copy to output
-            }
-           /* add repeating components within valid dates to output array, one each day */
-            elseif( $split ) {
-              if( $rend > $endDate )
-                $rend = $endDate;
-              $startYMD = date( 'Ymd', $rstart );
-              $endYMD   = date( 'Ymd', $rend );
-// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
-              while( $rstart <= $rend ) { // iterate.. .
-                $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-                if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
-                  break;
-// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
-                if( $rstart >= $startDate ) {    // date after dtstart
-                  if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
-                    $datestring = date( $startDateFormat, $checkDate );
-                  else
-                    $datestring = date( $startDateFormat, $rstart );
-                  if( isset( $start['tz'] ))
-                    $datestring .= ' '.$start['tz'];
-//echo "X-CURRENT-DTSTART 1 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
-                  $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
-                  if( $dtendExist || $dueExist || $durationExist ) {
-                    if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
-                      $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
-                    else
-                      $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-                    if( $endAllDayEvent && $dtendExist )
-                      $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
-                    $datestring = date( $endDateFormat, $tend );
-                    if( isset( $end['tz'] ))
-                      $datestring .= ' '.$end['tz'];
-                    $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
-                    $component2->setProperty( $propName, $datestring );
-                  } // end if( $dtendExist || $dueExist || $durationExist )
-                  $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
-                  $wd = getdate( $rstart );
-                  $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
-                } // end if( $checkDate > $startYMD ) {    // date after dtstart
-                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
-              } // end while( $rstart <= $rend )
-              $xRecurrence += 1;
-            } // end elseif( $split )
-            elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
-              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
-              if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
-                $xRecurrence += 1;
-                $datestring = date( $startDateFormat, $rstart );
-                if( isset( $start['tz'] ))
-                  $datestring .= ' '.$start['tz'];
-//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
-                $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
-                if( $dtendExist || $dueExist || $durationExist ) {
-                  $tend = $rstart + $rdurWsecs;
-                  if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
-                    $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
-                  else
-                    $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
-                  if( $endAllDayEvent && $dtendExist )
-                    $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
-                  $datestring = date( $endDateFormat, $tend );
-                  if( isset( $end['tz'] ))
-                    $datestring .= ' '.$end['tz'];
-                  $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
-                  $component2->setProperty( $propName, $datestring );
-                } // end if( $dtendExist || $dueExist || $durationExist )
-                $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
-                $wd = getdate( $rstart );
-                $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
-              } // end if( !isset( $exdatelist[$checkDate] ))
-            } // end elseif( $rstart >= $startDate )
-          } // end foreach( $recurlist as $recurkey => $durvalue )
-        } // end if( 0 < count( $recurlist ))
-            /* deselect components with startdate/enddate not within period */
-        if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
-          continue;
-      } // end if( TRUE === $any )
-    } // end foreach ( $this->components as $cix => $component )
-    if( 0 >= count( $result )) return FALSE;
-    elseif( !$flat ) {
-      foreach( $result as $y => $yeararr ) {
-        foreach( $yeararr as $m => $montharr ) {
-          foreach( $montharr as $d => $dayarr ) {
-            if( empty( $result[$y][$m][$d] ))
-                unset( $result[$y][$m][$d] );
-            else
-              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
-          }
-          if( empty( $result[$y][$m] ))
-              unset( $result[$y][$m] );
-          else
-            ksort( $result[$y][$m] );
-        }
-        if( empty( $result[$y] ))
-            unset( $result[$y] );
-        else
-          ksort( $result[$y] );
-      }
-      if( empty( $result ))
-          unset( $result );
-      else
-        ksort( $result );
-    } // end elseif( !$flat )
-    if( 0 >= count( $result ))
-      return FALSE;
-    return $result;
-  }
-/**
- * select components from calendar on based on Categories, Location, Resources and/or Summary
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-05-03
- * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
- * @return array
- */
-  function selectComponents2( $selectOptions ) {
-    $output = array();
-    $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' );
-    foreach( $this->components as $cix => $component3 ) {
-      if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
-        continue;
-      $uid = $component3->getProperty( 'UID' );
-      foreach( $selectOptions as $propName => $pvalue ) {
-        $propName = strtoupper( $propName );
-        if( !in_array( $propName, $allowedProperties ))
-          continue;
-        if( !is_array( $pvalue ))
-          $pvalue = array( $pvalue );
-        if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
-          $output[] = $component3->copy();
-          continue;
-        }
-        elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
-          $propValues = array();
-          $component3->_getProperties( $propName, $propValues );
-          $propValues = array_keys( $propValues );
-          foreach( $pvalue as $theValue ) {
-            if( in_array( $theValue, $propValues ) && !isset( $output[$uid] )) {
-              $output[$uid] = $component3->copy();
-              break;
-            }
-          }
-          continue;
-        } // end   elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName ))
-        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence
-          continue;
-        if( is_array( $d )) {
-          foreach( $d as $part ) {
-            if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
-              $output[$uid] = $component3->copy();
-          }
-        }
-        elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
-          foreach( $pvalue as $pval ) {
-            if( FALSE !== stripos( $d, $pval )) {
-              $output[$uid] = $component3->copy();
-              break;
-            }
-          }
-        }
-        elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
-          $output[$uid] = $component3->copy();
-      } // end foreach( $selectOptions as $propName => $pvalue ) {
-    } // end foreach( $this->components as $cix => $component3 ) {
-    if( !empty( $output )) {
-      ksort( $output );
-      $output = array_values( $output );
-    }
-    return $output;
-  }
-/**
- * add calendar component to container
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param object $component calendar component
- * @param mixed $arg1 optional, ordno/component type/ component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return void
- */
-  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
-    $component->setConfig( $this->getConfig(), FALSE, TRUE );
-    if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
-            /* make sure dtstamp and uid is set */
-      $dummy1 = $component->getProperty( 'dtstamp' );
-      $dummy2 = $component->getProperty( 'uid' );
-    }
-    if( !$arg1 ) { // plain insert, last in chain
-      $this->components[] = $component->copy();
-      return TRUE;
-    }
-    $argType = $index = null;
-    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
-      $argType = 'INDEX';
-      $index   = (int) $arg1 - 1;
-    }
-    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
-      $argType = strtolower( $arg1 );
-      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
-    }
-    // else if arg1 is set, arg1 must be an UID
-    $cix1sC = 0;
-    foreach ( $this->components as $cix => $component2) {
-      if( empty( $component2 )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
-        $this->components[$cix] = $component->copy();
-        return TRUE;
-      }
-      elseif( $argType == $component2->objName ) { // component Type index insert/replace
-        if( $index == $cix1sC ) {
-          $this->components[$cix] = $component->copy();
-          return TRUE;
-        }
-        $cix1sC++;
-      }
-      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
-        $this->components[$cix] = $component->copy();
-        return TRUE;
-      }
-    }
-            /* arg1=index and not found.. . insert at index .. .*/
-    if( 'INDEX' == $argType ) {
-      $this->components[$index] = $component->copy();
-      ksort( $this->components, SORT_NUMERIC );
-    }
-    else    /* not found.. . insert last in chain anyway .. .*/
-      $this->components[] = $component->copy();
-    return TRUE;
-  }
-/**
- * sort iCal compoments
- *
- * ascending sort on properties (if exist) x-current-dtstart, dtstart,
- * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid
- * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.4 - 2011-06-02
- * @param string $sortArg, optional
- * @return void
- *
- */
-  function sort( $sortArg=FALSE ) {
-    if( is_array( $this->components )) {
-      if( $sortArg ) {
-        $sortArg = strtoupper( $sortArg );
-        if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' )))
-          $sortArg = FALSE;
-      }
-            /* set sort parameters for each component */
-      foreach( $this->components as $cix => & $c ) {
-        $c->srtk = array( '0', '0', '0', '0' );
-        if( 'vtimezone' == $c->objName ) {
-          if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
-            $c->srtk[0] = 0;
-          continue;
-        }
-        elseif( $sortArg ) {
-          if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES'  == $sortArg )) {
-            $propValues = array();
-            $c->_getProperties( $sortArg, $propValues );
-            $c->srtk[0] = reset( array_keys( $propValues ));
-          }
-          elseif( FALSE !== ( $d = $c->getProperty( $sortArg )))
-            $c->srtk[0] = $d;
-          continue;
-        }
-        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
-          $c->srtk[0] = iCalUtilityFunctions::_date_time_string( $d[1] );
-          unset( $c->srtk[0]['unparsedtext'] );
-        }
-        elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
-          $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
-        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
-          $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
-          unset( $c->srtk[1]['unparsedtext'] );
-        }
-        elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
-          if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
-            $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] );
-            unset( $c->srtk[1]['unparsedtext'] );
-          }
-          elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
-            if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
-              $c->srtk[1] = 0;
-        }
-        if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
-          if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
-            $c->srtk[2] = 0;
-        if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
-          $c->srtk[3] = 0;
-      } // end foreach( $this->components as & $c
-            /* sort */
-      usort( $this->components, array( $this, '_cmpfcn' ));
-    }
-  }
-  function _cmpfcn( $a, $b ) {
-    if(        empty( $a ))                       return -1;
-    if(        empty( $b ))                       return  1;
-    if( 'vtimezone' == $a->objName ) {
-      if( 'vtimezone' != $b->objName )            return -1;
-      elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
-      else                                        return  1;
-    }
-    elseif( 'vtimezone' == $b->objName )          return  1;
-    $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
-    for( $k = 0; $k < 4 ; $k++ ) {
-      if(        empty( $a->srtk[$k] ))           return -1;
-      elseif(    empty( $b->srtk[$k] ))           return  1;
-      if( is_array( $a->srtk[$k] )) {
-        if( is_array( $b->srtk[$k] )) {
-          foreach( $sortkeys as $key ) {
-            if    (  empty( $a->srtk[$k][$key] )) return -1;
-            elseif(  empty( $b->srtk[$k][$key] )) return  1;
-            if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
-                                                  continue;
-            if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
-                                                  return -1;
-            elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
-                                                  return  1;
-          }
-        }
-        else                                      return -1;
-      }
-      elseif( is_array( $b->srtk[$k] ))           return  1;
-      elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
-      elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
-    }
-    return 0;
-  }
-/**
- * parse iCal text/file into vcalendar, components, properties and parameters
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.10 - 2012-01-31
- * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
- * @return bool FALSE if error occurs during parsing
- *
- */
-  function parse( $unparsedtext=FALSE ) {
-    $nl = $this->getConfig( 'nl' );
-    if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
-            /* directory+filename is set previously via setConfig directory+filename or url */
-      if( FALSE === ( $filename = $this->getConfig( 'url' )))
-        $filename = $this->getConfig( 'dirfile' );
-            /* READ FILE */
-      if( FALSE === ( $rows = file_get_contents( $filename )))
-        return FALSE;                 /* err 1 */
-    }
-    elseif( is_array( $unparsedtext ))
-      $rows =  implode( '\n'.$nl, $unparsedtext );
-    else
-      $rows = & $unparsedtext;
-            /* identify BEGIN:VCALENDAR, MUST be first row */
-    if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 )))
-      return FALSE;                   /* err 8 */
-            /* fix line folding */
-    $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
-    $EOLmark = FALSE;
-    foreach( $eolchars as $eolchar ) {
-      if( !$EOLmark  && ( FALSE !== strpos( $rows, $eolchar ))) {
-        $rows = str_replace( $eolchar." ",  '',  $rows );
-        $rows = str_replace( $eolchar."\t", '',  $rows );
-        if( $eolchar != $nl )
-          $rows = str_replace( $eolchar,    $nl, $rows );
-        $EOLmark = TRUE;
-      }
-    }
-    $rows = explode( $nl, $rows );
-            /* skip trailing empty lines */
-    $lix = count( $rows ) - 1;
-    while( empty( $rows[$lix] ) && ( 0 < $lix ))
-      $lix -= 1;
-            /* identify ending END:VCALENDAR row, MUST be last row */
-    if( 'END:VCALENDAR'   != strtoupper( substr( $rows[$lix], 0, 13 )))
-      return FALSE;                   /* err 9 */
-    if( 3 > count( $rows ))
-      return FALSE;                   /* err 10 */
-    $comp    = & $this;
-    $calsync = 0;
-            /* identify components and update unparsed data within component */
-    $config = $this->getConfig();
-    foreach( $rows as $line ) {
-      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
-        $calsync++;
-        continue;
-      }
-      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
-        $calsync--;
-        break;
-      }
-      elseif( 1 != $calsync )
-        return FALSE;                 /* err 20 */
-      elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) {
-        $this->components[] = $comp->copy();
-        continue;
-      }
-      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 )))
-        $comp = new vevent( $config );
-      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 )))
-        $comp = new vfreebusy( $config );
-      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 )))
-        $comp = new vjournal( $config );
-      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 )))
-        $comp = new vtodo( $config );
-      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 )))
-        $comp = new vtimezone( $config );
-      else { /* update component with unparsed data */
-        $comp->unparsed[] = $line;
-      }
-    } // end foreach( $rows as $line )
-    unset( $config );
-            /* parse data for calendar (this) object */
-    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
-            /* concatenate property values spread over several lines */
-      $lastix    = -1;
-      $propnames = array( 'calscale','method','prodid','version','x-' );
-      $proprows  = array();
-      foreach( $this->unparsed as $line ) {
-        $newProp = FALSE;
-        foreach ( $propnames as $propname ) {
-          if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
-            $newProp = TRUE;
-            break;
-          }
-        }
-        if( $newProp ) {
-          $newProp = FALSE;
-          $lastix++;
-          $proprows[$lastix]  = $line;
-        }
-        else
-          $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
-      }
-      $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
-      $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
-      $paramProto4 = array( 'crid:', 'news:', 'pres:' );
-      foreach( $proprows as $line ) {
-        $line = str_replace( '!"#¤%&/()=? ', '', $line );
-        $line = str_replace( '!"#¤%&/()=?', '', $line );
-        if( '\n' == substr( $line, -2 ))
-          $line = substr( $line, 0, strlen( $line ) - 2 );
-            /* get property name */
-        $cix = $propname = null;
-        for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
-          if( in_array( $line[$cix], array( ':', ';' )))
-            break;
-          else
-            $propname .= $line[$cix];
-        }
-            /* ignore version/prodid properties */
-        if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' )))
-          continue;
-        $line = substr( $line, $cix);
-            /* separate attributes from value */
-        $attr         = array();
-        $attrix       = -1;
-        $strlen       = strlen( $line );
-        $WithinQuotes = FALSE;
-        for( $cix=0; $cix < $strlen; $cix++ ) {
-          if(                       ( ':'  == $line[$cix] )                         &&
-                                    ( substr( $line,$cix,     3 )  != '://' )       &&
-             ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
-             ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
-             ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
-                        ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
-               !$WithinQuotes ) {
-            $attrEnd = TRUE;
-            if(( $cix < ( $strlen - 4 )) &&
-                 ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
-              for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
-                if( '://' == substr( $line, $c2ix - 2, 3 )) {
-                  $attrEnd = FALSE;
-                  break; // an URI with a portnr!!
-                }
-              }
-            }
-            if( $attrEnd) {
-              $line = substr( $line, ( $cix + 1 ));
-              break;
-            }
-          }
-          if( '"' == $line[$cix] )
-            $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
-          if( ';' == $line[$cix] )
-            $attr[++$attrix] = null;
-          else
-            $attr[$attrix] .= $line[$cix];
-        }
-            /* make attributes in array format */
-        $propattr = array();
-        foreach( $attr as $attribute ) {
-          $attrsplit = explode( '=', $attribute, 2 );
-          if( 1 < count( $attrsplit ))
-            $propattr[$attrsplit[0]] = $attrsplit[1];
-          else
-            $propattr[] = $attribute;
-        }
-            /* update Property */
-        if( FALSE !== strpos( $line, ',' )) {
-          $llen     = strlen( $line );
-          $content  = array( 0 => '' );
-          $cix      = 0;
-          for( $lix = 0; $lix < $llen; $lix++ ) {
-            if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
-              $cix++;
-              $content[$cix] = '';
-            }
-            else
-              $content[$cix] .= $line[$lix];
-          }
-          if( 1 < count( $content )) {
-            foreach( $content as $cix => $contentPart )
-              $content[$cix] = calendarComponent::_strunrep( $contentPart );
-            $this->setProperty( $propname, $content, $propattr );
-            continue;
-          }
-          else
-            $line = reset( $content );
-          $line = calendarComponent::_strunrep( $line );
-        }
-        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
-      } // end - foreach( $this->unparsed.. .
-    } // end - if( is_array( $this->unparsed.. .
-    unset( $unparsedtext, $rows, $this->unparsed, $proprows );
-            /* parse Components */
-    if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
-      $ckeys = array_keys( $this->components );
-      foreach( $ckeys as $ckey ) {
-        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
-          $this->components[$ckey]->parse();
-        }
-      }
-    }
-    else
-      return FALSE;                   /* err 91 or something.. . */
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * creates formatted output for calendar object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @return string
- */
-  function createCalendar() {
-    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
-    switch( $this->format ) {
-      case 'xcal':
-        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
-                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
-                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
-        $calendarStart = '>'.$this->nl.'<vcalendar';
-        break;
-      default:
-        $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
-        break;
-    }
-    $calendarStart .= $this->createVersion();
-    $calendarStart .= $this->createProdid();
-    $calendarStart .= $this->createCalscale();
-    $calendarStart .= $this->createMethod();
-    if( 'xcal' == $this->format )
-      $calendarStart .= '>'.$this->nl;
-    $calendar .= $this->createXprop();
-
-    foreach( $this->components as $component ) {
-      if( empty( $component )) continue;
-      $component->setConfig( $this->getConfig(), FALSE, TRUE );
-      $calendar .= $component->createComponent( $this->xcaldecl );
-    }
-    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
-      $calendarInit .= ' [';
-      $old_xcaldecl  = array();
-      foreach( $this->xcaldecl as $declix => $declPart ) {
-        if(( 0 < count( $old_xcaldecl))    &&
-             isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
-             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
-           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
-           ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
-          continue; // no duplicate uri and ext. references
-        if(( 0 < count( $old_xcaldecl))    &&
-            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
-             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
-           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
-          continue; // no duplicate element declarations
-        $calendarxCaldecl .= $this->nl.'<!';
-        foreach( $declPart as $declKey => $declValue ) {
-          switch( $declKey ) {                    // index
-            case 'xmldecl':                       // no 1
-              $calendarxCaldecl .= $declValue.' ';
-              break;
-            case 'uri':                           // no 2
-              $calendarxCaldecl .= $declValue.' ';
-              $old_xcaldecl['uri'][] = $declValue;
-              break;
-            case 'ref':                           // no 3
-              $calendarxCaldecl .= $declValue.' ';
-              $old_xcaldecl['ref'][] = $declValue;
-              break;
-            case 'external':                      // no 4
-              $calendarxCaldecl .= '"'.$declValue.'" ';
-              $old_xcaldecl['external'][] = $declValue;
-              break;
-            case 'type':                          // no 5
-              $calendarxCaldecl .= $declValue.' ';
-              break;
-            case 'type2':                         // no 6
-              $calendarxCaldecl .= $declValue;
-              break;
-          }
-        }
-        $calendarxCaldecl .= '>';
-      }
-      $calendarxCaldecl .= $this->nl.']';
-    }
-    switch( $this->format ) {
-      case 'xcal':
-        $calendar .= '</vcalendar>'.$this->nl;
-        break;
-      default:
-        $calendar .= 'END:VCALENDAR'.$this->nl;
-        break;
-    }
-    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
-  }
-/**
- * a HTTP redirect header is sent with created, updated and/or parsed calendar
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.24 - 2011-12-23
- * @param bool $utf8Encode
- * @param bool $gzip
- * @return redirect
- */
-  function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
-    $filename = $this->getConfig( 'filename' );
-    $output   = $this->createCalendar();
-    if( $utf8Encode )
-      $output = utf8_encode( $output );
-    if( $gzip ) {
-      $output = gzencode( $output, 9 );
-      header( 'Content-Encoding: gzip' );
-      header( 'Vary: *' );
-      header( 'Content-Length: '.strlen( $output ));
-    }
-    if( 'xcal' == $this->format )
-      header( 'Content-Type: application/calendar+xml; charset=utf-8' );
-    else
-      header( 'Content-Type: text/calendar; charset=utf-8' );
-    header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
-    header( 'Cache-Control: max-age=10' );
-    die( $output );
-  }
-/**
- * save content in a file
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.2.12 - 2007-12-30
- * @param string $directory optional
- * @param string $filename optional
- * @param string $delimiter optional
- * @return bool
- */
-  function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
-    if( $directory )
-      $this->setConfig( 'directory', $directory );
-    if( $filename )
-      $this->setConfig( 'filename',  $filename );
-    if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
-      $this->setConfig( 'delimiter', $delimiter );
-    if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
-      $dirfile = $this->getConfig( 'dirfile' );
-    $iCalFile = @fopen( $dirfile, 'w' );
-    if( $iCalFile ) {
-      if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
-        return FALSE;
-      fclose( $iCalFile );
-      return TRUE;
-    }
-    else
-      return FALSE;
-  }
-/**
- * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
- * else FALSE is returned
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.2.12 - 2007-10-28
- * @param string $directory optional alt. int timeout
- * @param string $filename optional
- * @param string $delimiter optional
- * @param int timeout optional, default 3600 sec
- * @return redirect/FALSE
- */
-  function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
-    if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
-      $timeout   = (int) $directory;
-      $directory = FALSE;
-    }
-    if( $directory )
-      $this->setConfig( 'directory', $directory );
-    if( $filename )
-      $this->setConfig( 'filename',  $filename );
-    if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
-      $this->setConfig( 'delimiter', $delimiter );
-    $filesize    = $this->getConfig( 'filesize' );
-    if( 0 >= $filesize )
-      return FALSE;
-    $dirfile     = $this->getConfig( 'dirfile' );
-    if( time() - filemtime( $dirfile ) < $timeout) {
-      clearstatcache();
-      $dirfile   = $this->getConfig( 'dirfile' );
-      $filename  = $this->getConfig( 'filename' );
-//    if( headers_sent( $filename, $linenum ))
-//      die( "Headers already sent in $filename on line $linenum\n" );
-      if( 'xcal' == $this->format )
-        header( 'Content-Type: application/calendar+xml; charset=utf-8' );
-      else
-        header( 'Content-Type: text/calendar; charset=utf-8' );
-      header( 'Content-Length: '.$filesize );
-      header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
-      header( 'Cache-Control: max-age=10' );
-      $fp = @fopen( $dirfile, 'r' );
-      if( $fp ) {
-        fpassthru( $fp );
-        fclose( $fp );
-      }
-      die();
-    }
-    else
-      return FALSE;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- *  abstract class for calendar components
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- */
-class calendarComponent {
-            //  component property variables
-  var $uid;
-  var $dtstamp;
-
-            //  component config variables
-  var $allowEmpty;
-  var $language;
-  var $nl;
-  var $unique_id;
-  var $format;
-  var $objName; // created automatically at instance creation
-  var $dtzid;   // default (local) timezone
-            //  component internal variables
-  var $componentStart1;
-  var $componentStart2;
-  var $componentEnd1;
-  var $componentEnd2;
-  var $elementStart1;
-  var $elementStart2;
-  var $elementEnd1;
-  var $elementEnd2;
-  var $intAttrDelimiter;
-  var $attributeDelimiter;
-  var $valueInit;
-            //  component xCal declaration container
-  var $xcaldecl;
-/**
- * constructor for calendar component object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-17
- */
-  function calendarComponent() {
-    $this->objName         = ( isset( $this->timezonetype )) ?
-                          strtolower( $this->timezonetype )  :  get_class ( $this );
-    $this->uid             = array();
-    $this->dtstamp         = array();
-
-    $this->language        = null;
-    $this->nl              = null;
-    $this->unique_id       = null;
-    $this->format          = null;
-    $this->dtzid           = null;
-    $this->allowEmpty      = TRUE;
-    $this->xcaldecl        = array();
-
-    $this->_createFormat();
-    $this->_makeDtstamp();
-  }
-/*********************************************************************************/
-/**
- * Property Name: ACTION
- */
-/**
- * creates formatted output for calendar component property action
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createAction() {
-    if( empty( $this->action )) return FALSE;
-    if( empty( $this->action['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
-    $attributes = $this->_createParams( $this->action['params'] );
-    return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
-  }
-/**
- * set calendar component property action
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
- * @param mixed $params
- * @return bool
- */
-  function setAction( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: ATTACH
- */
-/**
- * creates formatted output for calendar component property attach
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.16 - 2012-02-04
- * @return string
- */
-  function createAttach() {
-    if( empty( $this->attach )) return FALSE;
-    $output       = null;
-    foreach( $this->attach as $attachPart ) {
-      if( !empty( $attachPart['value'] )) {
-        $attributes = $this->_createParams( $attachPart['params'] );
-        if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
-          $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
-          $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
-          $output     = substr( $str, 0, 75 ).$this->nl;
-          $str        = substr( $str, 75 );
-          $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
-          if( ' ' == substr( $output, -1 ))
-            $output   = rtrim( $output );
-          if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
-            $output  .= $this->nl;
-          return $output;
-        }
-        $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
-      }
-      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
-    }
-    return $output;
-  }
-/**
- * set calendar component property attach
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-06
- * @param string $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setAttach( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: ATTENDEE
- */
-/**
- * creates formatted output for calendar component property attendee
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.12 - 2012-01-31
- * @return string
- */
-  function createAttendee() {
-    if( empty( $this->attendee )) return FALSE;
-    $output = null;
-    foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
-      if( empty( $attendeePart['value'] )) {
-        if( $this->getConfig( 'allowEmpty' ))
-          $output .= $this->_createElement( 'ATTENDEE' );
-        continue;
-      }
-      $attendee1 = $attendee2 = null;
-      foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
-        if( 'value' == $paramlabel )
-          $attendee2     .= $paramvalue;
-        elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
-          $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
-          foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
-            if( is_array( $pValue ) || in_array( $pKey, $mParams ))
-              continue;
-            if(( FALSE !== strpos( $pValue, ':' )) ||
-               ( FALSE !== strpos( $pValue, ';' )) ||
-               ( FALSE !== strpos( $pValue, ',' )))
-              $paramvalue[$pKey] = '"'.$pValue.'"';
-          }
-        // set attenddee parameters in rfc2445 order
-          if( isset( $paramvalue['CUTYPE'] ))
-            $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
-          if( isset( $paramvalue['MEMBER'] )) {
-            $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
-            foreach( $paramvalue['MEMBER'] as $cix => $opv )
-              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
-          }
-          if( isset( $paramvalue['ROLE'] ))
-            $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
-          if( isset( $paramvalue['PARTSTAT'] ))
-            $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
-          if( isset( $paramvalue['RSVP'] ))
-            $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
-          if( isset( $paramvalue['DELEGATED-TO'] )) {
-            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
-            foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
-              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
-          }
-          if( isset( $paramvalue['DELEGATED-FROM'] )) {
-            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
-            foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
-              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
-          }
-          if( isset( $paramvalue['SENT-BY'] ))
-            $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
-          if( isset( $paramvalue['CN'] ))
-            $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
-          if( isset( $paramvalue['DIR'] )) {
-            $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
-            $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
-          }
-          if( isset( $paramvalue['LANGUAGE'] ))
-            $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
-          $xparams = array();
-          foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
-            if( ctype_digit( (string) $optparamlabel )) {
-              $xparams[]  = $optparamvalue;
-              continue;
-            }
-            if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
-              $xparams[$optparamlabel] = $optparamvalue;
-          } // end foreach 3
-          ksort( $xparams, SORT_STRING );
-          foreach( $xparams as $paramKey => $paramValue ) {
-            if( ctype_digit( (string) $paramKey ))
-              $attendee1 .= $this->intAttrDelimiter.$paramValue;
-            else
-              $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
-          }      // end foreach 3
-        }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
-      }          // end foreach 2
-      $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
-    }              // end foreach 1
-    return $output;
-  }
-/**
- * set calendar component property attach
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.17 - 2012-02-03
- * @param string $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setAttendee( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-          // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
-    if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
-      $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos );
-    elseif( !empty( $value ))
-      $value = 'MAILTO:'.$value;
-    $params2 = array();
-    if( is_array($params )) {
-      $optarrays = array();
-      foreach( $params as $optparamlabel => $optparamvalue ) {
-        $optparamlabel = strtoupper( $optparamlabel );
-        switch( $optparamlabel ) {
-          case 'MEMBER':
-          case 'DELEGATED-TO':
-          case 'DELEGATED-FROM':
-            if( !is_array( $optparamvalue ))
-              $optparamvalue = array( $optparamvalue );
-            foreach( $optparamvalue as $part ) {
-              $part = trim( $part );
-              if(( '"' == substr( $part, 0, 1 )) &&
-                 ( '"' == substr( $part, -1 )))
-                $part = substr( $part, 1, ( strlen( $part ) - 2 ));
-              if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
-                $part = "MAILTO:$part";
-              else
-                $part = 'MAILTO:'.substr( $part, 7 );
-              $optarrays[$optparamlabel][] = $part;
-            }
-            break;
-          default:
-            if(( '"' == substr( $optparamvalue, 0, 1 )) &&
-               ( '"' == substr( $optparamvalue, -1 )))
-              $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
-            if( 'SENT-BY' ==  $optparamlabel ) {
-              if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
-                $optparamvalue = "MAILTO:$optparamvalue";
-              else
-                $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
-            }
-            $params2[$optparamlabel] = $optparamvalue;
-            break;
-        } // end switch( $optparamlabel.. .
-      } // end foreach( $optparam.. .
-      foreach( $optarrays as $optparamlabel => $optparams )
-        $params2[$optparamlabel] = $optparams;
-    }
-        // remove defaults
-    iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
-    iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
-    iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
-    iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
-        // check language setting
-    if( isset( $params2['CN' ] )) {
-      $lang = $this->getConfig( 'language' );
-      if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
-        $params2['LANGUAGE' ] = $lang;
-    }
-    iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: CATEGORIES
- */
-/**
- * creates formatted output for calendar component property categories
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createCategories() {
-    if( empty( $this->categories )) return FALSE;
-    $output = null;
-    foreach( $this->categories as $category ) {
-      if( empty( $category['value'] )) {
-        if ( $this->getConfig( 'allowEmpty' ))
-          $output .= $this->_createElement( 'CATEGORIES' );
-        continue;
-      }
-      $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
-      if( is_array( $category['value'] )) {
-        foreach( $category['value'] as $cix => $categoryPart )
-          $category['value'][$cix] = $this->_strrep( $categoryPart );
-        $content  = implode( ',', $category['value'] );
-      }
-      else
-        $content  = $this->_strrep( $category['value'] );
-      $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property categories
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-06
- * @param mixed $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setCategories( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
-    return TRUE;
- }
-/*********************************************************************************/
-/**
- * Property Name: CLASS
- */
-/**
- * creates formatted output for calendar component property class
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 0.9.7 - 2006-11-20
- * @return string
- */
-  function createClass() {
-    if( empty( $this->class )) return FALSE;
-    if( empty( $this->class['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
-    $attributes = $this->_createParams( $this->class['params'] );
-    return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
-  }
-/**
- * set calendar component property class
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
- * @param array $params optional
- * @return bool
- */
-  function setClass( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: COMMENT
- */
-/**
- * creates formatted output for calendar component property comment
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createComment() {
-    if( empty( $this->comment )) return FALSE;
-    $output = null;
-    foreach( $this->comment as $commentPart ) {
-      if( empty( $commentPart['value'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
-        continue;
-      }
-      $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
-      $content    = $this->_strrep( $commentPart['value'] );
-      $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property comment
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-06
- * @param string $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setComment( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: COMPLETED
- */
-/**
- * creates formatted output for calendar component property completed
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createCompleted( ) {
-    if( empty( $this->completed )) return FALSE;
-    if( !isset( $this->completed['value']['year'] )  &&
-        !isset( $this->completed['value']['month'] ) &&
-        !isset( $this->completed['value']['day'] )   &&
-        !isset( $this->completed['value']['hour'] )  &&
-        !isset( $this->completed['value']['min'] )   &&
-        !isset( $this->completed['value']['sec'] ))
-      if( $this->getConfig( 'allowEmpty' ))
-        return $this->_createElement( 'COMPLETED' );
-      else return FALSE;
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->completed['value'], 7 );
-    $attributes = $this->_createParams( $this->completed['params'] );
-    return $this->_createElement( 'COMPLETED', $attributes, $formatted );
-  }
-/**
- * set calendar component property completed
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return bool
- */
-  function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    if( empty( $year )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: CONTACT
- */
-/**
- * creates formatted output for calendar component property contact
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @return string
- */
-  function createContact() {
-    if( empty( $this->contact )) return FALSE;
-    $output = null;
-    foreach( $this->contact as $contact ) {
-      if( !empty( $contact['value'] )) {
-        $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
-        $content    = $this->_strrep( $contact['value'] );
-        $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
-      }
-      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
-    }
-    return $output;
-  }
-/**
- * set calendar component property contact
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param string $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setContact( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: CREATED
- */
-/**
- * creates formatted output for calendar component property created
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createCreated() {
-    if( empty( $this->created )) return FALSE;
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->created['value'], 7 );
-    $attributes = $this->_createParams( $this->created['params'] );
-    return $this->_createElement( 'CREATED', $attributes, $formatted );
-  }
-/**
- * set calendar component property created
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @param mixed $year optional
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param mixed $params optional
- * @return bool
- */
-  function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    if( !isset( $year )) {
-      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
-    }
-    $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DESCRIPTION
- */
-/**
- * creates formatted output for calendar component property description
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createDescription() {
-    if( empty( $this->description )) return FALSE;
-    $output       = null;
-    foreach( $this->description as $description ) {
-      if( !empty( $description['value'] )) {
-        $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
-        $content    = $this->_strrep( $description['value'] );
-        $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
-      }
-      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
-    }
-    return $output;
-  }
-/**
- * set calendar component property description
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.24 - 2010-11-06
- * @param string $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setDescription( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
-    if( 'vjournal' != $this->objName )
-      $index = 1;
-    iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DTEND
- */
-/**
- * creates formatted output for calendar component property dtend
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- * @return string
- */
-  function createDtend() {
-    if( empty( $this->dtend )) return FALSE;
-    if( !isset( $this->dtend['value']['year'] )  &&
-        !isset( $this->dtend['value']['month'] ) &&
-        !isset( $this->dtend['value']['day'] )   &&
-        !isset( $this->dtend['value']['hour'] )  &&
-        !isset( $this->dtend['value']['min'] )   &&
-        !isset( $this->dtend['value']['sec'] ))
-      if( $this->getConfig( 'allowEmpty' ))
-        return $this->_createElement( 'DTEND' );
-      else return FALSE;
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtend['value'] );
-    if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
-       ( !isset( $this->dtend['params']['VALUE'] )        || ( $this->dtend['params']['VALUE'] != 'DATE' )) &&
-         !isset( $this->dtend['params']['TZID'] ))
-      $this->dtend['params']['TZID'] = $tzid;
-    $attributes = $this->_createParams( $this->dtend['params'] );
-    return $this->_createElement( 'DTEND', $attributes, $formatted );
-  }
-/**
- * set calendar component property dtend
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param string $tz optional
- * @param array $params optional
- * @return bool
- */
-  function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
-    if( empty( $year )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DTSTAMP
- */
-/**
- * creates formatted output for calendar component property dtstamp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.4 - 2008-03-07
- * @return string
- */
-  function createDtstamp() {
-    if( !isset( $this->dtstamp['value']['year'] )  &&
-        !isset( $this->dtstamp['value']['month'] ) &&
-        !isset( $this->dtstamp['value']['day'] )   &&
-        !isset( $this->dtstamp['value']['hour'] )  &&
-        !isset( $this->dtstamp['value']['min'] )   &&
-        !isset( $this->dtstamp['value']['sec'] ))
-      $this->_makeDtstamp();
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 );
-    $attributes = $this->_createParams( $this->dtstamp['params'] );
-    return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
-  }
-/**
- * computes datestamp for calendar component object instance dtstamp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.9 - 2011-08-10
- * @return void
- */
-  function _makeDtstamp() {
-    $d = mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y'));
-    $this->dtstamp['value'] = array( 'year'  => date( 'Y', $d )
-                                   , 'month' => date( 'm', $d )
-                                   , 'day'   => date( 'd', $d )
-                                   , 'hour'  => date( 'H', $d )
-                                   , 'min'   => date( 'i', $d )
-                                   , 'sec'   => date( 's', $d ));
-    $this->dtstamp['params'] = null;
-  }
-/**
- * set calendar component property dtstamp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return TRUE
- */
-  function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    if( empty( $year ))
-      $this->_makeDtstamp();
-    else
-      $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DTSTART
- */
-/**
- * creates formatted output for calendar component property dtstart
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-15
- * @return string
- */
-  function createDtstart() {
-    if( empty( $this->dtstart )) return FALSE;
-    if( !isset( $this->dtstart['value']['year'] )  &&
-        !isset( $this->dtstart['value']['month'] ) &&
-        !isset( $this->dtstart['value']['day'] )   &&
-        !isset( $this->dtstart['value']['hour'] )  &&
-        !isset( $this->dtstart['value']['min'] )   &&
-        !isset( $this->dtstart['value']['sec'] )) {
-      if( $this->getConfig( 'allowEmpty' ))
-        return $this->_createElement( 'DTSTART' );
-      else return FALSE;
-    }
-    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
-       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
-    elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
-       ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' ))  &&
-         !isset( $this->dtstart['params']['TZID'] ))
-      $this->dtstart['params']['TZID'] = $tzid;
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstart['value'] );
-    $attributes = $this->_createParams( $this->dtstart['params'] );
-    return $this->_createElement( 'DTSTART', $attributes, $formatted );
-  }
-/**
- * set calendar component property dtstart
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.22 - 2010-09-22
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param string $tz optional
- * @param array $params optional
- * @return bool
- */
-  function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
-    if( empty( $year )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DUE
- */
-/**
- * creates formatted output for calendar component property due
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createDue() {
-    if( empty( $this->due )) return FALSE;
-    if( !isset( $this->due['value']['year'] )  &&
-        !isset( $this->due['value']['month'] ) &&
-        !isset( $this->due['value']['day'] )   &&
-        !isset( $this->due['value']['hour'] )  &&
-        !isset( $this->due['value']['min'] )   &&
-        !isset( $this->due['value']['sec'] )) {
-      if( $this->getConfig( 'allowEmpty' ))
-        return $this->_createElement( 'DUE' );
-      else
-       return FALSE;
-    }
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->due['value'] );
-    if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
-       ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' ))  &&
-         !isset( $this->due['params']['TZID'] ))
-      $this->due['params']['TZID'] = $tzid;
-    $attributes = $this->_createParams( $this->due['params'] );
-    return $this->_createElement( 'DUE', $attributes, $formatted );
-  }
-/**
- * set calendar component property due
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return bool
- */
-  function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
-    if( empty( $year )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: DURATION
- */
-/**
- * creates formatted output for calendar component property duration
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createDuration() {
-    if( empty( $this->duration )) return FALSE;
-    if( !isset( $this->duration['value']['week'] ) &&
-        !isset( $this->duration['value']['day'] )  &&
-        !isset( $this->duration['value']['hour'] ) &&
-        !isset( $this->duration['value']['min'] )  &&
-        !isset( $this->duration['value']['sec'] ))
-      if( $this->getConfig( 'allowEmpty' ))
-        return $this->_createElement( 'DURATION', array(), null );
-      else return FALSE;
-    $attributes = $this->_createParams( $this->duration['params'] );
-    return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_format_duration( $this->duration['value'] ));
-  }
-/**
- * set calendar component property duration
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param mixed $week
- * @param mixed $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return bool
- */
-  function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
-    if( is_array( $week ) && ( 1 <= count( $week )))
-      $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
-    elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
-      $week = trim( $week );
-      if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
-        $week = substr( $week, 1 );
-      $this->duration = array( 'value' => iCalUtilityFunctions::_duration_string( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
-    }
-    elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
-      return FALSE;
-    else
-      $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: EXDATE
- */
-/**
- * creates formatted output for calendar component property exdate
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createExdate() {
-    if( empty( $this->exdate )) return FALSE;
-    $output = null;
-    foreach( $this->exdate as $ex => $theExdate ) {
-      if( empty( $theExdate['value'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'EXDATE' );
-        continue;
-      }
-      $content = $attributes = null;
-      foreach( $theExdate['value'] as $eix => $exdatePart ) {
-        $parno = count( $exdatePart );
-        $formatted = iCalUtilityFunctions::_format_date_time( $exdatePart, $parno );
-        if( isset( $theExdate['params']['TZID'] ))
-          $formatted = str_replace( 'Z', '', $formatted);
-        if( 0 < $eix ) {
-          if( isset( $theExdate['value'][0]['tz'] )) {
-            if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
-               ( 'Z' == $theExdate['value'][0]['tz'] )) {
-              if( 'Z' != substr( $formatted, -1 ))
-                $formatted .= 'Z';
-            }
-            else
-              $formatted = str_replace( 'Z', '', $formatted );
-          }
-          else
-            $formatted = str_replace( 'Z', '', $formatted );
-        }
-        $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
-      }
-      $attributes .= $this->_createParams( $theExdate['params'] );
-      $output .= $this->_createElement( 'EXDATE', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property exdate
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-01-19
- * @param array exdates
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
-    if( empty( $exdates )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
-    $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
-            /* ev. check 1:st date and save ev. timezone **/
-    iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
-    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
-    foreach( $exdates as $eix => $theExdate ) {
-      iCalUtilityFunctions::_strDate2arr( $theExdate );
-      if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate ))
-        $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
-      elseif(  is_array( $theExdate ))
-        $exdatea = iCalUtilityFunctions::_date_time_array( $theExdate, $parno );
-      elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
-        $exdatea = iCalUtilityFunctions::_date_time_string( $theExdate, $parno );
-        unset( $exdatea['unparsedtext'] );
-      }
-      if( 3 == $parno )
-        unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
-      elseif( isset( $exdatea['tz'] ))
-        $exdatea['tz'] = (string) $exdatea['tz'];
-      if(  isset( $input['params']['TZID'] ) ||
-         ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
-         ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
-         ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
-        unset( $exdatea['tz'] );
-      if( $toZ ) // time zone Z
-        $exdatea['tz'] = 'Z';
-      $input['value'][] = $exdatea;
-    }
-    if( 0 >= count( $input['value'] ))
-      return FALSE;
-    if( 3 == $parno ) {
-      $input['params']['VALUE'] = 'DATE';
-      unset( $input['params']['TZID'] );
-    }
-    if( $toZ ) // time zone Z
-      unset( $input['params']['TZID'] );
-    iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: EXRULE
- */
-/**
- * creates formatted output for calendar component property exrule
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createExrule() {
-    if( empty( $this->exrule )) return FALSE;
-    return $this->_format_recur( 'EXRULE', $this->exrule );
-  }
-/**
- * set calendar component property exdate
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param array $exruleset
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
-    if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: FREEBUSY
- */
-/**
- * creates formatted output for calendar component property freebusy
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.1.23 - 2012-02-16
- * @return string
- */
-  function createFreebusy() {
-    if( empty( $this->freebusy )) return FALSE;
-    $output = null;
-    foreach( $this->freebusy as $freebusyPart ) {
-      if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
-        continue;
-      }
-      $attributes = $content = null;
-      if( isset( $freebusyPart['value']['fbtype'] )) {
-          $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
-        unset( $freebusyPart['value']['fbtype'] );
-        $freebusyPart['value'] = array_values( $freebusyPart['value'] );
-      }
-      else
-        $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
-      $attributes .= $this->_createParams( $freebusyPart['params'] );
-      $fno = 1;
-      $cnt = count( $freebusyPart['value']);
-      foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
-        $formatted   = iCalUtilityFunctions::_format_date_time( $freebusyPeriod[0] );
-        $content .= $formatted;
-        $content .= '/';
-        $cnt2 = count( $freebusyPeriod[1]);
-        if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
-          $cnt2 = 7;
-        elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
-          $cnt2 = 5;
-        if(( 7 == $cnt2 )   &&    // period=  -> date-time
-            isset( $freebusyPeriod[1]['year'] )  &&
-            isset( $freebusyPeriod[1]['month'] ) &&
-            isset( $freebusyPeriod[1]['day'] )) {
-          $content .= iCalUtilityFunctions::_format_date_time( $freebusyPeriod[1] );
-        }
-        else {                                  // period=  -> dur-time
-          $content .= iCalUtilityFunctions::_format_duration( $freebusyPeriod[1] );
-        }
-        if( $fno < $cnt )
-          $content .= ',';
-        $fno++;
-      }
-      $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property freebusy
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.30 - 2012-01-16
- * @param string $fbType
- * @param array $fbValues
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
-    if( empty( $fbValues )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $fbType = strtoupper( $fbType );
-    if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
-       ( 'X-' != substr( $fbType, 0, 2 )))
-      $fbType = 'BUSY';
-    $input = array( 'fbtype' => $fbType );
-    foreach( $fbValues as $fbPeriod ) {   // periods => period
-      if( empty( $fbPeriod ))
-        continue;
-      $freebusyPeriod = array();
-      foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
-        $freebusyPairMember = array();
-        if( is_array( $fbMember )) {
-          if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
-            $freebusyPairMember       = iCalUtilityFunctions::_date_time_array( $fbMember, 7 );
-            $freebusyPairMember['tz'] = 'Z';
-          }
-          elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
-            $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
-            $freebusyPairMember['tz'] = 'Z';
-          }
-          else {                                         // array format duration
-            $freebusyPairMember = iCalUtilityFunctions::_duration_array( $fbMember );
-          }
-        }
-        elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
-               ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
-          if( 'P' != $fbMember{0} )
-            $fbmember = substr( $fbMember, 1 );
-          $freebusyPairMember = iCalUtilityFunctions::_duration_string( $fbMember );
-        }
-        elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
-          $freebusyPairMember       = iCalUtilityFunctions::_date_time_string( $fbMember, 7 );
-          unset( $freebusyPairMember['unparsedtext'] );
-          $freebusyPairMember['tz'] = 'Z';
-        }
-        $freebusyPeriod[]   = $freebusyPairMember;
-      }
-      $input[]              = $freebusyPeriod;
-    }
-    iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: GEO
- */
-/**
- * creates formatted output for calendar component property geo
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createGeo() {
-    if( empty( $this->geo )) return FALSE;
-    if( empty( $this->geo['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
-    $attributes = $this->_createParams( $this->geo['params'] );
-    $content    = null;
-    $content   .= number_format( (float) $this->geo['value']['latitude'], 6, '.', '');
-    $content   .= ';';
-    $content   .= number_format( (float) $this->geo['value']['longitude'], 6, '.', '');
-    return $this->_createElement( 'GEO', $attributes, $content );
-  }
-/**
- * set calendar component property geo
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param float $latitude
- * @param float $longitude
- * @param array $params optional
- * @return bool
- */
-  function setGeo( $latitude, $longitude, $params=FALSE ) {
-    if( !empty( $latitude ) && !empty( $longitude )) {
-      if( !is_array( $this->geo )) $this->geo = array();
-      $this->geo['value']['latitude']  = $latitude;
-      $this->geo['value']['longitude'] = $longitude;
-      $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
-    }
-    elseif( $this->getConfig( 'allowEmpty' ))
-      $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
-    else
-      return FALSE;
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: LAST-MODIFIED
- */
-/**
- * creates formatted output for calendar component property last-modified
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createLastModified() {
-    if( empty( $this->lastmodified )) return FALSE;
-    $attributes = $this->_createParams( $this->lastmodified['params'] );
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 );
-    return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
-  }
-/**
- * set calendar component property completed
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @param mixed $year optional
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return boll
- */
-  function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    if( empty( $year ))
-      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
-    $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: LOCATION
- */
-/**
- * creates formatted output for calendar component property location
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @return string
- */
-  function createLocation() {
-    if( empty( $this->location )) return FALSE;
-    if( empty( $this->location['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
-    $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
-    $content    = $this->_strrep( $this->location['value'] );
-    return $this->_createElement( 'LOCATION', $attributes, $content );
-  }
-/**
- * set calendar component property location
- '
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param array params optional
- * @return bool
- */
-  function setLocation( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: ORGANIZER
- */
-/**
- * creates formatted output for calendar component property organizer
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.33 - 2010-12-17
- * @return string
- */
-  function createOrganizer() {
-    if( empty( $this->organizer )) return FALSE;
-    if( empty( $this->organizer['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
-    $attributes = $this->_createParams( $this->organizer['params']
-                                      , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
-    return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
-  }
-/**
- * set calendar component property organizer
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.27 - 2010-11-29
- * @param string $value
- * @param array params optional
- * @return bool
- */
-  function setOrganizer( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
-      $value = 'MAILTO:'.$value;
-    else
-      $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
-    $value = str_replace( 'mailto:', 'MAILTO:', $value );
-    $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    if( isset( $this->organizer['params']['SENT-BY'] )){
-      if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
-        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
-      else
-        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
-    }
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: PERCENT-COMPLETE
- */
-/**
- * creates formatted output for calendar component property percent-complete
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @return string
- */
-  function createPercentComplete() {
-    if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
-    if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
-    $attributes = $this->_createParams( $this->percentcomplete['params'] );
-    return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
-  }
-/**
- * set calendar component property percent-complete
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @param int $value
- * @param array $params optional
- * @return bool
- */
-  function setPercentComplete( $value, $params=FALSE ) {
-    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: PRIORITY
- */
-/**
- * creates formatted output for calendar component property priority
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @return string
- */
-  function createPriority() {
-    if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
-    if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
-    $attributes = $this->_createParams( $this->priority['params'] );
-    return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
-  }
-/**
- * set calendar component property priority
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @param int $value
- * @param array $params optional
- * @return bool
- */
-  function setPriority( $value, $params=FALSE  ) {
-    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: RDATE
- */
-/**
- * creates formatted output for calendar component property rdate
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-10-26
- * @return string
- */
-  function createRdate() {
-    if( empty( $this->rdate )) return FALSE;
-    $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
-    $output = null;
-    if( $utctime  )
-      unset( $this->rdate['params']['TZID'] );
-    foreach( $this->rdate as $theRdate ) {
-      if( empty( $theRdate['value'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
-        continue;
-      }
-      if( $utctime  )
-        unset( $theRdate['params']['TZID'] );
-      $attributes = $this->_createParams( $theRdate['params'] );
-      $cnt = count( $theRdate['value'] );
-      $content = null;
-      $rno = 1;
-      foreach( $theRdate['value'] as $rpix => $rdatePart ) {
-        $contentPart = null;
-        if( is_array( $rdatePart ) &&
-            isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
-          if( $utctime )
-            unset( $rdatePart[0]['tz'] );
-          $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1
-          if( $utctime || !empty( $theRdate['params']['TZID'] ))
-            $formatted = str_replace( 'Z', '', $formatted);
-          if( 0 < $rpix ) {
-            if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
-              if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
-            }
-            else
-              $formatted = str_replace( 'Z', '', $formatted );
-          }
-          $contentPart .= $formatted;
-          $contentPart .= '/';
-          $cnt2 = count( $rdatePart[1]);
-          if( array_key_exists( 'year', $rdatePart[1] )) {
-            if( array_key_exists( 'hour', $rdatePart[1] ))
-              $cnt2 = 7;                                      // date-time
-            else
-              $cnt2 = 3;                                      // date
-          }
-          elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
-            $cnt2 = 5;
-          if(( 7 == $cnt2 )   &&    // period=  -> date-time
-              isset( $rdatePart[1]['year'] )  &&
-              isset( $rdatePart[1]['month'] ) &&
-              isset( $rdatePart[1]['day'] )) {
-            if( $utctime )
-              unset( $rdatePart[1]['tz'] );
-            $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2
-            if( $utctime || !empty( $theRdate['params']['TZID'] ))
-              $formatted = str_replace( 'Z', '', $formatted);
-            if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
-              if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
-            }
-            else
-              $formatted = str_replace( 'Z', '', $formatted );
-           $contentPart .= $formatted;
-          }
-          else {                                  // period=  -> dur-time
-            $contentPart .= iCalUtilityFunctions::_format_duration( $rdatePart[1] );
-          }
-        } // PERIOD end
-        else { // SINGLE date start
-          if( $utctime )
-            unset( $rdatePart['tz'] );
-          $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart);
-          if( $utctime || !empty( $theRdate['params']['TZID'] ))
-            $formatted = str_replace( 'Z', '', $formatted);
-          if( !$utctime && ( 0 < $rpix )) {
-            if( !empty( $theRdate['value'][0]['tz'] ) && iCalUtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) {
-              if( 'Z' != substr( $formatted, -1 ))
-                $formatted .= 'Z';
-            }
-            else
-              $formatted = str_replace( 'Z', '', $formatted );
-          }
-          $contentPart .= $formatted;
-        }
-        $content .= $contentPart;
-        if( $rno < $cnt )
-          $content .= ',';
-        $rno++;
-      }
-      $output    .= $this->_createElement( 'RDATE', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property rdate
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-01-31
- * @param array $rdates
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
-    if( empty( $rdates )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
-    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
-      unset( $input['params']['TZID'] );
-      $input['params']['VALUE'] = 'DATE-TIME';
-    }
-    $zArr = array( 'GMT', 'UTC', 'Z' );
-    $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
-            /*  check if PERIOD, if not set */
-    if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
-          isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
-          isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
-    (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
-                                      iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
-                                    ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
-     ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
-      $input['params']['VALUE'] = 'PERIOD';
-            /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
-    $date  = reset( $rdates );
-    if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
-      $date  = reset( $date );
-    iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
-    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
-    foreach( $rdates as $rpix => $theRdate ) {
-      $inputa = null;
-      iCalUtilityFunctions::_strDate2arr( $theRdate );
-      if( is_array( $theRdate )) {
-        if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
-          foreach( $theRdate as $rix => $rPeriod ) {
-            iCalUtilityFunctions::_strDate2arr( $theRdate );
-            if( is_array( $rPeriod )) {
-              if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod ))      // timestamp
-                $inputab  = ( isset( $rPeriod['tz'] )) ? iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCalUtilityFunctions::_timestamp2date( $rPeriod, 6 );
-              elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod ))
-                $inputab  = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCalUtilityFunctions::_date_time_array( $rPeriod, 6 );
-              elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
-                $inputab  = iCalUtilityFunctions::_date_time_string( reset( $rPeriod ), $parno );
-                unset( $inputab['unparsedtext'] );
-              }
-              else                                               // array format duration
-                $inputab  = iCalUtilityFunctions::_duration_array( $rPeriod );
-            }
-            elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
-                   ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
-              if( 'P' != $rPeriod[0] )
-                $rPeriod  = substr( $rPeriod, 1 );
-              $inputab    = iCalUtilityFunctions::_duration_string( $rPeriod );
-            }
-            elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
-              $inputab    = iCalUtilityFunctions::_date_time_string( $rPeriod, $parno );
-              unset( $inputab['unparsedtext'] );
-            }
-            if(  isset( $input['params']['TZID'] ) ||
-               ( isset( $inputab['tz'] )   && !iCalUtilityFunctions::_isOffset( $inputab['tz'] )) ||
-               ( isset( $inputa[0] )       && ( !isset( $inputa[0]['tz'] )))       ||
-               ( isset( $inputa[0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa[0]['tz'] )))
-              unset( $inputab['tz'] );
-            if( $toZ )
-              $inputab['tz']   = 'Z';
-            $inputa[]     = $inputab;
-          }
-        } // PERIOD end
-        elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
-          $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
-          if( $toZ )
-            $inputa['tz']   = 'Z';
-        }
-        else {                                                                  // date[-time]
-          $inputa = iCalUtilityFunctions::_date_time_array( $theRdate, $parno );
-          $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
-          if( $toZ )
-            $inputa['tz']   = 'Z';
-        }
-      }
-      elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
-        $inputa       = iCalUtilityFunctions::_date_time_string( $theRdate, $parno );
-        unset( $inputa['unparsedtext'] );
-        if( $toZ )
-          $inputa['tz']   = 'Z';
-      }
-      if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
-        if( 3 == $parno )
-          unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
-        elseif( isset( $inputa['tz'] ))
-          $inputa['tz'] = (string) $inputa['tz'];
-        if(  isset( $input['params']['TZID'] ) ||
-           ( isset( $inputa['tz'] )            && !iCalUtilityFunctions::_isOffset( $inputa['tz'] ))     ||
-           ( isset( $input['value'][0] )       && ( !isset( $input['value'][0]['tz'] )))                 ||
-           ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
-          if( !$toZ )
-            unset( $inputa['tz'] );
-      }
-      $input['value'][] = $inputa;
-    }
-    if( 3 == $parno ) {
-      $input['params']['VALUE'] = 'DATE';
-      unset( $input['params']['TZID'] );
-    }
-    if( $toZ )
-      unset( $input['params']['TZID'] );
-    iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: RECURRENCE-ID
- */
-/**
- * creates formatted output for calendar component property recurrence-id
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-15
- * @return string
- */
-  function createRecurrenceid() {
-    if( empty( $this->recurrenceid )) return FALSE;
-    if( empty( $this->recurrenceid['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
-    $formatted  = iCalUtilityFunctions::_format_date_time( $this->recurrenceid['value'] );
-    if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
-       ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' ))  &&
-         !isset( $this->recurrenceid['params']['TZID'] ))
-      $this->recurrenceid['params']['TZID'] = $tzid;
-    $attributes = $this->_createParams( $this->recurrenceid['params'] );
-    return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
-  }
-/**
- * set calendar component property recurrence-id
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-15
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return bool
- */
-  function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
-    if( empty( $year )) {
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->recurrenceid = array( 'value' => null, 'params' => null );
-        return TRUE;
-      }
-      else
-        return FALSE;
-    }
-    $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: RELATED-TO
- */
-/**
- * creates formatted output for calendar component property related-to
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.24 - 2012-02-23
- * @return string
- */
-  function createRelatedTo() {
-    if( empty( $this->relatedto )) return FALSE;
-    $output = null;
-    foreach( $this->relatedto as $relation ) {
-      if( !empty( $relation['value'] ))
-        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), $this->_strrep( $relation['value'] ) );
-      elseif( $this->getConfig( 'allowEmpty' ))
-        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
-    }
-    return $output;
-  }
-/**
- * set calendar component property related-to
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.24 - 2012-02-23
- * @param float $relid
- * @param array $params, optional
- * @param index $index, optional
- * @return bool
- */
-  function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
-    iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: REPEAT
- */
-/**
- * creates formatted output for calendar component property repeat
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @return string
- */
-  function createRepeat() {
-    if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
-    if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
-    $attributes = $this->_createParams( $this->repeat['params'] );
-    return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
-  }
-/**
- * set calendar component property repeat
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @param string $value
- * @param array $params optional
- * @return void
- */
-  function setRepeat( $value, $params=FALSE ) {
-    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: REQUEST-STATUS
- */
-/**
- * creates formatted output for calendar component property request-status
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @return string
- */
-  function createRequestStatus() {
-    if( empty( $this->requeststatus )) return FALSE;
-    $output = null;
-    foreach( $this->requeststatus as $rstat ) {
-      if( empty( $rstat['value']['statcode'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
-        continue;
-      }
-      $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
-      $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
-      $content    .= ';'.$this->_strrep( $rstat['value']['text'] );
-      if( isset( $rstat['value']['extdata'] ))
-        $content  .= ';'.$this->_strrep( $rstat['value']['extdata'] );
-      $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property request-status
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param float $statcode
- * @param string $text
- * @param string $extdata, optional
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
-    if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
-    $input              = array( 'statcode' => $statcode, 'text' => $text );
-    if( $extdata )
-      $input['extdata'] = $extdata;
-    iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: RESOURCES
- */
-/**
- * creates formatted output for calendar component property resources
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-23
- * @return string
- */
-  function createResources() {
-    if( empty( $this->resources )) return FALSE;
-    $output = null;
-    foreach( $this->resources as $resource ) {
-      if( empty( $resource['value'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
-        continue;
-      }
-      $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
-      if( is_array( $resource['value'] )) {
-        foreach( $resource['value'] as $rix => $resourcePart )
-          $resource['value'][$rix] = $this->_strrep( $resourcePart );
-        $content   = implode( ',', $resource['value'] );
-      }
-      else
-        $content   = $this->_strrep( $resource['value'] );
-      $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
-    }
-    return $output;
-  }
-/**
- * set calendar component property recources
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param mixed $value
- * @param array $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setResources( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: RRULE
- */
-/**
- * creates formatted output for calendar component property rrule
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createRrule() {
-    if( empty( $this->rrule )) return FALSE;
-    return $this->_format_recur( 'RRULE', $this->rrule );
-  }
-/**
- * set calendar component property rrule
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param array $rruleset
- * @param array $params, optional
- * @param integer $index, optional
- * @return void
- */
-  function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
-    if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: SEQUENCE
- */
-/**
- * creates formatted output for calendar component property sequence
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @return string
- */
-  function createSequence() {
-    if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
-    if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
-       ( '0' != $this->sequence['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
-    $attributes = $this->_createParams( $this->sequence['params'] );
-    return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
-  }
-/**
- * set calendar component property sequence
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.8 - 2011-09-19
- * @param int $value optional
- * @param array $params optional
- * @return bool
- */
-  function setSequence( $value=FALSE, $params=FALSE ) {
-    if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
-      $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
-    $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: STATUS
- */
-/**
- * creates formatted output for calendar component property status
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createStatus() {
-    if( empty( $this->status )) return FALSE;
-    if( empty( $this->status['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
-    $attributes = $this->_createParams( $this->status['params'] );
-    return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
-  }
-/**
- * set calendar component property status
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param array $params optional
- * @return bool
- */
-  function setStatus( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: SUMMARY
- */
-/**
- * creates formatted output for calendar component property summary
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createSummary() {
-    if( empty( $this->summary )) return FALSE;
-    if( empty( $this->summary['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
-    $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
-    $content    = $this->_strrep( $this->summary['value'] );
-    return $this->_createElement( 'SUMMARY', $attributes, $content );
-  }
-/**
- * set calendar component property summary
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setSummary( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TRANSP
- */
-/**
- * creates formatted output for calendar component property transp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTransp() {
-    if( empty( $this->transp )) return FALSE;
-    if( empty( $this->transp['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
-    $attributes = $this->_createParams( $this->transp['params'] );
-    return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
-  }
-/**
- * set calendar component property transp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setTransp( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TRIGGER
- */
-/**
- * creates formatted output for calendar component property trigger
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-10-21
- * @return string
- */
-  function createTrigger() {
-    if( empty( $this->trigger )) return FALSE;
-    if( empty( $this->trigger['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
-    $content = $attributes = null;
-    if( isset( $this->trigger['value']['year'] )   &&
-        isset( $this->trigger['value']['month'] )  &&
-        isset( $this->trigger['value']['day'] ))
-      $content      .= iCalUtilityFunctions::_format_date_time( $this->trigger['value'] );
-    else {
-      if( TRUE !== $this->trigger['value']['relatedStart'] )
-        $attributes .= $this->intAttrDelimiter.'RELATED=END';
-      if( $this->trigger['value']['before'] )
-        $content    .= '-';
-      $content      .= iCalUtilityFunctions::_format_duration( $this->trigger['value'] );
-    }
-    $attributes     .= $this->_createParams( $this->trigger['params'] );
-    return $this->_createElement( 'TRIGGER', $attributes, $content );
-  }
-/**
- * set calendar component property trigger
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.30 - 2012-01-16
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $week optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param bool $relatedStart optional
- * @param bool $before optional
- * @param array $params optional
- * @return bool
- */
-  function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
-    if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
-      if( $this->getConfig( 'allowEmpty' )) {
-        $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
-        return TRUE;
-      }
-      else
-        return FALSE;
-    if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp
-      $params = iCalUtilityFunctions::_setParams( $month );
-      $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
-      foreach( $date as $k => $v )
-        $$k = $v;
-    }
-    elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
-      $params = iCalUtilityFunctions::_setParams( $month );
-      if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
-           array_key_exists( 'month', $year ) &&
-           array_key_exists( 'day',   $year ))) {  // when this must be a duration
-        if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
-          $relatedStart = FALSE;
-        else
-          $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
-        $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
-      }
-      $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
-      $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
-      $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
-      $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
-      $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
-      $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
-      $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
-      $year  = $SSYY;
-    }
-    elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
-      $params = iCalUtilityFunctions::_setParams( $month );
-      if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
-        $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
-        $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
-        if(     'P'  != $year[0] )
-          $year       = substr( $year, 1 );
-        $date         = iCalUtilityFunctions::_duration_string( $year);
-      }
-      else   // date
-        $date    = iCalUtilityFunctions::_date_time_string( $year, 7 );
-      unset( $year, $month, $day, $date['unparsedtext'] );
-      if( empty( $date ))
-        $sec = 0;
-      else
-        foreach( $date as $k => $v )
-          $$k = $v;
-    }
-    else // single values in function input parameters
-      $params = iCalUtilityFunctions::_setParams( $params );
-    if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
-      $params['VALUE'] = 'DATE-TIME';
-      $hour = ( $hour ) ? $hour : 0;
-      $min  = ( $min  ) ? $min  : 0;
-      $sec  = ( $sec  ) ? $sec  : 0;
-      $this->trigger = array( 'params' => $params );
-      $this->trigger['value'] = array( 'year'  => $year
-                                     , 'month' => $month
-                                     , 'day'   => $day
-                                     , 'hour'  => $hour
-                                     , 'min'   => $min
-                                     , 'sec'   => $sec
-                                     , 'tz'    => 'Z' );
-      return TRUE;
-    }
-    elseif(( empty( $year ) && empty( $month )) &&    // duration
-           (( !empty( $week ) || ( 0 == $week )) ||
-            ( !empty( $day )  || ( 0 == $day  )) ||
-            ( !empty( $hour ) || ( 0 == $hour )) ||
-            ( !empty( $min )  || ( 0 == $min  )) ||
-            ( !empty( $sec )  || ( 0 == $sec  )))) {
-      unset( $params['RELATED'] ); // set at output creation (END only)
-      unset( $params['VALUE'] );   // 'DURATION' default
-      $this->trigger = array( 'params' => $params );
-      $this->trigger['value']  = array();
-      if( !empty( $week )) $this->trigger['value']['week'] = $week;
-      if( !empty( $day  )) $this->trigger['value']['day']  = $day;
-      if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
-      if( !empty( $min  )) $this->trigger['value']['min']  = $min;
-      if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
-      if( empty( $this->trigger['value'] )) {
-        $this->trigger['value']['sec'] = 0;
-        $before                        = FALSE;
-      }
-      $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
-      $before       = ( FALSE !== $before )       ? TRUE : FALSE;
-      $this->trigger['value']['relatedStart'] = $relatedStart;
-      $this->trigger['value']['before']       = $before;
-      return TRUE;
-    }
-    return FALSE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TZID
- */
-/**
- * creates formatted output for calendar component property tzid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTzid() {
-    if( empty( $this->tzid )) return FALSE;
-    if( empty( $this->tzid['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
-    $attributes = $this->_createParams( $this->tzid['params'] );
-    return $this->_createElement( 'TZID', $attributes, $this->_strrep( $this->tzid['value'] ));
-  }
-/**
- * set calendar component property tzid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param array $params optional
- * @return bool
- */
-  function setTzid( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * .. .
- * Property Name: TZNAME
- */
-/**
- * creates formatted output for calendar component property tzname
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTzname() {
-    if( empty( $this->tzname )) return FALSE;
-    $output = null;
-    foreach( $this->tzname as $theName ) {
-      if( !empty( $theName['value'] )) {
-        $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
-        $output    .= $this->_createElement( 'TZNAME', $attributes, $this->_strrep( $theName['value'] ));
-      }
-      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
-    }
-    return $output;
-  }
-/**
- * set calendar component property tzname
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param string $value
- * @param string $params, optional
- * @param integer $index, optional
- * @return bool
- */
-  function setTzname( $value, $params=FALSE, $index=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TZOFFSETFROM
- */
-/**
- * creates formatted output for calendar component property tzoffsetfrom
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTzoffsetfrom() {
-    if( empty( $this->tzoffsetfrom )) return FALSE;
-    if( empty( $this->tzoffsetfrom['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
-    $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
-    return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
-  }
-/**
- * set calendar component property tzoffsetfrom
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setTzoffsetfrom( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TZOFFSETTO
- */
-/**
- * creates formatted output for calendar component property tzoffsetto
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTzoffsetto() {
-    if( empty( $this->tzoffsetto )) return FALSE;
-    if( empty( $this->tzoffsetto['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
-    $attributes = $this->_createParams( $this->tzoffsetto['params'] );
-    return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
-  }
-/**
- * set calendar component property tzoffsetto
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setTzoffsetto( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: TZURL
- */
-/**
- * creates formatted output for calendar component property tzurl
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createTzurl() {
-    if( empty( $this->tzurl )) return FALSE;
-    if( empty( $this->tzurl['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
-    $attributes = $this->_createParams( $this->tzurl['params'] );
-    return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
-  }
-/**
- * set calendar component property tzurl
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return boll
- */
-  function setTzurl( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: UID
- */
-/**
- * creates formatted output for calendar component property uid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 0.9.7 - 2006-11-20
- * @return string
- */
-  function createUid() {
-    if( 0 >= count( $this->uid ))
-      $this->_makeuid();
-    $attributes = $this->_createParams( $this->uid['params'] );
-    return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
-  }
-/**
- * create an unique id for this calendar component object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.2.7 - 2007-09-04
- * @return void
- */
-  function _makeUid() {
-    $date   = date('Ymd\THisT');
-    $unique = substr(microtime(), 2, 4);
-    $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
-    $start  = 0;
-    $end    = strlen( $base ) - 1;
-    $length = 6;
-    $str    = null;
-    for( $p = 0; $p < $length; $p++ )
-      $unique .= $base{mt_rand( $start, $end )};
-    $this->uid = array( 'params' => null );
-    $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
-  }
-/**
- * set calendar component property uid
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setUid( $value, $params=FALSE ) {
-    if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
-    $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: URL
- */
-/**
- * creates formatted output for calendar component property url
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-21
- * @return string
- */
-  function createUrl() {
-    if( empty( $this->url )) return FALSE;
-    if( empty( $this->url['value'] ))
-      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
-    $attributes = $this->_createParams( $this->url['params'] );
-    return $this->_createElement( 'URL', $attributes, $this->url['value'] );
-  }
-/**
- * set calendar component property url
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-11-04
- * @param string $value
- * @param string $params optional
- * @return bool
- */
-  function setUrl( $value, $params=FALSE ) {
-    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
-    return TRUE;
-  }
-/*********************************************************************************/
-/**
- * Property Name: x-prop
- */
-/**
- * creates formatted output for calendar component property x-prop
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.3 - 2011-05-14
- * @return string
- */
-  function createXprop() {
-    if( empty( $this->xprop )) return FALSE;
-    $output = null;
-    foreach( $this->xprop as $label => $xpropPart ) {
-      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
-        continue;
-      }
-      $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
-      if( is_array( $xpropPart['value'] )) {
-        foreach( $xpropPart['value'] as $pix => $theXpart )
-          $xpropPart['value'][$pix] = $this->_strrep( $theXpart );
-        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
-      }
-      else
-        $xpropPart['value'] = $this->_strrep( $xpropPart['value'] );
-      $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
-    }
-    return $output;
-  }
-/**
- * set calendar component property x-prop
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.9 - 2012-01-16
- * @param string $label
- * @param mixed $value
- * @param array $params optional
- * @return bool
- */
-  function setXprop( $label, $value, $params=FALSE ) {
-    if( empty( $label ))
-      return FALSE;
-    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
-      return FALSE;
-    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
-    $xprop           = array( 'value' => $value );
-    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
-    if( !is_array( $this->xprop )) $this->xprop = array();
-    $this->xprop[strtoupper( $label )] = $xprop;
-    return TRUE;
-  }
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * create element format parts
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.0.6 - 2006-06-20
- * @return string
- */
-  function _createFormat() {
-    $objectname                   = null;
-    switch( $this->format ) {
-      case 'xcal':
-        $objectname               = ( isset( $this->timezonetype )) ?
-                                 strtolower( $this->timezonetype )  :  strtolower( $this->objName );
-        $this->componentStart1    = $this->elementStart1 = '<';
-        $this->componentStart2    = $this->elementStart2 = '>';
-        $this->componentEnd1      = $this->elementEnd1   = '</';
-        $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
-        $this->intAttrDelimiter   = '<!-- -->';
-        $this->attributeDelimiter = $this->nl;
-        $this->valueInit          = null;
-        break;
-      default:
-        $objectname               = ( isset( $this->timezonetype )) ?
-                                 strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
-        $this->componentStart1    = 'BEGIN:';
-        $this->componentStart2    = null;
-        $this->componentEnd1      = 'END:';
-        $this->componentEnd2      = $this->nl;
-        $this->elementStart1      = null;
-        $this->elementStart2      = null;
-        $this->elementEnd1        = null;
-        $this->elementEnd2        = $this->nl;
-        $this->intAttrDelimiter   = '<!-- -->';
-        $this->attributeDelimiter = ';';
-        $this->valueInit          = ':';
-        break;
-    }
-    return $objectname;
-  }
-/**
- * creates formatted output for calendar component property
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @param string $label property name
- * @param string $attributes property attributes
- * @param string $content property content (optional)
- * @return string
- */
-  function _createElement( $label, $attributes=null, $content=FALSE ) {
-    switch( $this->format ) {
-      case 'xcal':
-        $label = strtolower( $label );
-        break;
-      default:
-        $label = strtoupper( $label );
-        break;
-    }
-    $output = $this->elementStart1.$label;
-    $categoriesAttrLang = null;
-    $attachInlineBinary = FALSE;
-    $attachfmttype      = null;
-    if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
-      $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
-                               , 'ref'      => $label
-                               , 'type2'    => '(#PCDATA)' );
-    }
-    if( !empty( $attributes ))  {
-      $attributes  = trim( $attributes );
-      if ( 'xcal' == $this->format ) {
-        $attributes2 = explode( $this->intAttrDelimiter, $attributes );
-        $attributes  = null;
-        foreach( $attributes2 as $aix => $attribute ) {
-          $attrKVarr = explode( '=', $attribute );
-          if( empty( $attrKVarr[0] ))
-            continue;
-          if( !isset( $attrKVarr[1] )) {
-            $attrValue = $attrKVarr[0];
-            $attrKey   = $aix;
-          }
-          elseif( 2 == count( $attrKVarr)) {
-            $attrKey   = strtolower( $attrKVarr[0] );
-            $attrValue = $attrKVarr[1];
-          }
-          else {
-            $attrKey   = strtolower( $attrKVarr[0] );
-            unset( $attrKVarr[0] );
-            $attrValue = implode( '=', $attrKVarr );
-          }
-          if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
-            $attachInlineBinary = TRUE;
-            if( 'fmttype' == $attrKey )
-              $attachfmttype = $attrKey.'='.$attrValue;
-            continue;
-          }
-          elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
-            $categoriesAttrLang = $attrKey.'='.$attrValue;
-          else {
-            $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
-            $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
-            if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
-              $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
-              $attrValue = str_replace( '"', '', $attrValue );
-            }
-            $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
-          }
-        }
-      }
-      else {
-        $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
-      }
-    }
-    if(( 'xcal' == $this->format) &&
-       ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
-      $pos = strrpos($content, "/");
-      $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
-      $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
-                               , 'uri'      => $docname
-                               , 'ref'      => 'SYSTEM'
-                               , 'external' => $content
-                               , 'type'     => 'NDATA'
-                               , 'type2'    => 'BINERY' );
-      $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
-      $attributes .= 'uri="'.$docname.'"';
-      $content = null;
-      if( 'attach' == $label ) {
-        $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
-        $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
-        $attributes = null;
-      }
-    }
-    elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
-      $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
-    }
-    $output .= $attributes;
-    if( !$content && ( '0' != $content )) {
-      switch( $this->format ) {
-        case 'xcal':
-          $output .= ' /';
-          $output .= $this->elementStart2.$this->nl;
-          return $output;
-          break;
-        default:
-          $output .= $this->elementStart2.$this->valueInit;
-          return $this->_size75( $output );
-          break;
-      }
-    }
-    $output .= $this->elementStart2;
-    $output .= $this->valueInit.$content;
-    switch( $this->format ) {
-      case 'xcal':
-        return $output.$this->elementEnd1.$label.$this->elementEnd2;
-        break;
-      default:
-        return $this->_size75( $output );
-        break;
-    }
-  }
-/**
- * creates formatted output for calendar component property parameters
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.27 - 2012-01-16
- * @param array $params  optional
- * @param array $ctrKeys optional
- * @return string
- */
-  function _createParams( $params=array(), $ctrKeys=array() ) {
-    if( !is_array( $params ) || empty( $params ))
-      $params = array();
-    $attrLANG = $attr1 = $attr2 = $lang = null;
-    $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
-    $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
-    $CNattrExist = $LANGattrExist = FALSE;
-    $xparams = array();
-    foreach( $params as $paramKey => $paramValue ) {
-      if(( FALSE !== strpos( $paramValue, ':' )) ||
-         ( FALSE !== strpos( $paramValue, ';' )) ||
-         ( FALSE !== strpos( $paramValue, ',' )))
-        $paramValue = '"'.$paramValue.'"';
-      if( ctype_digit( (string) $paramKey )) {
-        $xparams[]          = $paramValue;
-        continue;
-      }
-      $paramKey             = strtoupper( $paramKey );
-      if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
-        $xparams[$paramKey] = $paramValue;
-      else
-        $params[$paramKey]  = $paramValue;
-    }
-    ksort( $xparams, SORT_STRING );
-    foreach( $xparams as $paramKey => $paramValue ) {
-      if( ctype_digit( (string) $paramKey ))
-        $attr2             .= $this->intAttrDelimiter.$paramValue;
-      else
-        $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
-    }
-    if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
-      $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
-      $attr2                = null;
-    }
-    if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
-      if( !empty( $attr2 )) {
-        $attr1             .= $attr2;
-        $attr2              = null;
-      }
-      $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
-    }
-    if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
-      $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
-    if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
-      $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
-    }
-    if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
-      $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
-    if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
-      $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
-    if( isset( $params['CN'] )       && $CNattrKey ) {
-      $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
-      $CNattrExist          = TRUE;
-    }
-    if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
-      $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
-      $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
-    }
-    if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
-      $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
-    if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
-      $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
-      $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
-    }
-    if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
-      $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
-      $LANGattrExist        = TRUE;
-    }
-    if( !$LANGattrExist ) {
-      $lang = $this->getConfig( 'language' );
-      if(( $CNattrExist || $LANGattrKey ) && $lang )
-        $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
-    }
-    return $attr1.$attrLANG.$attr2;
-  }
-/**
- * creates formatted output for calendar component property data value type recur
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-22
- * @param array $recurlabel
- * @param array $recurdata
- * @return string
- */
-  function _format_recur( $recurlabel, $recurdata ) {
-    $output = null;
-    foreach( $recurdata as $therule ) {
-      if( empty( $therule['value'] )) {
-        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
-        continue;
-      }
-      $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
-      $content1  = $content2  = null;
-      foreach( $therule['value'] as $rulelabel => $rulevalue ) {
-        switch( $rulelabel ) {
-          case 'FREQ': {
-            $content1 .= "FREQ=$rulevalue";
-            break;
-          }
-          case 'UNTIL': {
-            $content2 .= ";UNTIL=";
-            $content2 .= iCalUtilityFunctions::_format_date_time( $rulevalue );
-            break;
-          }
-          case 'COUNT':
-          case 'INTERVAL':
-          case 'WKST': {
-            $content2 .= ";$rulelabel=$rulevalue";
-            break;
-          }
-          case 'BYSECOND':
-          case 'BYMINUTE':
-          case 'BYHOUR':
-          case 'BYMONTHDAY':
-          case 'BYYEARDAY':
-          case 'BYWEEKNO':
-          case 'BYMONTH':
-          case 'BYSETPOS': {
-            $content2 .= ";$rulelabel=";
-            if( is_array( $rulevalue )) {
-              foreach( $rulevalue as $vix => $valuePart ) {
-                $content2 .= ( $vix ) ? ',' : null;
-                $content2 .= $valuePart;
-              }
-            }
-            else
-             $content2 .= $rulevalue;
-            break;
-          }
-          case 'BYDAY': {
-            $content2 .= ";$rulelabel=";
-            $bydaycnt = 0;
-            foreach( $rulevalue as $vix => $valuePart ) {
-              $content21 = $content22 = null;
-              if( is_array( $valuePart )) {
-                $content2 .= ( $bydaycnt ) ? ',' : null;
-                foreach( $valuePart as $vix2 => $valuePart2 ) {
-                  if( 'DAY' != strtoupper( $vix2 ))
-                      $content21 .= $valuePart2;
-                  else
-                    $content22 .= $valuePart2;
-                }
-                $content2 .= $content21.$content22;
-                $bydaycnt++;
-              }
-              else {
-                $content2 .= ( $bydaycnt ) ? ',' : null;
-                if( 'DAY' != strtoupper( $vix ))
-                    $content21 .= $valuePart;
-                else {
-                  $content22 .= $valuePart;
-                  $bydaycnt++;
-                }
-                $content2 .= $content21.$content22;
-              }
-            }
-            break;
-          }
-          default: {
-            $content2 .= ";$rulelabel=$rulevalue";
-            break;
-          }
-        }
-      }
-      $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
-    }
-    return $output;
-  }
-/**
- * check if property not exists within component
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-15
- * @param string $propName
- * @return bool
- */
-  function _notExistProp( $propName ) {
-    if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
-    $propName = strtolower( $propName );
-    if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
-    elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
-    elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
-    elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
-    elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
-    elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
-    return FALSE;
-  }
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * get general component config variables or info about subcomponents
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.6 - 2011-05-14
- * @param mixed $config
- * @return value
- */
-  function getConfig( $config = FALSE) {
-    if( !$config ) {
-      $return = array();
-      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
-      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
-      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
-        $return['LANGUAGE']  = $lang;
-      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
-      $return['TZTD']        = $this->getConfig( 'TZID' );
-      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
-      return $return;
-    }
-    switch( strtoupper( $config )) {
-      case 'ALLOWEMPTY':
-        return $this->allowEmpty;
-        break;
-      case 'COMPSINFO':
-        unset( $this->compix );
-        $info = array();
-        if( isset( $this->components )) {
-          foreach( $this->components as $cix => $component ) {
-            if( empty( $component )) continue;
-            $info[$cix]['ordno'] = $cix + 1;
-            $info[$cix]['type']  = $component->objName;
-            $info[$cix]['uid']   = $component->getProperty( 'uid' );
-            $info[$cix]['props'] = $component->getConfig( 'propinfo' );
-            $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
-          }
-        }
-        return $info;
-        break;
-      case 'FORMAT':
-        return $this->format;
-        break;
-      case 'LANGUAGE':
-         // get language for calendar component as defined in [RFC 1766]
-        return $this->language;
-        break;
-      case 'NL':
-      case 'NEWLINECHAR':
-        return $this->nl;
-        break;
-      case 'PROPINFO':
-        $output = array();
-        if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
-          if( empty( $this->uid['value'] )) $this->_makeuid();
-                                              $output['UID']              = 1;
-        }
-        if( !empty( $this->dtstamp ))         $output['DTSTAMP']          = 1;
-        if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
-        if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
-        if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
-        if( !empty( $this->dtend ))           $output['DTEND']            = 1;
-        if( !empty( $this->due ))             $output['DUE']              = 1;
-        if( !empty( $this->duration ))        $output['DURATION']         = 1;
-        if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
-        if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
-        if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
-        if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
-        if( !empty( $this->action ))          $output['ACTION']           = 1;
-        if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
-        if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
-        if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
-        if( !empty( $this->class ))           $output['CLASS']            = 1;
-        if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
-        if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
-        if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
-        if( !empty( $this->created ))         $output['CREATED']          = 1;
-        if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
-        if( !empty( $this->geo ))             $output['GEO']              = 1;
-        if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
-        if( !empty( $this->location ))        $output['LOCATION']         = 1;
-        if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
-        if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
-        if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
-        if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
-        if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
-        if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
-        if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
-        if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
-        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
-        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
-        if( !empty( $this->status ))          $output['STATUS']           = 1;
-        if( !empty( $this->transp ))          $output['TRANSP']           = 1;
-        if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
-        if( !empty( $this->tzid ))            $output['TZID']             = 1;
-        if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
-        if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
-        if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
-        if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
-        if( !empty( $this->url ))             $output['URL']              = 1;
-        if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
-        return $output;
-        break;
-      case 'TZID':
-        return $this->dtzid;
-        break;
-      case 'UNIQUE_ID':
-        if( empty( $this->unique_id ))
-          $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
-        return $this->unique_id;
-        break;
-    }
-  }
-/**
- * general component config setting
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.18 - 2011-10-28
- * @param mixed  $config
- * @param string $value
- * @param bool   $softUpdate
- * @return void
- */
-  function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
-    if( is_array( $config )) {
-      $ak = array_keys( $config );
-      foreach( $ak as $k ) {
-        if( 'NEWLINECHAR' == strtoupper( $k )) {
-          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
-            return FALSE;
-          unset( $config[$k] );
-          break;
-        }
-      }
-      foreach( $config as $cKey => $cValue ) {
-        if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
-          return FALSE;
-      }
-      return TRUE;
-    }
-    $res = FALSE;
-    switch( strtoupper( $config )) {
-      case 'ALLOWEMPTY':
-        $this->allowEmpty = $value;
-        $subcfg = array( 'ALLOWEMPTY' => $value );
-        $res    = TRUE;
-        break;
-      case 'FORMAT':
-        $value  = trim( strtolower( $value ));
-        $this->format = $value;
-        $this->_createFormat();
-        $subcfg = array( 'FORMAT' => $value );
-        $res    = TRUE;
-        break;
-      case 'LANGUAGE':
-         // set language for calendar component as defined in [RFC 1766]
-        $value  = trim( $value );
-        if( empty( $this->language ) || !$softUpdate )
-          $this->language = $value;
-        $subcfg = array( 'LANGUAGE' => $value );
-        $res    = TRUE;
-        break;
-      case 'NL':
-      case 'NEWLINECHAR':
-        $this->nl = $value;
-        $this->_createFormat();
-        $subcfg = array( 'NL' => $value );
-        $res    = TRUE;
-        break;
-      case 'TZID':
-        $this->dtzid = $value;
-        $subcfg = array( 'TZID' => $value );
-        $res    = TRUE;
-        break;
-      case 'UNIQUE_ID':
-        $value  = trim( $value );
-        $this->unique_id = $value;
-        $subcfg = array( 'UNIQUE_ID' => $value );
-        $res    = TRUE;
-        break;
-      default:  // any unvalid config key.. .
-        return TRUE;
-    }
-    if( !$res ) return FALSE;
-    if( isset( $subcfg ) && !empty( $this->components )) {
-      foreach( $subcfg as $cfgkey => $cfgvalue ) {
-        foreach( $this->components as $cix => $component ) {
-          $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
-          if( !$res )
-            break 2;
-          $this->components[$cix] = $component->copy(); // PHP4 compliant
-        }
-      }
-    }
-    return $res;
-  }
-/*********************************************************************************/
-/**
- * delete component property value
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param mixed $propName, bool FALSE => X-property
- * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
- * @return bool, if successfull delete TRUE
- */
-  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
-    if( $this->_notExistProp( $propName )) return FALSE;
-    $propName = strtoupper( $propName );
-    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
-                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
-      if( !$propix )
-        $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
-      $this->propdelix[$propName] = --$propix;
-    }
-    $return = FALSE;
-    switch( $propName ) {
-      case 'ACTION':
-        if( !empty( $this->action )) {
-          $this->action = '';
-          $return = TRUE;
-        }
-        break;
-      case 'ATTACH':
-        return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
-        break;
-      case 'ATTENDEE':
-        return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
-        break;
-      case 'CATEGORIES':
-        return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
-        break;
-      case 'CLASS':
-        if( !empty( $this->class )) {
-          $this->class = '';
-          $return = TRUE;
-        }
-        break;
-      case 'COMMENT':
-        return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
-        break;
-      case 'COMPLETED':
-        if( !empty( $this->completed )) {
-          $this->completed = '';
-          $return = TRUE;
-        }
-        break;
-      case 'CONTACT':
-        return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
-        break;
-      case 'CREATED':
-        if( !empty( $this->created )) {
-          $this->created = '';
-          $return = TRUE;
-        }
-        break;
-      case 'DESCRIPTION':
-        return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
-        break;
-      case 'DTEND':
-        if( !empty( $this->dtend )) {
-          $this->dtend = '';
-          $return = TRUE;
-        }
-        break;
-      case 'DTSTAMP':
-        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
-          return FALSE;
-        if( !empty( $this->dtstamp )) {
-          $this->dtstamp = '';
-          $return = TRUE;
-        }
-        break;
-      case 'DTSTART':
-        if( !empty( $this->dtstart )) {
-          $this->dtstart = '';
-          $return = TRUE;
-        }
-        break;
-      case 'DUE':
-        if( !empty( $this->due )) {
-          $this->due = '';
-          $return = TRUE;
-        }
-        break;
-      case 'DURATION':
-        if( !empty( $this->duration )) {
-          $this->duration = '';
-          $return = TRUE;
-        }
-        break;
-      case 'EXDATE':
-        return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
-        break;
-      case 'EXRULE':
-        return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
-        break;
-      case 'FREEBUSY':
-        return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
-        break;
-      case 'GEO':
-        if( !empty( $this->geo )) {
-          $this->geo = '';
-          $return = TRUE;
-        }
-        break;
-      case 'LAST-MODIFIED':
-        if( !empty( $this->lastmodified )) {
-          $this->lastmodified = '';
-          $return = TRUE;
-        }
-        break;
-      case 'LOCATION':
-        if( !empty( $this->location )) {
-          $this->location = '';
-          $return = TRUE;
-        }
-        break;
-      case 'ORGANIZER':
-        if( !empty( $this->organizer )) {
-          $this->organizer = '';
-          $return = TRUE;
-        }
-        break;
-      case 'PERCENT-COMPLETE':
-        if( !empty( $this->percentcomplete )) {
-          $this->percentcomplete = '';
-          $return = TRUE;
-        }
-        break;
-      case 'PRIORITY':
-        if( !empty( $this->priority )) {
-          $this->priority = '';
-          $return = TRUE;
-        }
-        break;
-      case 'RDATE':
-        return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
-        break;
-      case 'RECURRENCE-ID':
-        if( !empty( $this->recurrenceid )) {
-          $this->recurrenceid = '';
-          $return = TRUE;
-        }
-        break;
-      case 'RELATED-TO':
-        return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
-        break;
-      case 'REPEAT':
-        if( !empty( $this->repeat )) {
-          $this->repeat = '';
-          $return = TRUE;
-        }
-        break;
-      case 'REQUEST-STATUS':
-        return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
-        break;
-      case 'RESOURCES':
-        return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
-        break;
-      case 'RRULE':
-        return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
-        break;
-      case 'SEQUENCE':
-        if( !empty( $this->sequence )) {
-          $this->sequence = '';
-          $return = TRUE;
-        }
-        break;
-      case 'STATUS':
-        if( !empty( $this->status )) {
-          $this->status = '';
-          $return = TRUE;
-        }
-        break;
-      case 'SUMMARY':
-        if( !empty( $this->summary )) {
-          $this->summary = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TRANSP':
-        if( !empty( $this->transp )) {
-          $this->transp = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TRIGGER':
-        if( !empty( $this->trigger )) {
-          $this->trigger = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TZID':
-        if( !empty( $this->tzid )) {
-          $this->tzid = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TZNAME':
-        return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
-        break;
-      case 'TZOFFSETFROM':
-        if( !empty( $this->tzoffsetfrom )) {
-          $this->tzoffsetfrom = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TZOFFSETTO':
-        if( !empty( $this->tzoffsetto )) {
-          $this->tzoffsetto = '';
-          $return = TRUE;
-        }
-        break;
-      case 'TZURL':
-        if( !empty( $this->tzurl )) {
-          $this->tzurl = '';
-          $return = TRUE;
-        }
-        break;
-      case 'UID':
-        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
-          return FALSE;
-        if( !empty( $this->uid )) {
-          $this->uid = '';
-          $return = TRUE;
-        }
-        break;
-      case 'URL':
-        if( !empty( $this->url )) {
-          $this->url = '';
-          $return = TRUE;
-        }
-        break;
-      default:
-        $reduced = '';
-        if( $propName != 'X-PROP' ) {
-          if( !isset( $this->xprop[$propName] )) return FALSE;
-          foreach( $this->xprop as $k => $a ) {
-            if(( $k != $propName ) && !empty( $a ))
-              $reduced[$k] = $a;
-          }
-        }
-        else {
-          if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
-          $xpropno = 0;
-          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
-            if( $propix != $xpropno )
-              $reduced[$xpropkey] = $xpropvalue;
-            $xpropno++;
-          }
-        }
-        $this->xprop = $reduced;
-        if( empty( $this->xprop )) {
-          unset( $this->propdelix[$propName] );
-          return FALSE;
-        }
-        return TRUE;
-    }
-    return $return;
-  }
-/*********************************************************************************/
-/**
- * delete component property value, fixing components with multiple occurencies
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param array $multiprop, reference to a component property
- * @param int   $propix, reference to removal counter
- * @return bool TRUE
- */
-  function deletePropertyM( & $multiprop, & $propix ) {
-    if( isset( $multiprop[$propix] ))
-      unset( $multiprop[$propix] );
-    if( empty( $multiprop )) {
-      $multiprop = '';
-      unset( $propix );
-      return FALSE;
-    }
-    else
-      return TRUE;
-  }
-/**
- * get component property value/params
- *
- * if property has multiply values, consequtive function calls are needed
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.3 - 2012-01-10
- * @param string $propName, optional
- * @param int @propix, optional, if specific property is wanted in case of multiply occurences
- * @param bool $inclParam=FALSE
- * @param bool $specform=FALSE
- * @return mixed
- */
-  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
-    if( $this->_notExistProp( $propName )) return FALSE;
-    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
-    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
-                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
-      if( !$propix )
-        $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
-      $this->propix[$propName] = --$propix;
-    }
-    switch( $propName ) {
-      case 'ACTION':
-        if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
-        break;
-      case 'ATTACH':
-        $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
-        while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
-        break;
-      case 'ATTENDEE':
-        $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
-        while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
-        break;
-      case 'CATEGORIES':
-        $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
-        while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
-        break;
-      case 'CLASS':
-        if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
-        break;
-      case 'COMMENT':
-        $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
-        while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
-        break;
-      case 'COMPLETED':
-        if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
-        break;
-      case 'CONTACT':
-        $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
-        while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
-        break;
-      case 'CREATED':
-        if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
-        break;
-      case 'DESCRIPTION':
-        $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
-        while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
-        break;
-      case 'DTEND':
-        if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
-        break;
-      case 'DTSTAMP':
-        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
-          return;
-        if( !isset( $this->dtstamp['value'] ))
-          $this->_makeDtstamp();
-        return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
-        break;
-      case 'DTSTART':
-        if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
-        break;
-      case 'DUE':
-        if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
-        break;
-      case 'DURATION':
-        if( !isset( $this->duration['value'] )) return FALSE;
-        $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
-        return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
-        break;
-      case 'EXDATE':
-        $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
-        while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
-        break;
-      case 'EXRULE':
-        $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
-        while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
-        break;
-      case 'FREEBUSY':
-        $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
-        while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
-        break;
-      case 'GEO':
-        if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
-        break;
-      case 'LAST-MODIFIED':
-        if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
-        break;
-      case 'LOCATION':
-        if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
-        break;
-      case 'ORGANIZER':
-        if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
-        break;
-      case 'PERCENT-COMPLETE':
-        if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
-        break;
-      case 'PRIORITY':
-        if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
-        break;
-      case 'RDATE':
-        $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
-        while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
-        break;
-      case 'RECURRENCE-ID':
-        if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
-        break;
-      case 'RELATED-TO':
-        $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
-        while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
-        break;
-      case 'REPEAT':
-        if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
-        break;
-      case 'REQUEST-STATUS':
-        $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
-        while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
-        break;
-      case 'RESOURCES':
-        $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
-        while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
-        break;
-      case 'RRULE':
-        $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
-        while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
-        break;
-      case 'SEQUENCE':
-        if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
-        break;
-      case 'STATUS':
-        if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
-        break;
-      case 'SUMMARY':
-        if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
-        break;
-      case 'TRANSP':
-        if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
-        break;
-      case 'TRIGGER':
-        if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
-        break;
-      case 'TZID':
-        if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
-        break;
-      case 'TZNAME':
-        $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
-        while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
-          $propix++;
-        $this->propix[$propName] = $propix;
-        if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
-        return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
-        break;
-      case 'TZOFFSETFROM':
-        if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
-        break;
-      case 'TZOFFSETTO':
-        if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
-        break;
-      case 'TZURL':
-        if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
-        break;
-      case 'UID':
-        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
-          return FALSE;
-        if( empty( $this->uid['value'] ))
-          $this->_makeuid();
-        return ( $inclParam ) ? $this->uid : $this->uid['value'];
-        break;
-      case 'URL':
-        if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
-        break;
-      default:
-        if( $propName != 'X-PROP' ) {
-          if( !isset( $this->xprop[$propName] )) return FALSE;
-          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
-                                : array( $propName, $this->xprop[$propName]['value'] );
-        }
-        else {
-          if( empty( $this->xprop )) return FALSE;
-          $xpropno = 0;
-          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
-            if( $propix == $xpropno )
-              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
-                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
-            else
-              $xpropno++;
-          }
-          return FALSE; // not found ??
-        }
-    }
-    return FALSE;
-  }
-/**
- * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-04-13
- * @param string $propName
- * @param array  $output, incremented result array
- */
-  function _getProperties( $propName, & $output ) {
-    if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' )))
-      return output;
-    while( FALSE !== ( $content = $this->getProperty( $propName ))) {
-      if( is_array( $content )) {
-        foreach( $content as $part ) {
-          if( FALSE !== strpos( $part, ',' )) {
-            $part = explode( ',', $part );
-            foreach( $part as $thePart ) {
-              $thePart = trim( $thePart );
-              if( !empty( $thePart )) {
-                if( !isset( $output[$thePart] ))
-                  $output[$thePart] = 1;
-                else
-                  $output[$thePart] += 1;
-              }
-            }
-          }
-          else {
-            $part = trim( $part );
-            if( !isset( $output[$part] ))
-              $output[$part] = 1;
-            else
-              $output[$part] += 1;
-          }
-        }
-      }
-      elseif( FALSE !== strpos( $content, ',' )) {
-        $content = explode( ',', $content );
-        foreach( $content as $thePart ) {
-          $thePart = trim( $thePart );
-          if( !empty( $thePart )) {
-            if( !isset( $output[$thePart] ))
-              $output[$thePart] = 1;
-            else
-              $output[$thePart] += 1;
-          }
-        }
-      }
-      else {
-        $content = trim( $content );
-        if( !empty( $content )) {
-          if( !isset( $output[$content] ))
-            $output[$content] = 1;
-          else
-            $output[$content] += 1;
-        }
-      }
-    }
-    ksort( $output );
-    return $output;
-  }
-/**
- * general component property setting
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-05
- * @param mixed $args variable number of function arguments,
- *                    first argument is ALWAYS component name,
- *                    second ALWAYS component value!
- * @return void
- */
-  function setProperty() {
-    $numargs    = func_num_args();
-    if( 1 > $numargs ) return FALSE;
-    $arglist    = func_get_args();
-    if( $this->_notExistProp( $arglist[0] )) return FALSE;
-    if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
-      return FALSE;
-    $arglist[0] = strtoupper( $arglist[0] );
-    for( $argix=$numargs; $argix < 12; $argix++ ) {
-      if( !isset( $arglist[$argix] ))
-        $arglist[$argix] = null;
-    }
-    switch( $arglist[0] ) {
-      case 'ACTION':
-        return $this->setAction( $arglist[1], $arglist[2] );
-      case 'ATTACH':
-        return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
-      case 'ATTENDEE':
-        return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
-      case 'CATEGORIES':
-        return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
-      case 'CLASS':
-        return $this->setClass( $arglist[1], $arglist[2] );
-      case 'COMMENT':
-        return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
-      case 'COMPLETED':
-        return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
-      case 'CONTACT':
-        return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
-      case 'CREATED':
-        return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
-      case 'DESCRIPTION':
-        return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
-      case 'DTEND':
-        return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
-      case 'DTSTAMP':
-        return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
-      case 'DTSTART':
-        return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
-      case 'DUE':
-        return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
-      case 'DURATION':
-        return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
-      case 'EXDATE':
-        return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
-      case 'EXRULE':
-        return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
-      case 'FREEBUSY':
-        return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
-      case 'GEO':
-        return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
-      case 'LAST-MODIFIED':
-        return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
-      case 'LOCATION':
-        return $this->setLocation( $arglist[1], $arglist[2] );
-      case 'ORGANIZER':
-        return $this->setOrganizer( $arglist[1], $arglist[2] );
-      case 'PERCENT-COMPLETE':
-        return $this->setPercentComplete( $arglist[1], $arglist[2] );
-      case 'PRIORITY':
-        return $this->setPriority( $arglist[1], $arglist[2] );
-      case 'RDATE':
-        return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
-      case 'RECURRENCE-ID':
-       return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
-      case 'RELATED-TO':
-        return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
-      case 'REPEAT':
-        return $this->setRepeat( $arglist[1], $arglist[2] );
-      case 'REQUEST-STATUS':
-        return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
-      case 'RESOURCES':
-        return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
-      case 'RRULE':
-        return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
-      case 'SEQUENCE':
-        return $this->setSequence( $arglist[1], $arglist[2] );
-      case 'STATUS':
-        return $this->setStatus( $arglist[1], $arglist[2] );
-      case 'SUMMARY':
-        return $this->setSummary( $arglist[1], $arglist[2] );
-      case 'TRANSP':
-        return $this->setTransp( $arglist[1], $arglist[2] );
-      case 'TRIGGER':
-        return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
-      case 'TZID':
-        return $this->setTzid( $arglist[1], $arglist[2] );
-      case 'TZNAME':
-        return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
-      case 'TZOFFSETFROM':
-        return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
-      case 'TZOFFSETTO':
-        return $this->setTzoffsetto( $arglist[1], $arglist[2] );
-      case 'TZURL':
-        return $this->setTzurl( $arglist[1], $arglist[2] );
-      case 'UID':
-        return $this->setUid( $arglist[1], $arglist[2] );
-      case 'URL':
-        return $this->setUrl( $arglist[1], $arglist[2] );
-      default:
-        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
-    }
-    return FALSE;
-  }
-/*********************************************************************************/
-/**
- * parse component unparsed data into properties
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.17 - 2012-02-03
- * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
- * @return bool FALSE if error occurs during parsing
- *
- */
-  function parse( $unparsedtext=null ) {
-    if( !empty( $unparsedtext )) {
-      $nl = $this->getConfig( 'nl' );
-      if( is_array( $unparsedtext ))
-        $unparsedtext = implode( '\n'.$nl, $unparsedtext );
-            /* fix line folding */
-      $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
-      $EOLmark = FALSE;
-      foreach( $eolchars as $eolchar ) {
-        if( !$EOLmark  && ( FALSE !== strpos( $unparsedtext, $eolchar ))) {
-          $unparsedtext = str_replace( $eolchar." ",  '',  $unparsedtext );
-          $unparsedtext = str_replace( $eolchar."\t", '',  $unparsedtext );
-          if( $eolchar != $nl )
-            $unparsedtext = str_replace( $eolchar,    $nl, $unparsedtext );
-          $EOLmark = TRUE;
-        }
-      }
-      $tmp = explode( $nl, $unparsedtext );
-      $unparsedtext = array();
-      foreach( $tmp as $tmpr )
-        if( !empty( $tmpr ))
-          $unparsedtext[] = $tmpr;
-    }
-    elseif( !isset( $this->unparsed ))
-      $unparsedtext = array();
-    else
-      $unparsedtext = $this->unparsed;
-    $this->unparsed = array();
-    $comp = & $this;
-    $config = $this->getConfig();
-    foreach ( $unparsedtext as $line ) {
-      if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' )))
-        $this->components[] = $comp->copy();
-      elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 )))
-        array_unshift( $this->components, $comp->copy());
-      elseif( 'END:' == strtoupper( substr( $line, 0, 4 )))
-        break;
-      elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 )))
-        $comp = new valarm( $config);
-      elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 )))
-        $comp = new vtimezone( 'standard', $config );
-      elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 )))
-        $comp = new vtimezone( 'daylight', $config );
-      elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))
-        continue;
-      else
-        $comp->unparsed[] = $line;
-    }
-    unset( $config );
-            /* concatenate property values spread over several lines */
-    $lastix    = -1;
-    $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
-                      , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
-                      , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
-                      , 'last-modified', 'location', 'organizer', 'percent-complete'
-                      , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
-                      , 'request-status', 'resources', 'rrule', 'sequence', 'status'
-                      , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
-                      , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
-    $proprows  = array();
-    foreach( $this->unparsed as $line ) {
-      $newProp = FALSE;
-      foreach ( $propnames as $propname ) {
-        if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
-          $newProp = TRUE;
-          break;
-        }
-      }
-      if( $newProp ) {
-        $newProp = FALSE;
-        $lastix++;
-        $proprows[$lastix]  = $line;
-      }
-      else
-        $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
-    }
-            /* parse each property 'line' */
-    $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
-    $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
-    $paramProto4 = array( 'crid:', 'news:', 'pres:' );
-    foreach( $proprows as $line ) {
-      $line = str_replace( '!"#¤%&/()=? ', '', $line );
-      $line = str_replace( '!"#¤%&/()=?', '', $line );
-      if( '\n' == substr( $line, -2 ))
-        $line = substr( $line, 0, strlen( $line ) - 2 );
-            /* get propname, (problem with x-properties, otherwise in previous loop) */
-      $cix = $propname = null;
-      for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
-        if( in_array( $line[$cix], array( ':', ';' )))
-          break;
-        else
-          $propname .= $line[$cix];
-      }
-      if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
-        $propname2 = $propname;
-        $propname  = 'X-';
-      }
-            /* rest of the line is opt.params and value */
-      $line = substr( $line, $cix );
-            /* separate attributes from value */
-      $attr         = array();
-      $attrix       = -1;
-      $clen         = strlen( $line );
-      $WithinQuotes = FALSE;
-      for( $cix=0; $cix < $clen; $cix++ ) {
-        if(                       (  ':' == $line[$cix] )                         &&
-                                  ( substr( $line,$cix,     3 )  != '://' )       &&
-           ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
-           ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
-           ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
-                      ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
-             !$WithinQuotes ) {
-          $attrEnd = TRUE;
-          if(( $cix < ( $clen - 4 )) &&
-               ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
-            for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
-              if( '://' == substr( $line, $c2ix - 2, 3 )) {
-                $attrEnd = FALSE;
-                break; // an URI with a portnr!!
-              }
-            }
-          }
-          if( $attrEnd) {
-            $line = substr( $line, ( $cix + 1 ));
-            break;
-          }
-        }
-        if( '"' == $line[$cix] )
-          $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
-        if( ';' == $line[$cix] )
-          $attr[++$attrix] = null;
-        else
-          $attr[$attrix] .= $line[$cix];
-      }
-            /* make attributes in array format */
-      $propattr = array();
-      foreach( $attr as $attribute ) {
-        $attrsplit = explode( '=', $attribute, 2 );
-        if( 1 < count( $attrsplit ))
-          $propattr[$attrsplit[0]] = $attrsplit[1];
-        else
-          $propattr[] = $attribute;
-      }
-            /* call setProperty( $propname.. . */
-      switch( strtoupper( $propname )) {
-        case 'ATTENDEE':
-          foreach( $propattr as $pix => $attr ) {
-            if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
-              continue;
-            $attr2 = explode( ',', $attr );
-              if( 1 < count( $attr2 ))
-                $propattr[$pix] = $attr2;
-          }
-          $this->setProperty( $propname, $line, $propattr );
-          break;
-        case 'X-':
-          $propname = ( isset( $propname2 )) ? $propname2 : $propname;
-          unset( $propname2 );
-        case 'CATEGORIES':
-        case 'RESOURCES':
-          if( FALSE !== strpos( $line, ',' )) {
-            $llen     = strlen( $line );
-            $content  = array( 0 => '' );
-            $cix      = 0;
-            for( $lix = 0; $lix < $llen; $lix++ ) {
-              if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
-                $cix++;
-                $content[$cix] = '';
-              }
-              else
-                $content[$cix] .= $line[$lix];
-            }
-            if( 1 < count( $content )) {
-              $content = array_values( $content );
-              foreach( $content as $cix => $contentPart )
-                $content[$cix] = calendarComponent::_strunrep( $contentPart );
-              $this->setProperty( $propname, $content, $propattr );
-              break;
-            }
-            else
-              $line = reset( $content );
-          }
-        case 'COMMENT':
-        case 'CONTACT':
-        case 'DESCRIPTION':
-        case 'LOCATION':
-        case 'SUMMARY':
-          if( empty( $line ))
-            $propattr = null;
-          $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr );
-          break;
-        case 'REQUEST-STATUS':
-          $values    = explode( ';', $line, 3 );
-          $values[1] = ( !isset( $values[1] )) ? null : calendarComponent::_strunrep( $values[1] );
-          $values[2] = ( !isset( $values[2] )) ? null : calendarComponent::_strunrep( $values[2] );
-          $this->setProperty( $propname
-                            , $values[0]  // statcode
-                            , $values[1]  // statdesc
-                            , $values[2]  // extdata
-                            , $propattr );
-          break;
-        case 'FREEBUSY':
-          $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
-          unset( $propattr['FBTYPE'] );
-          $values = explode( ',', $line );
-          foreach( $values as $vix => $value ) {
-            $value2 = explode( '/', $value );
-            if( 1 < count( $value2 ))
-              $values[$vix] = $value2;
-          }
-          $this->setProperty( $propname, $fbtype, $values, $propattr );
-          break;
-        case 'GEO':
-          $value = explode( ';', $line, 2 );
-          if( 2 > count( $value ))
-            $value[1] = null;
-          $this->setProperty( $propname, $value[0], $value[1], $propattr );
-          break;
-        case 'EXDATE':
-          $values = ( !empty( $line )) ? explode( ',', $line ) : null;
-          $this->setProperty( $propname, $values, $propattr );
-          break;
-        case 'RDATE':
-          if( empty( $line )) {
-            $this->setProperty( $propname, $line, $propattr );
-            break;
-          }
-          $values = explode( ',', $line );
-          foreach( $values as $vix => $value ) {
-            $value2 = explode( '/', $value );
-            if( 1 < count( $value2 ))
-              $values[$vix] = $value2;
-          }
-          $this->setProperty( $propname, $values, $propattr );
-          break;
-        case 'EXRULE':
-        case 'RRULE':
-          $values = explode( ';', $line );
-          $recur = array();
-          foreach( $values as $value2 ) {
-            if( empty( $value2 ))
-              continue; // ;-char in ending position ???
-            $value3 = explode( '=', $value2, 2 );
-            $rulelabel = strtoupper( $value3[0] );
-            switch( $rulelabel ) {
-              case 'BYDAY': {
-                $value4 = explode( ',', $value3[1] );
-                if( 1 < count( $value4 )) {
-                  foreach( $value4 as $v5ix => $value5 ) {
-                    $value6 = array();
-                    $dayno = $dayname = null;
-                    $value5 = trim( (string) $value5 );
-                    if(( ctype_alpha( substr( $value5, -1 ))) &&
-                       ( ctype_alpha( substr( $value5, -2, 1 )))) {
-                      $dayname = substr( $value5, -2, 2 );
-                      if( 2 < strlen( $value5 ))
-                        $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
-                    }
-                    if( $dayno )
-                      $value6[] = $dayno;
-                    if( $dayname )
-                      $value6['DAY'] = $dayname;
-                    $value4[$v5ix] = $value6;
-                  }
-                }
-                else {
-                  $value4 = array();
-                  $dayno  = $dayname = null;
-                  $value5 = trim( (string) $value3[1] );
-                  if(( ctype_alpha( substr( $value5, -1 ))) &&
-                     ( ctype_alpha( substr( $value5, -2, 1 )))) {
-                      $dayname = substr( $value5, -2, 2 );
-                    if( 2 < strlen( $value5 ))
-                      $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
-                  }
-                  if( $dayno )
-                    $value4[] = $dayno;
-                  if( $dayname )
-                    $value4['DAY'] = $dayname;
-                }
-                $recur[$rulelabel] = $value4;
-                break;
-              }
-              default: {
-                $value4 = explode( ',', $value3[1] );
-                if( 1 < count( $value4 ))
-                  $value3[1] = $value4;
-                $recur[$rulelabel] = $value3[1];
-                break;
-              }
-            } // end - switch $rulelabel
-          } // end - foreach( $values.. .
-          $this->setProperty( $propname, $recur, $propattr );
-          break;
-        case 'ACTION':
-        case 'CLASSIFICATION':
-        case 'STATUS':
-        case 'TRANSP':
-        case 'UID':
-        case 'TZID':
-        case 'RELATED-TO':
-        case 'TZNAME':
-          $line = calendarComponent::_strunrep( $line );
-        default:
-          $this->setProperty( $propname, $line, $propattr );
-          break;
-      } // end  switch( $propname.. .
-    } // end - foreach( $proprows.. .
-    unset( $unparsedtext, $this->unparsed, $proprows );
-    if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
-      $ckeys = array_keys( $this->components );
-      foreach( $ckeys as $ckey ) {
-        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
-          $this->components[$ckey]->parse();
-        }
-      }
-    }
-    return TRUE;
-  }
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * return a copy of this component
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @return object
- */
-  function copy() {
-    $serialized_contents = serialize( $this );
-    $copy = unserialize( $serialized_contents );
-    return $copy;
- }
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * delete calendar subcomponent from component container
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param mixed $arg1 ordno / component type / component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return void
- */
-  function deleteComponent( $arg1, $arg2=FALSE  ) {
-    if( !isset( $this->components )) return FALSE;
-    $argType = $index = null;
-    if ( ctype_digit( (string) $arg1 )) {
-      $argType = 'INDEX';
-      $index   = (int) $arg1 - 1;
-    }
-    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
-      $argType = strtolower( $arg1 );
-      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
-    }
-    $cix2dC = 0;
-    foreach ( $this->components as $cix => $component) {
-      if( empty( $component )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
-        unset( $this->components[$cix] );
-        return TRUE;
-      }
-      elseif( $argType == $component->objName ) {
-        if( $index == $cix2dC ) {
-          unset( $this->components[$cix] );
-          return TRUE;
-        }
-        $cix2dC++;
-      }
-      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
-        unset( $this->components[$cix] );
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-/**
- * get calendar component subcomponent from component container
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param mixed $arg1 optional, ordno/component type/ component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return object
- */
-  function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
-    if( !isset( $this->components )) return FALSE;
-    $index = $argType = null;
-    if ( !$arg1 ) {
-      $argType = 'INDEX';
-      $index   = $this->compix['INDEX'] =
-        ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
-    }
-    elseif ( ctype_digit( (string) $arg1 )) {
-      $argType = 'INDEX';
-      $index   = (int) $arg1;
-      unset( $this->compix );
-    }
-    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
-      unset( $this->compix['INDEX'] );
-      $argType = strtolower( $arg1 );
-      if( !$arg2 )
-        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
-      else
-        $index = (int) $arg2;
-    }
-    $index  -= 1;
-    $ckeys = array_keys( $this->components );
-    if( !empty( $index) && ( $index > end( $ckeys )))
-      return FALSE;
-    $cix2gC = 0;
-    foreach( $this->components as $cix => $component ) {
-      if( empty( $component )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix ))
-        return $component->copy();
-      elseif( $argType == $component->objName ) {
-         if( $index == $cix2gC )
-           return $component->copy();
-         $cix2gC++;
-      }
-      elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
-        return $component->copy();
-    }
-            /* not found.. . */
-    unset( $this->compix );
-    return false;
-  }
-/**
- * add calendar component as subcomponent to container for subcomponents
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 1.x.x - 2007-04-24
- * @param object $component calendar component
- * @return void
- */
-  function addSubComponent ( $component ) {
-    $this->setComponent( $component );
-  }
-/**
- * create new calendar component subcomponent, already included within component
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.33 - 2011-01-03
- * @param string $compType subcomponent type
- * @return object (reference)
- */
-  function & newComponent( $compType ) {
-    $config = $this->getConfig();
-    $keys   = array_keys( $this->components );
-    $ix     = end( $keys) + 1;
-    switch( strtoupper( $compType )) {
-      case 'ALARM':
-      case 'VALARM':
-        $this->components[$ix] = new valarm( $config );
-        break;
-      case 'STANDARD':
-        array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
-        $ix = 0;
-        break;
-      case 'DAYLIGHT':
-        $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
-        break;
-      default:
-        return FALSE;
-    }
-    return $this->components[$ix];
-  }
-/**
- * add calendar component as subcomponent to container for subcomponents
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.8 - 2011-03-15
- * @param object $component calendar component
- * @param mixed $arg1 optional, ordno/component type/ component uid
- * @param mixed $arg2 optional, ordno if arg1 = component type
- * @return bool
- */
-  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
-    if( !isset( $this->components )) return FALSE;
-    $component->setConfig( $this->getConfig(), FALSE, TRUE );
-    if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
-            /* make sure dtstamp and uid is set */
-      $dummy = $component->getProperty( 'dtstamp' );
-      $dummy = $component->getProperty( 'uid' );
-    }
-    if( !$arg1 ) { // plain insert, last in chain
-      $this->components[] = $component->copy();
-      return TRUE;
-    }
-    $argType = $index = null;
-    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
-      $argType = 'INDEX';
-      $index   = (int) $arg1 - 1;
-    }
-    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
-      $argType = strtolower( $arg1 );
-      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
-    }
-    // else if arg1 is set, arg1 must be an UID
-    $cix2sC = 0;
-    foreach ( $this->components as $cix => $component2 ) {
-      if( empty( $component2 )) continue;
-      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
-        $this->components[$cix] = $component->copy();
-        return TRUE;
-      }
-      elseif( $argType == $component2->objName ) { // component Type index insert/replace
-        if( $index == $cix2sC ) {
-          $this->components[$cix] = $component->copy();
-          return TRUE;
-        }
-        $cix2sC++;
-      }
-      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
-        $this->components[$cix] = $component->copy();
-        return TRUE;
-      }
-    }
-            /* arg1=index and not found.. . insert at index .. .*/
-    if( 'INDEX' == $argType ) {
-      $this->components[$index] = $component->copy();
-      ksort( $this->components, SORT_NUMERIC );
-    }
-    else    /* not found.. . insert last in chain anyway .. .*/
-    $this->components[] = $component->copy();
-    return TRUE;
-  }
-/**
- * creates formatted output for subcomponents
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.20 - 2012-02-06
- * @param array $xcaldecl
- * @return string
- */
-  function createSubComponent() {
-    $output = null;
-    if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
-      $stdarr = $dlarr = array();
-      foreach( $this->components as $component ) {
-        if( empty( $component ))
-          continue;
-        $dt  = $component->getProperty( 'dtstart' );
-        $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
-        if( 'standard' == $component->objName ) {
-          while( isset( $stdarr[$key] ))
-            $key += 1;
-          $stdarr[$key] = $component->copy();
-        }
-        elseif( 'daylight' == $component->objName ) {
-          while( isset( $dlarr[$key] ))
-            $key += 1;
-          $dlarr[$key] = $component->copy();
-        }
-      } // end foreach( $this->components as $component )
-      $this->components = array();
-      ksort( $stdarr, SORT_NUMERIC );
-      foreach( $stdarr as $std )
-        $this->components[] = $std->copy();
-      unset( $stdarr );
-      ksort( $dlarr,  SORT_NUMERIC );
-      foreach( $dlarr as $dl )
-        $this->components[] = $dl->copy();
-      unset( $dlarr );
-    } // end if( 'vtimezone' == $this->objName )
-    foreach( $this->components as $component ) {
-      $component->setConfig( $this->getConfig(), FALSE, TRUE );
-      $output .= $component->createComponent( $this->xcaldecl );
-    }
-    return $output;
-  }
-/********************************************************************************/
-/**
- * break lines at pos 75
- *
- * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
- * break. Long content lines SHOULD be split into a multiple line
- * representations using a line "folding" technique. That is, a long
- * line can be split between any two characters by inserting a CRLF
- * immediately followed by a single linear white space character (i.e.,
- * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
- * of CRLF followed immediately by a single linear white space character
- * is ignored (i.e., removed) when processing the content type.
- *
- * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
- * the reserved expression "\n" in the arg $string could be broken up by the
- * folding of lines, causing ambiguity in the return string.
- * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be.
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.13 - 2012-02-14
- * @param string $value
- * @return string
- */
-  function _size75( $string ) {
-    $tmp        = $string;
-    $string     = '';
-    $eolcharlen = strlen( '\n' );
-            /* if PHP is config with  mb_string and conf overload.. . */
-    if( defined( 'MB_OVERLOAD_STRING' ) && ( 1 < ini_get( 'mbstring.func_overload' ))) {
-      $strlen  = mb_strlen( $tmp );
-      while( $strlen > 75 ) {
-        if( '\n' == mb_substr( $tmp, 75, $eolcharlen ))
-          $breakAtChar = 74;
-        else
-          $breakAtChar = 75;
-        $string .= mb_substr( $tmp, 0, $breakAtChar );
-        if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
-          $string .= $this->nl;
-        $tmp     = mb_substr( $tmp, $breakAtChar );
-        if( !empty( $tmp ))
-          $tmp   = ' '.$tmp;
-        $strlen  = mb_strlen( $tmp );
-      } // end while
-      if( 0 < $strlen ) {
-        $string .= $tmp; // the rest
-        if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
-          $string .= $this->nl;
-      }
-      return $string;
-    }
-            /* if PHP is not config with  mb_string.. . */
-    while( TRUE ) {
-      $bytecnt = strlen( $tmp );
-      $charCnt = $ix = 0;
-      for( $ix = 0; $ix < $bytecnt; $ix++ ) {
-        if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen )))
-          break;                                    // break before '\n'
-        elseif( 74 < $charCnt ) {
-          if( '\n' == substr( $tmp, $ix, $eolcharlen ))
-            $ix -= 1;                               // don't break inside '\n'
-          break;                                    // always break while-loop here
-        }
-        else {
-          $byte = ord( $tmp[$ix] );
-          if ($byte <= 127) {                       // add a one byte character
-            $string .= substr( $tmp, $ix, 1 );
-            $charCnt += 1;
-          }
-          else if ($byte >= 194 && $byte <= 223) {  // start byte in two byte character
-            $string .= substr( $tmp, $ix, 2 );      // add a two bytes character
-            $charCnt += 1;
-          }
-          else if ($byte >= 224 && $byte <= 239) {  // start byte in three bytes character
-            $string .= substr( $tmp, $ix, 3 );      // add a three bytes character
-            $charCnt += 1;
-          }
-          else if ($byte >= 240 && $byte <= 244) {  // start byte in four bytes character
-            $string .= substr( $tmp, $ix, 4 );      // add a four bytes character
-            $charCnt += 1;
-          }
-        }
-      } // end for
-      if( $this->nl != substr( $string, ( 0 - strlen( $this->nl ))))
-        $string .= $this->nl;
-      if( FALSE === ( $tmp = substr( $tmp, $ix )))
-        break; // while-loop breakes here
-      else
-        $tmp  = ' '.$tmp;
-    } // end while
-    if( '\n'.$this->nl == substr( $string, ( 0 - strlen( '\n'.$this->nl ))))
-      $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n'.$this->nl ))).$this->nl;
-    return $string;
-  }
-/**
- * special characters management output
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.15 - 2010-09-24
- * @param string $string
- * @return string
- */
-  function _strrep( $string ) {
-    switch( $this->format ) {
-      case 'xcal':
-        $string = str_replace( '\n',  $this->nl, $string);
-        $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
-        break;
-      default:
-        $pos = 0;
-        $specChars = array( 'n', 'N', 'r', ',', ';' );
-        while( $pos <= strlen( $string )) {
-          $pos = strpos( $string, "\\", $pos );
-          if( FALSE === $pos )
-            break;
-          if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
-            $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
-            $pos += 1;
-          }
-          $pos += 1;
-        }
-        if( FALSE !== strpos( $string, '"' ))
-          $string = str_replace('"',   "'",       $string);
-        if( FALSE !== strpos( $string, ',' ))
-          $string = str_replace(',',   '\,',      $string);
-        if( FALSE !== strpos( $string, ';' ))
-          $string = str_replace(';',   '\;',      $string);
-
-        if( FALSE !== strpos( $string, "\r\n" ))
-          $string = str_replace( "\r\n", '\n',    $string);
-        elseif( FALSE !== strpos( $string, "\r" ))
-          $string = str_replace( "\r", '\n',      $string);
-
-        elseif( FALSE !== strpos( $string, "\n" ))
-          $string = str_replace( "\n", '\n',      $string);
-
-        if( FALSE !== strpos( $string, '\N' ))
-          $string = str_replace( '\N', '\n',      $string);
-//        if( FALSE !== strpos( $string, $this->nl ))
-          $string = str_replace( $this->nl, '\n', $string);
-        break;
-    }
-    return $string;
-  }
-/**
- * special characters management input (from iCal file)
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.22 - 2010-10-17
- * @param string $string
- * @return string
- */
-  static function _strunrep( $string ) {
-    $string = str_replace( '\\\\', '\\',     $string);
-    $string = str_replace( '\,',   ',',      $string);
-    $string = str_replace( '\;',   ';',      $string);
-//    $string = str_replace( '\n',  $this->nl, $string); // ??
-    return $string;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * class for calendar component VEVENT
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class vevent extends calendarComponent {
-  var $attach;
-  var $attendee;
-  var $categories;
-  var $comment;
-  var $contact;
-  var $class;
-  var $created;
-  var $description;
-  var $dtend;
-  var $dtstart;
-  var $duration;
-  var $exdate;
-  var $exrule;
-  var $geo;
-  var $lastmodified;
-  var $location;
-  var $organizer;
-  var $priority;
-  var $rdate;
-  var $recurrenceid;
-  var $relatedto;
-  var $requeststatus;
-  var $resources;
-  var $rrule;
-  var $sequence;
-  var $status;
-  var $summary;
-  var $transp;
-  var $url;
-  var $xprop;
-            //  component subcomponents container
-  var $components;
-/**
- * constructor for calendar component VEVENT object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param  array $config
- * @return void
- */
-  function vevent( $config = array()) {
-    $this->calendarComponent();
-
-    $this->attach          = '';
-    $this->attendee        = '';
-    $this->categories      = '';
-    $this->class           = '';
-    $this->comment         = '';
-    $this->contact         = '';
-    $this->created         = '';
-    $this->description     = '';
-    $this->dtstart         = '';
-    $this->dtend           = '';
-    $this->duration        = '';
-    $this->exdate          = '';
-    $this->exrule          = '';
-    $this->geo             = '';
-    $this->lastmodified    = '';
-    $this->location        = '';
-    $this->organizer       = '';
-    $this->priority        = '';
-    $this->rdate           = '';
-    $this->recurrenceid    = '';
-    $this->relatedto       = '';
-    $this->requeststatus   = '';
-    $this->resources       = '';
-    $this->rrule           = '';
-    $this->sequence        = '';
-    $this->status          = '';
-    $this->summary         = '';
-    $this->transp          = '';
-    $this->url             = '';
-    $this->xprop           = '';
-
-    $this->components      = array();
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VEVENT object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.16 - 2011-10-28
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname    = $this->_createFormat();
-    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component    .= $this->createUid();
-    $component    .= $this->createDtstamp();
-    $component    .= $this->createAttach();
-    $component    .= $this->createAttendee();
-    $component    .= $this->createCategories();
-    $component    .= $this->createComment();
-    $component    .= $this->createContact();
-    $component    .= $this->createClass();
-    $component    .= $this->createCreated();
-    $component    .= $this->createDescription();
-    $component    .= $this->createDtstart();
-    $component    .= $this->createDtend();
-    $component    .= $this->createDuration();
-    $component    .= $this->createExdate();
-    $component    .= $this->createExrule();
-    $component    .= $this->createGeo();
-    $component    .= $this->createLastModified();
-    $component    .= $this->createLocation();
-    $component    .= $this->createOrganizer();
-    $component    .= $this->createPriority();
-    $component    .= $this->createRdate();
-    $component    .= $this->createRrule();
-    $component    .= $this->createRelatedTo();
-    $component    .= $this->createRequestStatus();
-    $component    .= $this->createRecurrenceid();
-    $component    .= $this->createResources();
-    $component    .= $this->createSequence();
-    $component    .= $this->createStatus();
-    $component    .= $this->createSummary();
-    $component    .= $this->createTransp();
-    $component    .= $this->createUrl();
-    $component    .= $this->createXprop();
-    $component    .= $this->createSubComponent();
-    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * class for calendar component VTODO
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class vtodo extends calendarComponent {
-  var $attach;
-  var $attendee;
-  var $categories;
-  var $comment;
-  var $completed;
-  var $contact;
-  var $class;
-  var $created;
-  var $description;
-  var $dtstart;
-  var $due;
-  var $duration;
-  var $exdate;
-  var $exrule;
-  var $geo;
-  var $lastmodified;
-  var $location;
-  var $organizer;
-  var $percentcomplete;
-  var $priority;
-  var $rdate;
-  var $recurrenceid;
-  var $relatedto;
-  var $requeststatus;
-  var $resources;
-  var $rrule;
-  var $sequence;
-  var $status;
-  var $summary;
-  var $url;
-  var $xprop;
-            //  component subcomponents container
-  var $components;
-/**
- * constructor for calendar component VTODO object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param array $config
- * @return void
- */
-  function vtodo( $config = array()) {
-    $this->calendarComponent();
-
-    $this->attach          = '';
-    $this->attendee        = '';
-    $this->categories      = '';
-    $this->class           = '';
-    $this->comment         = '';
-    $this->completed       = '';
-    $this->contact         = '';
-    $this->created         = '';
-    $this->description     = '';
-    $this->dtstart         = '';
-    $this->due             = '';
-    $this->duration        = '';
-    $this->exdate          = '';
-    $this->exrule          = '';
-    $this->geo             = '';
-    $this->lastmodified    = '';
-    $this->location        = '';
-    $this->organizer       = '';
-    $this->percentcomplete = '';
-    $this->priority        = '';
-    $this->rdate           = '';
-    $this->recurrenceid    = '';
-    $this->relatedto       = '';
-    $this->requeststatus   = '';
-    $this->resources       = '';
-    $this->rrule           = '';
-    $this->sequence        = '';
-    $this->status          = '';
-    $this->summary         = '';
-    $this->url             = '';
-    $this->xprop           = '';
-
-    $this->components      = array();
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VTODO object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-11-07
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname    = $this->_createFormat();
-    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component    .= $this->createUid();
-    $component    .= $this->createDtstamp();
-    $component    .= $this->createAttach();
-    $component    .= $this->createAttendee();
-    $component    .= $this->createCategories();
-    $component    .= $this->createClass();
-    $component    .= $this->createComment();
-    $component    .= $this->createCompleted();
-    $component    .= $this->createContact();
-    $component    .= $this->createCreated();
-    $component    .= $this->createDescription();
-    $component    .= $this->createDtstart();
-    $component    .= $this->createDue();
-    $component    .= $this->createDuration();
-    $component    .= $this->createExdate();
-    $component    .= $this->createExrule();
-    $component    .= $this->createGeo();
-    $component    .= $this->createLastModified();
-    $component    .= $this->createLocation();
-    $component    .= $this->createOrganizer();
-    $component    .= $this->createPercentComplete();
-    $component    .= $this->createPriority();
-    $component    .= $this->createRdate();
-    $component    .= $this->createRelatedTo();
-    $component    .= $this->createRequestStatus();
-    $component    .= $this->createRecurrenceid();
-    $component    .= $this->createResources();
-    $component    .= $this->createRrule();
-    $component    .= $this->createSequence();
-    $component    .= $this->createStatus();
-    $component    .= $this->createSummary();
-    $component    .= $this->createUrl();
-    $component    .= $this->createXprop();
-    $component    .= $this->createSubComponent();
-    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * class for calendar component VJOURNAL
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class vjournal extends calendarComponent {
-  var $attach;
-  var $attendee;
-  var $categories;
-  var $comment;
-  var $contact;
-  var $class;
-  var $created;
-  var $description;
-  var $dtstart;
-  var $exdate;
-  var $exrule;
-  var $lastmodified;
-  var $organizer;
-  var $rdate;
-  var $recurrenceid;
-  var $relatedto;
-  var $requeststatus;
-  var $rrule;
-  var $sequence;
-  var $status;
-  var $summary;
-  var $url;
-  var $xprop;
-/**
- * constructor for calendar component VJOURNAL object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param array $config
- * @return void
- */
-  function vjournal( $config = array()) {
-    $this->calendarComponent();
-
-    $this->attach          = '';
-    $this->attendee        = '';
-    $this->categories      = '';
-    $this->class           = '';
-    $this->comment         = '';
-    $this->contact         = '';
-    $this->created         = '';
-    $this->description     = '';
-    $this->dtstart         = '';
-    $this->exdate          = '';
-    $this->exrule          = '';
-    $this->lastmodified    = '';
-    $this->organizer       = '';
-    $this->rdate           = '';
-    $this->recurrenceid    = '';
-    $this->relatedto       = '';
-    $this->requeststatus   = '';
-    $this->rrule           = '';
-    $this->sequence        = '';
-    $this->status          = '';
-    $this->summary         = '';
-    $this->url             = '';
-    $this->xprop           = '';
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VJOURNAL object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname = $this->_createFormat();
-    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component .= $this->createUid();
-    $component .= $this->createDtstamp();
-    $component .= $this->createAttach();
-    $component .= $this->createAttendee();
-    $component .= $this->createCategories();
-    $component .= $this->createClass();
-    $component .= $this->createComment();
-    $component .= $this->createContact();
-    $component .= $this->createCreated();
-    $component .= $this->createDescription();
-    $component .= $this->createDtstart();
-    $component .= $this->createExdate();
-    $component .= $this->createExrule();
-    $component .= $this->createLastModified();
-    $component .= $this->createOrganizer();
-    $component .= $this->createRdate();
-    $component .= $this->createRequestStatus();
-    $component .= $this->createRecurrenceid();
-    $component .= $this->createRelatedTo();
-    $component .= $this->createRrule();
-    $component .= $this->createSequence();
-    $component .= $this->createStatus();
-    $component .= $this->createSummary();
-    $component .= $this->createUrl();
-    $component .= $this->createXprop();
-    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * class for calendar component VFREEBUSY
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class vfreebusy extends calendarComponent {
-  var $attendee;
-  var $comment;
-  var $contact;
-  var $dtend;
-  var $dtstart;
-  var $duration;
-  var $freebusy;
-  var $organizer;
-  var $requeststatus;
-  var $url;
-  var $xprop;
-            //  component subcomponents container
-  var $components;
-/**
- * constructor for calendar component VFREEBUSY object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param array $config
- * @return void
- */
-  function vfreebusy( $config = array()) {
-    $this->calendarComponent();
-
-    $this->attendee        = '';
-    $this->comment         = '';
-    $this->contact         = '';
-    $this->dtend           = '';
-    $this->dtstart         = '';
-    $this->duration        = '';
-    $this->freebusy        = '';
-    $this->organizer       = '';
-    $this->requeststatus   = '';
-    $this->url             = '';
-    $this->xprop           = '';
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VFREEBUSY object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.3.1 - 2007-11-19
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname = $this->_createFormat();
-    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component .= $this->createUid();
-    $component .= $this->createDtstamp();
-    $component .= $this->createAttendee();
-    $component .= $this->createComment();
-    $component .= $this->createContact();
-    $component .= $this->createDtstart();
-    $component .= $this->createDtend();
-    $component .= $this->createDuration();
-    $component .= $this->createFreebusy();
-    $component .= $this->createOrganizer();
-    $component .= $this->createRequestStatus();
-    $component .= $this->createUrl();
-    $component .= $this->createXprop();
-    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * class for calendar component VALARM
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class valarm extends calendarComponent {
-  var $action;
-  var $attach;
-  var $attendee;
-  var $description;
-  var $duration;
-  var $repeat;
-  var $summary;
-  var $trigger;
-  var $xprop;
-/**
- * constructor for calendar component VALARM object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param array $config
- * @return void
- */
-  function valarm( $config = array()) {
-    $this->calendarComponent();
-
-    $this->action          = '';
-    $this->attach          = '';
-    $this->attendee        = '';
-    $this->description     = '';
-    $this->duration        = '';
-    $this->repeat          = '';
-    $this->summary         = '';
-    $this->trigger         = '';
-    $this->xprop           = '';
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VALARM object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-22
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname    = $this->_createFormat();
-    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component    .= $this->createAction();
-    $component    .= $this->createAttach();
-    $component    .= $this->createAttendee();
-    $component    .= $this->createDescription();
-    $component    .= $this->createDuration();
-    $component    .= $this->createRepeat();
-    $component    .= $this->createSummary();
-    $component    .= $this->createTrigger();
-    $component    .= $this->createXprop();
-    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/**********************************************************************************
-/*********************************************************************************/
-/**
- * class for calendar component VTIMEZONE
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-12
- */
-class vtimezone extends calendarComponent {
-  var $timezonetype;
-
-  var $comment;
-  var $dtstart;
-  var $lastmodified;
-  var $rdate;
-  var $rrule;
-  var $tzid;
-  var $tzname;
-  var $tzoffsetfrom;
-  var $tzoffsetto;
-  var $tzurl;
-  var $xprop;
-            //  component subcomponents container
-  var $components;
-/**
- * constructor for calendar component VTIMEZONE object
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.2 - 2011-05-01
- * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
- * @param array $config
- * @return void
- */
-  function vtimezone( $timezonetype=FALSE, $config = array()) {
-    if( is_array( $timezonetype )) {
-      $config       = $timezonetype;
-      $timezonetype = FALSE;
-    }
-    if( !$timezonetype )
-      $this->timezonetype = 'VTIMEZONE';
-    else
-      $this->timezonetype = strtoupper( $timezonetype );
-    $this->calendarComponent();
-
-    $this->comment         = '';
-    $this->dtstart         = '';
-    $this->lastmodified    = '';
-    $this->rdate           = '';
-    $this->rrule           = '';
-    $this->tzid            = '';
-    $this->tzname          = '';
-    $this->tzoffsetfrom    = '';
-    $this->tzoffsetto      = '';
-    $this->tzurl           = '';
-    $this->xprop           = '';
-
-    $this->components      = array();
-
-    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
-                                          $config['language']   = ICAL_LANG;
-    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
-    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
-    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
-    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
-    $this->setConfig( $config );
-
-  }
-/**
- * create formatted output for calendar component VTIMEZONE object instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.5.1 - 2008-10-25
- * @param array $xcaldecl
- * @return string
- */
-  function createComponent( &$xcaldecl ) {
-    $objectname    = $this->_createFormat();
-    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
-    $component    .= $this->createTzid();
-    $component    .= $this->createLastModified();
-    $component    .= $this->createTzurl();
-    $component    .= $this->createDtstart();
-    $component    .= $this->createTzoffsetfrom();
-    $component    .= $this->createTzoffsetto();
-    $component    .= $this->createComment();
-    $component    .= $this->createRdate();
-    $component    .= $this->createRrule();
-    $component    .= $this->createTzname();
-    $component    .= $this->createXprop();
-    $component    .= $this->createSubComponent();
-    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
-    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
-      foreach( $this->xcaldecl as $localxcaldecl )
-        $xcaldecl[] = $localxcaldecl;
-    }
-    return $component;
-  }
-}
-/*********************************************************************************/
-/*********************************************************************************/
-/**
- * moving all utility (static) functions to a utility class
- * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.1 - 2011-07-16
- *
- */
-class iCalUtilityFunctions {
-  // Store the single instance of iCalUtilityFunctions
-  private static $m_pInstance;
-
-  // Private constructor to limit object instantiation to within the class
-  private function __construct() {
-    $m_pInstance = FALSE;
-  }
-
-  // Getter method for creating/returning the single instance of this class
-  public static function getInstance() {
-    if (!self::$m_pInstance)
-      self::$m_pInstance = new iCalUtilityFunctions();
-
-    return self::$m_pInstance;
-  }
-/**
- * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.30 - 2012-01-16
- * @param array $date, date to check
- * @param int $parno, no of date parts (i.e. year, month.. .)
- * @return array $params, property parameters
- */
-  public static function _chkdatecfg( $theDate, & $parno, & $params ) {
-    if( isset( $params['TZID'] ))
-      $parno = 6;
-    elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
-      $parno = 3;
-    else {
-      if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
-        $parno = 7;
-      if( is_array( $theDate )) {
-        if( isset( $theDate['timestamp'] ))
-          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
-        else
-          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
-        if( !empty( $tzid )) {
-          $parno = 7;
-          if( !iCalUtilityFunctions::_isOffset( $tzid ))
-            $params['TZID'] = $tzid; // save only timezone
-        }
-        elseif( !$parno && ( 3 == count( $theDate )) &&
-          ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
-          $parno = 3;
-        else
-          $parno = 6;
-      }
-      else { // string
-        $date = trim( $theDate );
-        if( 'Z' == substr( $date, -1 ))
-          $parno = 7; // UTC DATE-TIME
-        elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
-          ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
-          $parno = 3; // DATE
-        $date = iCalUtilityFunctions::_date_time_string( $date, $parno );
-        unset( $date['unparsedtext'] );
-        if( !empty( $date['tz'] )) {
-          $parno = 7;
-          if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
-            $params['TZID'] = $date['tz']; // save only timezone
-        }
-        elseif( empty( $parno ))
-          $parno = 6;
-      }
-      if( isset( $params['TZID'] ))
-        $parno = 6;
-    }
-  }
-/**
- * create timezone and standard/daylight components
- *
- * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
- *
- * BEGIN:VTIMEZONE
- * TZID:Europe/Stockholm
- * BEGIN:STANDARD
- * DTSTART:20101031T020000
- * TZOFFSETFROM:+0200
- * TZOFFSETTO:+0100
- * TZNAME:CET
- * END:STANDARD
- * BEGIN:DAYLIGHT
- * DTSTART:20100328T030000
- * TZOFFSETFROM:+0100
- * TZOFFSETTO:+0200
- * TZNAME:CEST
- * END:DAYLIGHT
- * END:VTIMEZONE
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-02-06
- * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
- * @param object $calendar, reference to an iCalcreator calendar instance
- * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
- * @param array  $xProp,    *[x-propName => x-propValue], optional
- * @param int    $from      an unix timestamp
- * @param int    $to        an unix timestamp
- * @return bool
- */
-  public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
-    if( !class_exists( 'DateTimeZone' ))
-      return FALSE;
-    if( empty( $timezone ))
-      return FALSE;
-    try {
-      $dtz               = new DateTimeZone( $timezone );
-      $transitions       = $dtz->getTransitions();
-      unset( $dtz );
-      $utcTz             = new DateTimeZone( 'UTC' );
-    }
-    catch( Exception $e ) {
-      return FALSE;
-    }
-    if( empty( $to ))
-      $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
-    $transCnt            = 2; // number of transitions in output if empty input $from/$to and an empty dates-array
-    $dateFrom            = new DateTime( 'now' );
-    $dateTo              = new DateTime( 'now' );
-    if( !empty( $from ))
-      $dateFrom->setTimestamp( $from );
-    else {
-      if( !empty( $dates ))
-        $dateFrom = new DateTime( reset( $dates ));              // set lowest date to the lowest dtstart date
-      $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
-    }
-    $dateFrom->setTimezone( $utcTz );                            // convert local date to UTC
-    if( !empty( $to ))
-      $dateTo->setTimestamp( $to );
-    else {
-      if( !empty( $dates )) {
-        $dateTo          = new DateTime( end( $dates ));         // set highest date to the highest dtstart date
-        $to              = $dateTo->getTimestamp();              // set mark that a highest date is found
-      }
-      $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
-    }
-    $dateTo->setTimezone( $utcTz );                              // convert local date to UTC
-    $transTemp           = array();
-    $prevOffsetfrom      = $stdCnt = $dlghtCnt = 0;
-    $stdIx  = $dlghtIx   = null;
-    $date = new DateTime( 'now', $utcTz );
-    foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
-      $date->setTimestamp( $trans['ts'] );                       // set transition date (UTC)
-      if ( $date < $dateFrom ) {
-        $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
-        continue;
-      }
-      if( $date > $dateTo )
-        break;                                                   // loop always (?) breaks here
-      if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
-        $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
-        $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
-        $trans['time'] = array( 'year'  => $date->format( 'Y' )  // set dtstart to array to ease up dtstart and (opt) rdate setting
-                              , 'month' => $date->format( 'n' )
-                              , 'day'   => $date->format( 'j' )
-                              , 'hour'  => $date->format( 'G' )
-                              , 'min'   => $date->format( 'i' )
-                              , 'sec'   => $date->format( 's' )); 
-      }
-      $prevOffsetfrom    = $trans['offset'];
-      $trans['prevYear'] = $trans['time']['year'];
-      if( TRUE !== $trans['isdst'] ) {                           // standard timezone
-        if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any rdate's (in strict year order)
-           ( $transTemp[$stdIx]['abbr']          == $trans['abbr'] )       &&
-           ( $transTemp[$stdIx]['offsetfrom']    == $trans['offsetfrom'] ) &&
-           ( $transTemp[$stdIx]['offset']        == $trans['offset'] )     &&
-           (($transTemp[$stdIx]['prevYear'] + 1) == $trans['time']['year'] )) {
-          $transTemp[$stdIx]['prevYear'] = $trans['time']['year'];
-          $transTemp[$stdIx]['rdate'][]  = $trans['time'];
-          continue;
-        }
-        $stdIx           = $tix;
-        $stdCnt         += 1;
-      } // end standard timezone
-      else {                                                     // daylight timezone
-        if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any rdate's (in strict year order)
-           ( $transTemp[$dlghtIx]['abbr']          == $trans['abbr'] )           &&
-           ( $transTemp[$dlghtIx]['offsetfrom']    == $trans['offsetfrom'] )     &&
-           ( $transTemp[$dlghtIx]['offset']        == $trans['offset'] )         &&
-           (($transTemp[$dlghtIx]['prevYear'] + 1) == $trans['time']['year'] )) {
-          $transTemp[$dlghtIx]['prevYear'] = $trans['time']['year'];
-          $transTemp[$dlghtIx]['rdate'][]  = $trans['time'];
-          continue;
-        }
-        $dlghtIx         = $tix;
-        $dlghtCnt       += 1;
-      } // end daylight timezone
-      if( empty( $to ) && ( $transCnt == count( $transTemp ))) { // store only $transCnt transitions
-        if( TRUE !== $transTemp[0]['isdst'] )
-          $stdCnt       -= 1;
-        else
-         $dlghtCnt      -= 1;
-        array_shift( $transTemp );
-      } // end if( empty( $to ) && ( $transCnt == count( $transTemp )))
-      $transTemp[$tix]   = $trans;
-    } // end foreach( $transitions as $tix => $trans )
-    unset( $transitions );
-    if( empty( $transTemp ))
-      return FALSE;
-    $tz  = & $calendar->newComponent( 'vtimezone' );
-    $tz->setproperty( 'tzid', $timezone );
-    if( !empty( $xProp )) {
-      foreach( $xProp as $xPropName => $xPropValue )
-        if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
-          $tz->setproperty( $xPropName, $xPropValue );
-    }
-    foreach( $transTemp as $trans ) {
-      $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
-      $scomp = & $tz->newComponent( $type );
-      $scomp->setProperty( 'dtstart',         $trans['time'] );
-//      $scomp->setProperty( 'x-utc-timestamp', $trans['ts'] );   // test ###
-      if( !empty( $trans['abbr'] ))
-        $scomp->setProperty( 'tzname',        $trans['abbr'] );
-      $scomp->setProperty( 'tzoffsetfrom',    iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
-      $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
-      if( isset( $trans['rdate'] ))
-        $scomp->setProperty( 'RDATE',         $trans['rdate'] );
-    }
-    return TRUE;
-  }
-/**
- * convert a date/datetime (array) to timestamp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.8 - 2008-10-30
- * @param array  $datetime  datetime/(date)
- * @param string $tz        timezone
- * @return timestamp
- */
-  public static function _date2timestamp( $datetime, $tz=null ) {
-    $output = null;
-    if( !isset( $datetime['hour'] )) $datetime['hour'] = '0';
-    if( !isset( $datetime['min'] ))  $datetime['min']  = '0';
-    if( !isset( $datetime['sec'] ))  $datetime['sec']  = '0';
-    foreach( $datetime as $dkey => $dvalue ) {
-      if( 'tz' != $dkey )
-        $datetime[$dkey] = (integer) $dvalue;
-    }
-    if( $tz )
-      $datetime['tz'] = $tz;
-    $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) : 0;
-    $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] );
-    return $output;
-  }
-/**
- * ensures internal date-time/date format for input date-time/date in array format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.4 - 2012-03-18
- * @param array $datetime
- * @param int $parno optional, default FALSE
- * @return array
- */
-  public static function _date_time_array( $datetime, $parno=FALSE ) {
-    $output = array();
-    foreach( $datetime as $dateKey => $datePart ) {
-      switch ( $dateKey ) {
-        case '0': case 'year':   $output['year']  = $datePart; break;
-        case '1': case 'month':  $output['month'] = $datePart; break;
-        case '2': case 'day':    $output['day']   = $datePart; break;
-      }
-      if( 3 != $parno ) {
-        switch ( $dateKey ) {
-          case '0':
-          case '1':
-          case '2': break;
-          case '3': case 'hour': $output['hour']  = $datePart; break;
-          case '4': case 'min' : $output['min']   = $datePart; break;
-          case '5': case 'sec' : $output['sec']   = $datePart; break;
-          case '6': case 'tz'  : $output['tz']    = $datePart; break;
-        }
-      }
-    }
-    if( 3 != $parno ) {
-      if( !isset( $output['hour'] ))
-        $output['hour'] = 0;
-      if( !isset( $output['min']  ))
-        $output['min'] = 0;
-      if( !isset( $output['sec']  ))
-        $output['sec'] = 0;
-      if( isset( $output['tz'] ) && ( 'Z' != $output['tz'] ) &&
-        (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
-          $output['tz'] = 'Z';
-    }
-    return $output;
-  }
-/**
- * ensures internal date-time/date format for input date-time/date in string fromat
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.30 - 2012-01-06
- * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
- * @param array $datetime
- * @param int $parno optional, default FALSE
- * @return array
- */
-  public static function _date_time_string( $datetime, $parno=FALSE ) {
-    // save original input string to return it later
-    $unparseddatetime = $datetime;
-    $datetime = (string) trim( $datetime );
-    $tz  = null;
-    $len = strlen( $datetime ) - 1;
-    if( 'Z' == substr( $datetime, -1 )) {
-      $tz = 'Z';
-      $datetime = trim( substr( $datetime, 0, $len ));
-    }
-    elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date
-                  ( '-' == substr( $datetime, -3, 1 )) ||
-                  ( ':' == substr( $datetime, -3, 1 )) ||
-                  ( '.' == substr( $datetime, -3, 1 ))) {
-      $continue = TRUE;
-    }
-    elseif( ( ctype_digit( substr( $datetime, -4, 4 ))) && // 4 pos offset
-            ( ' +' == substr( $datetime, -6, 2 )) ||
-            ( ' -' == substr( $datetime, -6, 2 ))) {
-      $tz = substr( $datetime, -5, 5 );
-      $datetime = substr( $datetime, 0, ($len - 5));
-    }
-    elseif( ( ctype_digit( substr( $datetime, -6, 6 ))) && // 6 pos offset
-            ( ' +' == substr( $datetime, -8, 2 )) ||
-            ( ' -' == substr( $datetime, -8, 2 ))) {
-      $tz = substr( $datetime, -7, 7 );
-      $datetime = substr( $datetime, 0, ($len - 7));
-    }
-    elseif( ( 6 < $len ) && ( ctype_digit( substr( $datetime, -6, 6 )))) {
-      $continue = TRUE;
-    }
-    elseif( 'T' ==  substr( $datetime, -7, 1 )) {
-      $continue = TRUE;
-    }
-    else {
-      $cx  = $tx = 0;    //  19970415T133000 US-Eastern
-      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
-        $char = substr( $datetime, $cx, 1 );
-        if(( ' ' == $char) || ctype_digit( $char))
-          break; // if exists, tz ends here.. . ?
-        else
-           $tx--; // tz length counter
-      }
-      if( 0 > $tx ) {
-        $tz = substr( $datetime, $tx );
-        $datetime = trim( substr( $datetime, 0, $len + $tx + 1 ));
-      }
-    }
-    if( 0 < substr_count( $datetime, '-' )) {
-      $datetime = str_replace( '-', '/', $datetime );
-    }
-    elseif( ctype_digit( substr( $datetime, 0, 8 )) &&
-           ( 'T' ==      substr( $datetime, 8, 1 )) &&
-            ctype_digit( substr( $datetime, 9, 6 ))) {
-     }
-    $datestring = date( 'Y-m-d H:i:s', strtotime( $datetime ));
-    $tz                = trim( $tz );
-    $output            = array();
-    $output['year']    = substr( $datestring, 0, 4 );
-    $output['month']   = substr( $datestring, 5, 2 );
-    $output['day']     = substr( $datestring, 8, 2 );
-    if(( 6 == $parno ) || ( 7 == $parno ) || ( !$parno && ( 'Z' == $tz ))) {
-      $output['hour']  = substr( $datestring, 11, 2 );
-      $output['min']   = substr( $datestring, 14, 2 );
-      $output['sec']   = substr( $datestring, 17, 2 );
-      if( !empty( $tz ))
-        $output['tz']  = $tz;
-    }
-    elseif( 3 != $parno ) {
-      if(( '00' < substr( $datestring, 11, 2 )) ||
-         ( '00' < substr( $datestring, 14, 2 )) ||
-         ( '00' < substr( $datestring, 17, 2 ))) {
-        $output['hour']  = substr( $datestring, 11, 2 );
-        $output['min']   = substr( $datestring, 14, 2 );
-        $output['sec']   = substr( $datestring, 17, 2 );
-      }
-      if( !empty( $tz ))
-        $output['tz']  = $tz;
-    }
-    // return original string in the array in case strtotime failed to make sense of it
-    $output['unparsedtext']    = $unparseddatetime;
-    return $output;
-  }
-/**
- * convert local startdate/enddate (Ymd[His]) to duration array
- *
- * uses this component dates if missing input dates
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.11 - 2010-10-21
- * @param array $startdate
- * @param array $duration
- * @return array duration
- */
-  public static function _date2duration( $startdate, $enddate ) {
-    $startWdate  = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] );
-    $endWdate    = mktime( 0, 0, 0, $enddate['month'],   $enddate['day'],   $enddate['year'] );
-    $wduration   = $endWdate - $startWdate;
-    $dur         = array();
-    $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 ));
-    $wduration   =              $wduration % ( 7 * 24 * 60 * 60 );
-    $dur['day']  = (int) floor( $wduration / ( 24 * 60 * 60 ));
-    $wduration   =              $wduration % ( 24 * 60 * 60 );
-    $dur['hour'] = (int) floor( $wduration / ( 60 * 60 ));
-    $wduration   =              $wduration % ( 60 * 60 );
-    $dur['min']  = (int) floor( $wduration / ( 60 ));
-    $dur['sec']  = (int)        $wduration % ( 60 );
-    return $dur;
-  }
-/**
- * ensures internal duration format for input in array format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.1.1 - 2007-06-24
- * @param array $duration
- * @return array
- */
-  public static function _duration_array( $duration ) {
-    $output = array();
-    if(    is_array( $duration )        &&
-       ( 1 == count( $duration ))       &&
-              isset( $duration['sec'] ) &&
-              ( 60 < $duration['sec'] )) {
-      $durseconds  = $duration['sec'];
-      $output['week'] = floor( $durseconds / ( 60 * 60 * 24 * 7 ));
-      $durseconds  =           $durseconds % ( 60 * 60 * 24 * 7 );
-      $output['day']  = floor( $durseconds / ( 60 * 60 * 24 ));
-      $durseconds  =           $durseconds % ( 60 * 60 * 24 );
-      $output['hour'] = floor( $durseconds / ( 60 * 60 ));
-      $durseconds  =           $durseconds % ( 60 * 60 );
-      $output['min']  = floor( $durseconds / ( 60 ));
-      $output['sec']  =      ( $durseconds % ( 60 ));
-    }
-    else {
-      foreach( $duration as $durKey => $durValue ) {
-        if( empty( $durValue )) continue;
-        switch ( $durKey ) {
-          case '0': case 'week': $output['week']  = $durValue; break;
-          case '1': case 'day':  $output['day']   = $durValue; break;
-          case '2': case 'hour': $output['hour']  = $durValue; break;
-          case '3': case 'min':  $output['min']   = $durValue; break;
-          case '4': case 'sec':  $output['sec']   = $durValue; break;
-        }
-      }
-    }
-    if( isset( $output['week'] ) && ( 0 < $output['week'] )) {
-      unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
-      return $output;
-    }
-    unset( $output['week'] );
-    if( empty( $output['day'] ))
-      unset( $output['day'] );
-    if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) {
-      if( !isset( $output['hour'] )) $output['hour'] = 0;
-      if( !isset( $output['min']  )) $output['min']  = 0;
-      if( !isset( $output['sec']  )) $output['sec']  = 0;
-      if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
-        unset( $output['hour'], $output['min'], $output['sec'] );
-    }
-    return $output;
-  }
-/**
- * ensures internal duration format for input in string format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.0.5 - 2007-03-14
- * @param string $duration
- * @return array
- */
-  public static function _duration_string( $duration ) {
-    $duration = (string) trim( $duration );
-    while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
-      if( 0 < strlen( $duration ))
-        $duration = substr( $duration, 1 );
-      else
-        return false; // no leading P !?!?
-    }
-    $duration = substr( $duration, 1 ); // skip P
-    $duration = str_replace ( 't', 'T', $duration );
-    $duration = str_replace ( 'T', '', $duration );
-    $output = array();
-    $val    = null;
-    for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
-      switch( strtoupper( substr( $duration, $ix, 1 ))) {
-       case 'W':
-         $output['week'] = $val;
-         $val            = null;
-         break;
-       case 'D':
-         $output['day']  = $val;
-         $val            = null;
-         break;
-       case 'H':
-         $output['hour'] = $val;
-         $val            = null;
-         break;
-       case 'M':
-         $output['min']  = $val;
-         $val            = null;
-         break;
-       case 'S':
-         $output['sec']  = $val;
-         $val            = null;
-         break;
-       default:
-         if( !ctype_digit( substr( $duration, $ix, 1 )))
-           return false; // unknown duration control character  !?!?
-         else
-           $val .= substr( $duration, $ix, 1 );
-      }
-    }
-    return iCalUtilityFunctions::_duration_array( $output );
-  }
-/**
- * convert duration to date in array format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.8.7 - 2011-03-03
- * @param array $startdate
- * @param array $duration
- * @return array, date format
- */
-  public static function _duration2date( $startdate=null, $duration=null ) {
-    if( empty( $startdate )) return FALSE;
-    if( empty( $duration ))  return FALSE;
-    $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
-    $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
-    $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
-    $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
-    $dtend = 0;
-    if(    isset( $duration['week'] ))
-      $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
-    if(    isset( $duration['day'] ))
-      $dtend += ( $duration['day'] * 24 * 60 * 60 );
-    if(    isset( $duration['hour'] ))
-      $dtend += ( $duration['hour'] * 60 *60 );
-    if(    isset( $duration['min'] ))
-      $dtend += ( $duration['min'] * 60 );
-    if(    isset( $duration['sec'] ))
-      $dtend +=   $duration['sec'];
-    $dtend  = mktime( $startdate['hour'], $startdate['min'], ( $startdate['sec'] + $dtend ), $startdate['month'], $startdate['day'], $startdate['year'] );
-    $dtend2 = array();
-    $dtend2['year']   = date('Y', $dtend );
-    $dtend2['month']  = date('m', $dtend );
-    $dtend2['day']    = date('d', $dtend );
-    $dtend2['hour']   = date('H', $dtend );
-    $dtend2['min']    = date('i', $dtend );
-    $dtend2['sec']    = date('s', $dtend );
-    if( isset( $startdate['tz'] ))
-      $dtend2['tz']   = $startdate['tz'];
-    if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
-      unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
-    return $dtend2;
-  }
-/**
- * if not preSet, if exist, remove key with expected value from array and return hit value else return elseValue
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-11-08
- * @param array $array
- * @param string $expkey, expected key
- * @param string $expval, expected value
- * @param int $hitVal optional, return value if found
- * @param int $elseVal optional, return value if not found
- * @param int $preSet optional, return value if already preset
- * @return int
- */
-  public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
-    if( $preSet )
-      return $preSet;
-    if( !is_array( $array ) || ( 0 == count( $array )))
-      return $elseVal;
-    foreach( $array as $key => $value ) {
-      if( strtoupper( $expkey ) == strtoupper( $key )) {
-        if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
-          unset( $array[$key] );
-          return $hitVal;
-        }
-      }
-    }
-    return $elseVal;
-  }
-/**
- * creates formatted output for calendar component property data value type date/date-time
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-03-17
- * @param array   $datetime
- * @param int     $parno, optional, default 6
- * @return string
- */
-  public static function _format_date_time( $datetime, $parno=6 ) {
-    if( !isset( $datetime['year'] )  &&
-        !isset( $datetime['month'] ) &&
-        !isset( $datetime['day'] )   &&
-        !isset( $datetime['hour'] )  &&
-        !isset( $datetime['min'] )   &&
-        !isset( $datetime['sec'] ))
-      return ;
-    $output = null;
-    foreach( $datetime as $dkey => & $dvalue )
-      if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
-    $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
-    if( isset( $datetime['hour'] )  ||
-        isset( $datetime['min'] )   ||
-        isset( $datetime['sec'] )   ||
-        isset( $datetime['tz'] )) {
-      if( isset( $datetime['tz'] )  &&
-         !isset( $datetime['hour'] ))
-        $datetime['hour'] = 0;
-      if( isset( $datetime['hour'] )  &&
-         !isset( $datetime['min'] ))
-        $datetime['min'] = 0;
-      if( isset( $datetime['hour'] )  &&
-          isset( $datetime['min'] )   &&
-         !isset( $datetime['sec'] ))
-        $datetime['sec'] = 0;
-      $output .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
-      if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
-        $datetime['tz'] = trim( $datetime['tz'] );
-        if( 'Z' == $datetime['tz'] )
-          $output .= 'Z';
-        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
-        if( 0 != $offset ) {
-          $date   = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year']);
-          $output = date( 'Ymd\THis\Z', $date );
-        }
-      }
-      elseif( 7 == $parno )
-        $output .= 'Z';
-    }
-    return $output;
-  }
-/**
- * creates formatted output for calendar component property data value type duration
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.9.9 - 2011-06-17
- * @param array $duration ( week, day, hour, min, sec )
- * @return string
- */
-  public static function _format_duration( $duration ) {
-    if( isset( $duration['week'] ) ||
-        isset( $duration['day'] )  ||
-        isset( $duration['hour'] ) ||
-        isset( $duration['min'] )  ||
-        isset( $duration['sec'] ))
-       $ok = TRUE;
-    else
-      return;
-    if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
-      return 'P'.$duration['week'].'W';
-    $output = 'P';
-    if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
-      $output .= $duration['day'].'D';
-    if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
-       ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
-       ( isset( $duration['sec'])  && ( 0 < $duration['sec'] )))
-      $output .= 'T';
-    $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '';
-    $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '';
-    $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '';
-    if( 'P' == $output )
-      $output = 'PT0S';
-    return $output;
-  }
-/**
- * checks if input array contains a date
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-01-20
- * @param array $input
- * @return bool
- */
-  public static function _isArrayDate( $input ) {
-    if( !is_array( $input ))
-      return FALSE;
-    if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 ))))
-      return FALSE;
-    if( 7 == count( $input ))
-      return TRUE;
-    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
-      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
-    if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
-      return FALSE;
-    if( in_array( 0, $input ))
-      return FALSE;
-    if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
-      return FALSE;
-    if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
-         checkdate( (int) $input[1], (int) $input[2], (int) $input[0] ))
-      return TRUE;
-    $input = iCalUtilityFunctions::_date_time_string( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
-    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
-      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
-    return FALSE;
-  }
-/**
- * checks if input array contains a timestamp date
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-10-18
- * @param array $input
- * @return bool
- */
-  public static function _isArrayTimestampDate( $input ) {
-    return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
-  }
-/**
- * controll if input string contains trailing UTC offset
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-10-19
- * @param string $input
- * @return bool
- */
-  public static function _isOffset( $input ) {
-    $input         = trim( (string) $input );
-    if( 'Z' == substr( $input, -1 ))
-      return TRUE;
-    elseif((   5 <= strlen( $input )) &&
-       ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
-       (   '0000'  < substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
-      return TRUE;
-    elseif((    7 <= strlen( $input )) &&
-       ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
-       ( '000000'  < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
-      return TRUE;
-    return FALSE;
-  }
-/**
- * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
- * matching (MS) UCT offset and time zone descriptors
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.29 - 2012-01-11
- * @param string $timezone, input/output variable reference
- * @return bool
- */
-  public static function ms2phpTZ( & $timezone ) {
-    if( !class_exists( 'DateTimeZone' ))
-      return FALSE;
-    if( empty( $timezone ))
-      return FALSE;
-    $search = str_replace( '"', '', $timezone );
-    $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
-    if( '(UTC' != substr( $search, 0, 4 ))
-      return FALSE;
-    if( FALSE === ( $pos = strpos( $search, ')' )))
-      return FALSE;
-    $pos    = strpos( $search, ')' );
-    $searchOffset = substr( $search, 4, ( $pos - 4 ));
-    $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
-    while( ' ' ==substr( $search, ( $pos + 1 )))
-      $pos += 1;
-    $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
-    $searchWords  = explode( ' ', $searchText );
-    $timezone_abbreviations = DateTimeZone::listAbbreviations();
-    $hits = array();
-    foreach( $timezone_abbreviations as $name => $transitions ) {
-      foreach( $transitions as $cnt => $transition ) {
-        if( empty( $transition['offset'] )      ||
-            empty( $transition['timezone_id'] ) ||
-          ( $transition['offset'] != $searchOffset ))
-        continue;
-        $cWords = explode( '/', $transition['timezone_id'] );
-        $cPrio   = $hitCnt = $rank = 0;
-        foreach( $cWords as $cWord ) {
-          if( empty( $cWord ))
-            continue;
-          $cPrio += 1;
-          $sPrio  = 0;
-          foreach( $searchWords as $sWord ) {
-            if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
-              continue;
-            $sPrio += 1;
-            if( strtolower( $cWord ) == strtolower( $sWord )) {
-              $hitCnt += 1;
-              $rank   += ( $cPrio + $sPrio );
-            }
-            else
-              $rank += 10;
-          }
-        }
-        if( 0 < $hitCnt ) {
-          $hits[$rank][] = $transition['timezone_id'];
-        }
-      }
-    }
-    unset( $timezone_abbreviations );
-    if( empty( $hits ))
-      return FALSE;
-    ksort( $hits );
-    foreach( $hits as $rank => $tzs ) {
-      if( !empty( $tzs )) {
-        $timezone = reset( $tzs );
-        return TRUE;
-      }
-    }
-    return FALSE;
-  }
-/**
- * transform offset in seconds to [-/+]hhmm[ss]
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2011-05-02
- * @param string $seconds
- * @return string
- */
-  public static function offsetSec2His( $seconds ) {
-    if( '-' == substr( $seconds, 0, 1 )) {
-      $prefix  = '-';
-      $seconds = substr( $seconds, 1 );
-    }
-    elseif( '+' == substr( $seconds, 0, 1 )) {
-      $prefix  = '+';
-      $seconds = substr( $seconds, 1 );
-    }
-    else
-      $prefix  = '+';
-    $output  = '';
-    $hour    = (int) floor( $seconds / 3600 );
-    if( 10 > $hour )
-      $hour  = '0'.$hour;
-    $seconds = $seconds % 3600;
-    $min     = (int) floor( $seconds / 60 );
-    if( 10 > $min )
-      $min   = '0'.$min;
-    $output  = $hour.$min;
-    $seconds = $seconds % 60;
-    if( 0 < $seconds) {
-      if( 9 < $seconds)
-        $output .= $seconds;
-      else
-        $output .= '0'.$seconds;
-    }
-    return $prefix.$output;
-  }
-/**
- * remakes a recur pattern to an array of dates
- *
- * if missing, UNTIL is set 1 year from startdate (emergency break)
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.10.19 - 2011-10-31
- * @param array $result, array to update, array([timestamp] => timestamp)
- * @param array $recur, pattern for recurrency (only value part, params ignored)
- * @param array $wdate, component start date
- * @param array $startdate, start date
- * @param array $enddate, optional
- * @return array of recurrence (start-)dates as index
- * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
- */
-  public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
-    foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
-    $wdateStart  = $wdate;
-    $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
-    $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
-    if( !$enddate ) {
-      $enddate = $startdate;
-      $enddate['year'] += 1;
-    }
-// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test###
-    $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
-    if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
-      $recur['UNTIL'] = $enddate; // create break
-    if( isset( $recur['UNTIL'] )) {
-      $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
-      if( $endDatets > $tdatets ) {
-        $endDatets = $tdatets; // emergency break
-        $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
-      }
-      else
-        $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
-    }
-    if( $wdatets > $endDatets ) {
-// echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
-      return array(); // nothing to do.. .
-    }
-    if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
-      $recur['FREQ'] = 'DAILY'; // ??
-    $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
-    $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
-    if( !isset( $recur['INTERVAL'] ))
-      $recur['INTERVAL'] = 1;
-    $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
-            /* find out how to step up dates and set index for interval count */
-    $step = array();
-    if( 'YEARLY' == $recur['FREQ'] )
-      $step['year']  = 1;
-    elseif( 'MONTHLY' == $recur['FREQ'] )
-      $step['month'] = 1;
-    elseif( 'WEEKLY' == $recur['FREQ'] )
-      $step['day']   = 7;
-    else
-      $step['day']   = 1;
-    if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
-      $step = array( 'month' => 1 );
-    if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
-      $step = array( 'day' => 7 );
-    if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
-      $step = array( 'day' => 1 );
-    $intervalarr = array();
-    if( 1 < $recur['INTERVAL'] ) {
-      $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
-      $intervalarr = array( $intervalix => 0 );
-    }
-    if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
-      $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
-// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ###
-      if( is_array( $recur['BYSETPOS'] )) {
-        foreach( $recur['BYSETPOS'] as $bix => $bval )
-          $recur['BYSETPOS'][$bix] = (int) $bval;
-      }
-      else
-        $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
-      if( 'YEARLY' == $recur['FREQ'] ) {
-        $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
-        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
-        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
-      }
-      elseif( 'MONTHLY' == $recur['FREQ'] ) {
-        $wdate['day']   = 1; // start from beginning of month
-        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
-        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
-      }
-      else
-        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
-// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test###
-      $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
-      $bysetposYold = $wdate['year'];
-      $bysetposMold = $wdate['month'];
-      $bysetposDold = $wdate['day'];
-    }
-    else
-      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
-    $year_old     = null;
-    $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
-             /* MAIN LOOP */
-// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test
-    while( TRUE ) {
-      if( isset( $endDatets ) && ( $wdatets > $endDatets ))
-        break;
-      if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
-        break;
-      if( $year_old != $wdate['year'] ) {
-        $year_old   = $wdate['year'];
-        $daycnts    = array();
-        $yeardays   = $weekno = 0;
-        $yeardaycnt = array();
-        foreach( $daynames as $dn )
-          $yeardaycnt[$dn] = 0;
-        for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
-          $daycnts[$m] = array();
-          $weekdaycnt = array();
-          foreach( $daynames as $dn )
-            $weekdaycnt[$dn] = 0;
-          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
-          for( $d   = 1; $d <= $mcnt; $d++ ) {
-            $daycnts[$m][$d] = array();
-            if( isset( $recur['BYYEARDAY'] )) {
-              $yeardays++;
-              $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
-            }
-            if( isset( $recur['BYDAY'] )) {
-              $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
-              $day    = $daynames[$day];
-              $daycnts[$m][$d]['DAY'] = $day;
-              $weekdaycnt[$day]++;
-              $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
-              $yeardaycnt[$day]++;
-              $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
-            }
-            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
-              $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
-          }
-        }
-        $daycnt = 0;
-        $yeardaycnt = array();
-        if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
-          $weekno = null;
-          for( $d=31; $d > 25; $d-- ) { // get last weekno for year
-            if( !$weekno )
-              $weekno = $daycnts[12][$d]['weekno_up'];
-            elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
-              $weekno = $daycnts[12][$d]['weekno_up'];
-              break;
-            }
-          }
-        }
-        for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
-          $weekdaycnt = array();
-          foreach( $daynames as $dn )
-            $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
-          $monthcnt = 0;
-          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
-          for( $d   = $mcnt; $d > 0; $d-- ) {
-            if( isset( $recur['BYYEARDAY'] )) {
-              $daycnt -= 1;
-              $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
-            }
-            if( isset( $recur['BYMONTHDAY'] )) {
-              $monthcnt -= 1;
-              $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
-            }
-            if( isset( $recur['BYDAY'] )) {
-              $day  = $daycnts[$m][$d]['DAY'];
-              $weekdaycnt[$day] -= 1;
-              $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
-              $yeardaycnt[$day] -= 1;
-              $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
-            }
-            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
-              $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
-          }
-        }
-      }
-            /* check interval */
-      if( 1 < $recur['INTERVAL'] ) {
-            /* create interval index */
-        $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
-            /* check interval */
-        $currentKey = array_keys( $intervalarr );
-        $currentKey = end( $currentKey ); // get last index
-        if( $currentKey != $intervalix )
-          $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
-        if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
-           ( 0 != $intervalarr[$intervalix] )) {
-            /* step up date */
-// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
-          iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
-          continue;
-        }
-        else // continue within the selected interval
-          $intervalarr[$intervalix] = 0;
-// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
-      }
-      $updateOK = TRUE;
-      if( $updateOK && isset( $recur['BYMONTH'] ))
-        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
-                                           , $wdate['month']
-                                           ,($wdate['month'] - 13));
-      if( $updateOK && isset( $recur['BYWEEKNO'] ))
-        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
-                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
-                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
-      if( $updateOK && isset( $recur['BYYEARDAY'] ))
-        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
-                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
-                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
-      if( $updateOK && isset( $recur['BYMONTHDAY'] ))
-        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
-                                           , $wdate['day']
-                                           , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
-// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test###
-      if( $updateOK && isset( $recur['BYDAY'] )) {
-        $updateOK = FALSE;
-        $m = $wdate['month'];
-        $d = $wdate['day'];
-        if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
-          $daynoexists = $daynosw = $daynamesw =  FALSE;
-          if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
-            $daynamesw = TRUE;
-          if( isset( $recur['BYDAY'][0] )) {
-            $daynoexists = TRUE;
-            if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
-              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
-                                                , $daycnts[$m][$d]['monthdayno_up']
-                                                , $daycnts[$m][$d]['monthdayno_down'] );
-            elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
-              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
-                                                , $daycnts[$m][$d]['yeardayno_up']
-                                                , $daycnts[$m][$d]['yeardayno_down'] );
-          }
-          if((  $daynoexists &&  $daynosw && $daynamesw ) ||
-             ( !$daynoexists && !$daynosw && $daynamesw )) {
-            $updateOK = TRUE;
-// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
-          }
-//echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
-        }
-        else {
-          foreach( $recur['BYDAY'] as $bydayvalue ) {
-            $daynoexists = $daynosw = $daynamesw = FALSE;
-            if( isset( $bydayvalue['DAY'] ) &&
-                     ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
-              $daynamesw = TRUE;
-            if( isset( $bydayvalue[0] )) {
-              $daynoexists = TRUE;
-              if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
-                   isset( $recur['BYMONTH'] ))
-                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
-                                                  , $daycnts[$m][$d]['monthdayno_up']
-                                                  , $daycnts[$m][$d]['monthdayno_down'] );
-              elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
-                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
-                                                  , $daycnts[$m][$d]['yeardayno_up']
-                                                  , $daycnts[$m][$d]['yeardayno_down'] );
-            }
-// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ###
-            if((  $daynoexists &&  $daynosw && $daynamesw ) ||
-               ( !$daynoexists && !$daynosw && $daynamesw )) {
-              $updateOK = TRUE;
-              break;
-            }
-          }
-        }
-      }
-// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ###
-            /* check BYSETPOS */
-      if( $updateOK ) {
-        if( isset( $recur['BYSETPOS'] ) &&
-          ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
-          if( isset( $recur['WEEKLY'] )) {
-            if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
-              $bysetposw1[] = $wdatets;
-            else
-              $bysetposw2[] = $wdatets;
-          }
-          else {
-            if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
-                                            ( $bysetposYold == $wdate['year'] ))   ||
-               ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
-                                           (( $bysetposYold == $wdate['year'] )  &&
-                                            ( $bysetposMold == $wdate['month'] ))) ||
-               ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
-                                           (( $bysetposYold == $wdate['year'] )  &&
-                                            ( $bysetposMold == $wdate['month'])  &&
-                                            ( $bysetposDold == $wdate['day'] )))) {
-// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
-              $bysetposymd1[] = $wdatets;
-            }
-            else {
-// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
-              $bysetposymd2[] = $wdatets;
-            }
-          }
-        }
-        else {
-            /* update result array if BYSETPOS is set */
-          $countcnt++;
-          if( $startdatets <= $wdatets ) { // only output within period
-            $result[$wdatets] = TRUE;
-// echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
-          }
-// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test
-          $updateOK = FALSE;
-        }
-      }
-            /* step up date */
-      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
-            /* check if BYSETPOS is set for updating result array */
-      if( $updateOK && isset( $recur['BYSETPOS'] )) {
-        $bysetpos       = FALSE;
-        if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
-          ( $bysetposYold != $wdate['year'] )) {
-          $bysetpos     = TRUE;
-          $bysetposYold = $wdate['year'];
-        }
-        elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
-         (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
-          $bysetpos     = TRUE;
-          $bysetposYold = $wdate['year'];
-          $bysetposMold = $wdate['month'];
-        }
-        elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
-          $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
-          if( $bysetposWold != $weekno ) {
-            $bysetposWold = $weekno;
-            $bysetpos     = TRUE;
-          }
-        }
-        elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
-         (( $bysetposYold != $wdate['year'] )  ||
-          ( $bysetposMold != $wdate['month'] ) ||
-          ( $bysetposDold != $wdate['day'] ))) {
-          $bysetpos     = TRUE;
-          $bysetposYold = $wdate['year'];
-          $bysetposMold = $wdate['month'];
-          $bysetposDold = $wdate['day'];
-        }
-        if( $bysetpos ) {
-          if( isset( $recur['BYWEEKNO'] )) {
-            $bysetposarr1 = & $bysetposw1;
-            $bysetposarr2 = & $bysetposw2;
-          }
-          else {
-            $bysetposarr1 = & $bysetposymd1;
-            $bysetposarr2 = & $bysetposymd2;
-          }
-// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
-          foreach( $recur['BYSETPOS'] as $ix ) {
-            if( 0 > $ix ) // both positive and negative BYSETPOS allowed
-              $ix = ( count( $bysetposarr1 ) + $ix + 1);
-            $ix--;
-            if( isset( $bysetposarr1[$ix] )) {
-              if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
-//                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
-//                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
-// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
-                $result[$bysetposarr1[$ix]] = TRUE;
-// echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
-              }
-              $countcnt++;
-            }
-            if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
-              break;
-          }
-// echo "<br />\n"; // test ###
-          $bysetposarr1 = $bysetposarr2;
-          $bysetposarr2 = array();
-        }
-      }
-    }
-  }
-  public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
-    if( is_array( $BYvalue ) &&
-      ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
-      return TRUE;
-    elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
-      return TRUE;
-    else
-      return FALSE;
-  }
-  public static function _recurIntervalIx( $freq, $date, $wkst ) {
-            /* create interval index */
-    switch( $freq ) {
-      case 'YEARLY':
-        $intervalix = $date['year'];
-        break;
-      case 'MONTHLY':
-        $intervalix = $date['year'].'-'.$date['month'];
-        break;
-      case 'WEEKLY':
-        $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
-        $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
-       break;
-      case 'DAILY':
-           default:
-        $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
-        break;
-    }
-    return $intervalix;
-  }
-/**
- * convert input format for exrule and rrule to internal format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.15 - 2012-01-31
- * @param array $rexrule
- * @return array
- */
-  public static function _setRexrule( $rexrule ) {
-    $input          = array();
-    if( empty( $rexrule ))
-      return $input;
-    foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
-      $rexrulelabel = strtoupper( $rexrulelabel );
-      if( 'UNTIL'  != $rexrulelabel )
-        $input[$rexrulelabel]   = $rexrulevalue;
-      else {
-        iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
-        if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time
-          $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 6 );
-        elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or date-time
-          $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 6 : 3;
-          $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_array( $rexrulevalue, $parno );
-        }
-        elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual datetime/date 2006-08-03 10:12:18
-          $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_string( $rexrulevalue );
-          unset( $input['$rexrulelabel']['unparsedtext'] );
-        }
-        if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
-          $input[$rexrulelabel]['tz'] = 'Z';
-      }
-    }
-            /* set recurrence rule specification in rfc2445 order */
-    $input2 = array();
-    if( isset( $input['FREQ'] ))
-      $input2['FREQ']       = $input['FREQ'];
-    if( isset( $input['UNTIL'] ))
-      $input2['UNTIL']      = $input['UNTIL'];
-    elseif( isset( $input['COUNT'] ))
-      $input2['COUNT']      = $input['COUNT'];
-    if( isset( $input['INTERVAL'] ))
-      $input2['INTERVAL']   = $input['INTERVAL'];
-    if( isset( $input['BYSECOND'] ))
-      $input2['BYSECOND']   = $input['BYSECOND'];
-    if( isset( $input['BYMINUTE'] ))
-      $input2['BYMINUTE']   = $input['BYMINUTE'];
-    if( isset( $input['BYHOUR'] ))
-      $input2['BYHOUR']     = $input['BYHOUR'];
-    if( isset( $input['BYDAY'] )) {
-      if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
-        $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
-      else {
-        foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
-          if( 'DAY'        == strtoupper( $BYDAYx ))
-             $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
-          elseif( !is_array( $BYDAYv )) {
-             $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
-          }
-          else {
-            foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
-              if( 'DAY'    == strtoupper( $BYDAYx2 ))
-                 $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
-              else
-                 $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
-            }
-          }
-        }
-      }
-    }
-    if( isset( $input['BYMONTHDAY'] ))
-      $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
-    if( isset( $input['BYYEARDAY'] ))
-      $input2['BYYEARDAY']  = $input['BYYEARDAY'];
-    if( isset( $input['BYWEEKNO'] ))
-      $input2['BYWEEKNO']   = $input['BYWEEKNO'];
-    if( isset( $input['BYMONTH'] ))
-      $input2['BYMONTH']    = $input['BYMONTH'];
-    if( isset( $input['BYSETPOS'] ))
-      $input2['BYSETPOS']   = $input['BYSETPOS'];
-    if( isset( $input['WKST'] ))
-      $input2['WKST']       = $input['WKST'];
-    return $input2;
-  }
-/**
- * convert format for input date to internal date with parameters
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-03-18
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param string $tz optional
- * @param array $params optional
- * @param string $caller optional
- * @param string $objName optional
- * @param string $tzid optional
- * @return array
- */
-  public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
-    $input = $parno = null;
-    $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
-    iCalUtilityFunctions::_strDate2arr( $year );
-    if( iCalUtilityFunctions::_isArrayDate( $year )) {
-      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
-      if( isset( $input['params']['TZID'] )) {
-        $input['params']['VALUE'] = 'DATE-TIME';
-        unset( $year['tz'] );
-      }
-      $hitval          = ( isset( $year['tz'] ) || isset( $year[6] )) ? 7 : 6;
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $year ), $parno );
-      $input['value']  = iCalUtilityFunctions::_date_time_array( $year, $parno );
-    }
-    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
-      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
-      if( isset( $input['params']['TZID'] )) {
-        $input['params']['VALUE'] = 'DATE-TIME';
-        unset( $year['tz'] );
-      }
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
-      $hitval          = ( isset( $year['tz'] )) ? 7 : 6;
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
-      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
-    }
-    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
-      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
-      if( isset( $input['params']['TZID'] )) {
-        $input['params']['VALUE'] = 'DATE-TIME';
-        $parno = 6;
-      }
-      elseif( $tzid && iCalUtilityFunctions::_isOffset( substr( $year, -7 ))) {
-        if(( in_array( substr( $year, -5, 1 ), array( '+', '-' ))) &&
-           (   '0000'  < substr( $year, -4 )) && (   '9999' >= substr( $year, -4 )))
-          $year = substr( $year, 0, ( strlen( $year ) - 5 ));
-        elseif(( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
-               ( '000000'  < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
-          $year = substr( $year, 0, ( strlen( $year ) - 7 ));
-        $parno = 6;
-      }
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
-      $input['value']  = iCalUtilityFunctions::_date_time_string( $year, $parno );
-      unset( $input['value']['unparsedtext'] );
-    }
-    else {
-      if( is_array( $params )) {
-        if( $localtime ) unset ( $params['VALUE'], $params['TZID'] );
-        $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
-      }
-      elseif( is_array( $tz )) {
-        $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
-        $tz = FALSE;
-      }
-      elseif( is_array( $hour )) {
-        $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
-        $hour = $min = $sec = $tz = FALSE;
-      }
-      if( isset( $input['params']['TZID'] )) {
-        $tz            = null;
-        $input['params']['VALUE'] = 'DATE-TIME';
-      }
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
-      $hitval          = ( !empty( $tz )) ? 7 : 6;
-      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
-      $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
-      if( 3 != $parno ) {
-        $input['value']['hour'] = ( $hour ) ? $hour : '0';
-        $input['value']['min']  = ( $min )  ? $min  : '0';
-        $input['value']['sec']  = ( $sec )  ? $sec  : '0';
-        if( !empty( $tz ))
-          $input['value']['tz'] = $tz;
-      }
-    }
-    if( 3 == $parno ) {
-      $input['params']['VALUE'] = 'DATE';
-      unset( $input['value']['tz'] );
-      unset( $input['params']['TZID'] );
-    }
-    elseif( isset( $input['params']['TZID'] ))
-      unset( $input['value']['tz'] );
-    if( $localtime )
-      unset( $input['value']['tz'], $input['params']['TZID'] );
-    elseif(( !isset( $input['params']['VALUE'] ) || ( $input['params']['VALUE'] != 'DATE' )) && !isset( $input['params']['TZID'] ) && $tzid )
-      $input['params']['TZID'] = $tzid;
-    if( isset( $input['value']['tz'] ))
-      $input['value']['tz'] = (string) $input['value']['tz'];
-    if( !empty( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && // real time zone in tz to TZID
-      ( !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))) {
-      $input['params']['TZID'] = $input['value']['tz'];
-      unset( $input['value']['tz'] );
-    }
-    if(  isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
-      if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {  // utc offset in TZID to tz
-        $input['value']['tz'] = $input['params']['TZID'];
-        unset( $input['params']['TZID'] );
-      }
-      elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z
-        $input['value']['tz'] = 'Z';
-        unset( $input['params']['TZID'] );
-      }
-    }
-    return $input;
-  }
-/**
- * convert format for input date (UTC) to internal date with parameters
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-01-19
- * @param mixed $year
- * @param mixed $month optional
- * @param int $day optional
- * @param int $hour optional
- * @param int $min optional
- * @param int $sec optional
- * @param array $params optional
- * @return array
- */
-  public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
-    $input = null;
-    iCalUtilityFunctions::_strDate2arr( $year );
-    if( iCalUtilityFunctions::_isArrayDate( $year )) {
-      $input['value']  = iCalUtilityFunctions::_date_time_array( $year, 7 );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
-    }
-    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
-      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
-    }
-    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
-      $input['value']  = iCalUtilityFunctions::_date_time_string( $year, 7 );
-      unset( $input['value']['unparsedtext'] );
-      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
-    }
-    else {
-      $input['value']  = array( 'year'  => $year
-                              , 'month' => $month
-                              , 'day'   => $day
-                              , 'hour'  => $hour
-                              , 'min'   => $min
-                              , 'sec'   => $sec );
-      $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
-    }
-    $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
-    if( !isset( $input['value']['hour'] ))
-      $input['value']['hour'] = 0;
-    if( !isset( $input['value']['min'] ))
-      $input['value']['min'] = 0;
-    if( !isset( $input['value']['sec'] ))
-      $input['value']['sec'] = 0;
-    if(  isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
-      if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {  // utc offset in TZID to tz
-        $input['value']['tz'] = $input['params']['TZID'];
-        unset( $input['params']['TZID'] );
-      }
-      elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z
-        $input['value']['tz'] = 'Z';
-        unset( $input['params']['TZID'] );
-      }
-    }
-    if( !isset( $input['value']['tz'] ) || !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
-      $input['value']['tz'] = 'Z';
-    return $input;
-  }
-/**
- * check index and set (an indexed) content in multiple value array
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.12 - 2011-01-03
- * @param array $valArr
- * @param mixed $value
- * @param array $params
- * @param array $defaults
- * @param int $index
- * @return void
- */
-  public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
-    if( !is_array( $valArr )) $valArr = array();
-    if( $index )
-      $index = $index - 1;
-    elseif( 0 < count( $valArr )) {
-      $keys  = array_keys( $valArr );
-      $index = end( $keys ) + 1;
-    }
-    else
-      $index = 0;
-    $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
-    ksort( $valArr );
-  }
-/**
- * set input (formatted) parameters- component property attributes
- *
- * default parameters can be set, if missing
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 1.x.x - 2007-05-01
- * @param array $params
- * @param array $defaults
- * @return array
- */
-  public static function _setParams( $params, $defaults=FALSE ) {
-    if( !is_array( $params))
-      $params = array();
-    $input = array();
-    foreach( $params as $paramKey => $paramValue ) {
-      if( is_array( $paramValue )) {
-        foreach( $paramValue as $pkey => $pValue ) {
-          if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
-            $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
-        }
-      }
-      elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
-        $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
-      if( 'VALUE' == strtoupper( $paramKey ))
-        $input['VALUE']                 = strtoupper( $paramValue );
-      else
-        $input[strtoupper( $paramKey )] = $paramValue;
-    }
-    if( is_array( $defaults )) {
-      foreach( $defaults as $paramKey => $paramValue ) {
-        if( !isset( $input[$paramKey] ))
-          $input[$paramKey] = $paramValue;
-      }
-    }
-    return (0 < count( $input )) ? $input : null;
-  }
-/**
- * step date, return updated date, array and timpstamp
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-10-18
- * @param array $date, date to step
- * @param int $timestamp
- * @param array $step, default array( 'day' => 1 )
- * @return void
- */
-  public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
-    foreach( $step as $stepix => $stepvalue )
-      $date[$stepix] += $stepvalue;
-    $timestamp  = iCalUtilityFunctions::_date2timestamp( $date );
-    $date       = iCalUtilityFunctions::_timestamp2date( $timestamp, 6 );
-    foreach( $date as $k => $v ) {
-      if( ctype_digit( $v ))
-        $date[$k] = (int) $v;
-    }
-  }
-/**
- * convert a date from specific string to array format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.8 - 2012-01-27
- * @param mixed $input
- * @return bool, TRUE on success
- */
-  public static function _strDate2arr( & $input ) {
-    if( is_array( $input ))
-      return FALSE;
-    if( 5 > strlen( (string) $input ))
-      return FALSE;
-    $work = $input;
-    if( 2 == substr_count( $work, '-' ))
-      $work = str_replace( '-', '', $work );
-    if( 2 == substr_count( $work, '/' ))
-      $work = str_replace( '/', '', $work );
-    if( !ctype_digit( substr( $work, 0, 8 )))
-      return FALSE;
-    if( !checkdate( (int) substr( $work,  4, 2 ), (int) substr( $work,  6, 2 ), (int) substr( $work,  0, 4 )))
-      return FALSE;
-    $temp = array( 'year'  => substr( $work,  0, 4 )
-                 , 'month' => substr( $work,  4, 2 )
-                 , 'day'   => substr( $work,  6, 2 ));
-    if( 8 == strlen( $work )) {
-      $input = $temp;
-      return TRUE;
-    }
-    if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
-      $work =  substr( $work, 9 );
-    elseif( ctype_digit( substr( $work, 8, 1 )))
-      $work = substr( $work, 8 );
-    else
-     return FALSE;
-    if( 2 == substr_count( $work, ':' ))
-      $work = str_replace( ':', '', $work );
-    if( !ctype_digit( substr( $work, 0, 4 )))
-      return FALSE;
-    $temp['hour']  = substr( $work, 0, 2 );
-    $temp['min']   = substr( $work, 2, 2 );
-    if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
-       (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
-      return FALSE;
-    if( ctype_digit( substr( $work, 4, 2 ))) {
-      $temp['sec'] = substr( $work, 4, 2 );
-      if((  0 > $temp['sec'] )  || ( $temp['sec']  > 59 ))
-        return FALSE;
-      $len = 6;
-    }
-    else {
-      $temp['sec'] = 0;
-      $len = 4;
-    }
-    if( $len < strlen( $work))
-      $temp['tz'] = trim( substr( $work, 6 ));
-    $input = $temp;
-    return TRUE;
-  }
-/**
- * convert timestamp to date array
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.4.16 - 2008-11-01
- * @param mixed $timestamp
- * @param int $parno
- * @return array
- */
-  public static function _timestamp2date( $timestamp, $parno=6 ) {
-    if( is_array( $timestamp )) {
-      if(( 7 == $parno ) && !empty( $timestamp['tz'] ))
-        $tz = $timestamp['tz'];
-      $timestamp = $timestamp['timestamp'];
-    }
-    $output = array( 'year'  => date( 'Y', $timestamp )
-                   , 'month' => date( 'm', $timestamp )
-                   , 'day'   => date( 'd', $timestamp ));
-    if( 3 != $parno ) {
-             $output['hour'] =  date( 'H', $timestamp );
-             $output['min']  =  date( 'i', $timestamp );
-             $output['sec']  =  date( 's', $timestamp );
-      if( isset( $tz ))
-        $output['tz'] = $tz;
-    }
-    return $output;
-  }
-/**
- * convert timestamp to duration in array format
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.6.23 - 2010-10-23
- * @param int $timestamp
- * @return array, duration format
- */
-  public static function _timestamp2duration( $timestamp ) {
-    $dur         = array();
-    $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
-    $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
-    $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
-    $timestamp   =              $timestamp % ( 24 * 60 * 60 );
-    $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
-    $timestamp   =              $timestamp % ( 60 * 60 );
-    $dur['min']  = (int) floor( $timestamp / ( 60 ));
-    $dur['sec']  = (int)        $timestamp % ( 60 );
-    return $dur;
-  }
-/**
- * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.14 - 2012-01-24
- * @param mixed  $date,   date to alter
- * @param string $tzFrom, PHP valid old timezone
- * @param string $tzTo,   PHP valid new timezone, default 'UTC'
- * @param string $format, date output format, default 'Ymd\THis'
- * @return bool
- */
-  public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
-    if( !class_exists( 'DateTime' ) || !class_exists( 'DateTimeZone' ))
-      return FALSE;
-    if( is_array( $date ) && isset( $date['timestamp'] ))
-       $timestamp = $date['timestamp'];
-    elseif( iCalUtilityFunctions::_isArrayDate( $date )) {
-      if(isset( $date['tz'] ))
-        unset( $date['tz'] );
-      $date  = iCalUtilityFunctions::_format_date_time( iCalUtilityFunctions::_date_time_array( $date ));
-      if( 'Z' == substr( $date, -1 ))
-        $date = substr( $date, 0, ( strlen( $date ) - 2 ));
-      if( FALSE === ( $timestamp = strtotime( $date )))
-        return FALSE;
-    }
-    elseif( FALSE === ( $timestamp = @strtotime( $date )))
-      return FALSE;
-    try {
-      $d = new DateTime( date( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tzFrom ));
-      $d->setTimezone( new DateTimeZone( $tzTo ));
-    }
-    catch (Exception $e) {
-      return FALSE;
-    }
-    $date = $d->format( $format );
-    return TRUE;
-  }
-/**
- * convert (numeric) local time offset, ("+" / "-")HHmm[ss], to seconds correcting localtime to GMT
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.4 - 2012-01-11
- * @param string $offset
- * @return integer
- */
-  public static function _tz2offset( $tz ) {
-    $tz           = trim( (string) $tz );
-    $offset       = 0;
-    if(((     5  != strlen( $tz )) && ( 7  != strlen( $tz ))) ||
-       ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
-       (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
-           (( 7  == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
-      return $offset;
-    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
-    $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
-    $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
-    $offset       = $hours2sec + $min2sec + $sec;
-    $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
-    return $offset;
-  }
-}
-/*********************************************************************************/
-/*          iCalcreator XML (rfc6321) helper functions                           */
-/*********************************************************************************/
-/**
- * format iCal XML output, rfc6321, using PHP SimpleXMLElement
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.1 - 2012-02-22
- * @param object $calendar, iCalcreator vcalendar instance reference
- * @return string
- */
-function iCal2XML( & $calendar ) {
-            /** fix an SimpleXMLElement instance and create root element */
-  $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
-  $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
-  $xmlstr    .= '</icalendar>';
-  $xml        = new SimpleXMLElement( $xmlstr );
-  $vcalendar  = $xml->addChild( 'vcalendar' );
-            /** fix calendar properties */
-  $properties = $vcalendar->addChild( 'properties' );
-  $calProps = array( 'prodid', 'version', 'calscale', 'method' );
-  foreach( $calProps as $calProp ) {
-    if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
-      _addXMLchild( $properties, $calProp, 'text', $content );
-  }
-  while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
-    _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
-  $langCal = $calendar->getConfig( 'language' );
-            /** prepare to fix components with properties */
-  $components    = $vcalendar->addChild( 'components' );
-  $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
-  $eventProps    = array( 'dtstamp', 'dtstart', 'uid',
-                          'class', 'created', 'description', 'geo', 'last-modified', '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',
-                          'x-prop' );
-  $todoProps     = array( 'dtstamp', 'uid',
-                          'class', 'completed', 'created', 'description', 'geo', 'last-modified', 'location', 'organizer', 'percent-complete', 'priority',
-                          'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule', 'dtstart', 'due', 'duration',
-                          'attach', 'attendee', 'categories', 'comment', 'contact', 'exdate', 'request-status', 'related-to', 'resources', 'rdate',
-                          'x-prop' );
-  $journalProps  = array( 'dtstamp', 'uid',
-                          'class', 'created', 'dtstart', 'last-modified', 'organizer', 'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule',
-                          'attach', 'attendee', 'categories', 'comment', 'contact',
-                          'description',
-                          'exdate', 'related-to', 'rdate', 'request-status',
-                          'x-prop' );
-  $freebusyProps = array( 'dtstamp', 'uid',
-                          'contact', 'dtstart', 'dtend', 'duration', 'organizer', 'url',
-                          'attendee', 'comment', 'freebusy', 'request-status',
-                          'x-prop' );
-  $timezoneProps = array( 'tzid',
-                          'last-modified', 'tzurl',
-                          'x-prop' );
-  $alarmProps    = array( 'action', 'description', 'trigger', 'summary',
-                          'attendee',
-                          'duration', 'repeat', 'attach',
-                          'x-prop' );
-  $stddghtProps  = array( 'dtstart', 'tzoffsetto', 'tzoffsetfrom',
-                          'rrule',
-                          'comment', 'rdate', 'tzname',
-                          'x-prop' );
-  foreach( $comps as $compName ) {
-    switch( $compName ) {
-      case 'vevent':
-        $props        = & $eventProps;
-        $subComps     = array( 'valarm' );
-        $subCompProps = & $alarmProps;
-        break;
-      case 'vtodo':
-        $props        = & $todoProps;
-        $subComps     = array( 'valarm' );
-        $subCompProps = & $alarmProps;
-        break;
-      case 'vjournal':
-        $props        = & $journalProps;
-        $subComps     = array();
-        $subCompProps = array();
-        break;
-      case 'vfreebusy':
-        $props        = & $freebusyProps;
-        $subComps     = array();
-        $subCompProps = array();
-        break;
-      case 'vtimezone':
-        $props        = & $timezoneProps;
-        $subComps     = array( 'standard', 'daylight' );
-        $subCompProps = & $stddghtProps;
-        break;
-    } // end switch( $compName )
-            /** fix component properties */
-    while( FALSE !== ( $component = $calendar->getComponent( $compName ))) {
-      $child      = $components->addChild( $compName );
-      $properties = $child->addChild( 'properties' );
-      $langComp = $component->getConfig( 'language' );
-      foreach( $props as $prop ) {
-        switch( $prop ) {
-          case 'attach':          // may occur multiple times, below
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
-              unset( $content['params']['VALUE'] );
-              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-            }
-            break;
-          case 'attendee':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
-                if( $langComp )
-                  $content['params']['LANGUAGE'] = $langComp;
-                elseif( $langCal )
-                  $content['params']['LANGUAGE'] = $langCal;
-              }
-              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
-            }
-            break;
-          case 'exdate':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
-              unset( $content['params']['VALUE'] );
-              foreach( $content['value'] as & $exDate ) {
-                if( (  isset( $exDate['tz'] ) &&  // fix UTC-date if offset set
-                       iCalUtilityFunctions::_isOffset( $exDate['tz'] ) &&
-                     ( 'Z' != $exDate['tz'] ))
-                 || (  isset( $content['params']['TZID'] ) &&
-                       iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                     ( 'Z' != $content['params']['TZID'] ))) {
-                  $offset = isset( $exDate['tz'] ) ? $exDate['tz'] : $content['params']['TZID'];
-                  $date = mktime( (int)  $exDate['hour'],
-                                  (int)  $exDate['min'],
-                                  (int) ($exDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                  (int)  $exDate['month'],
-                                  (int)  $exDate['day'],
-                                  (int)  $exDate['year'] );
-                  unset( $exDate['tz'] );
-                  $exDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                  unset( $exDate['unparsedtext'] );
-                }
-              }
-              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-            }
-            break;
-          case 'freebusy':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
-            break;
-          case 'request-status':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if( !isset( $content['params']['LANGUAGE'] )) {
-                if( $langComp )
-                  $content['params']['LANGUAGE'] = $langComp;
-                elseif( $langCal )
-                  $content['params']['LANGUAGE'] = $langCal;
-              }
-              _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
-            }
-            break;
-          case 'rdate':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              $type = 'date-time';
-              if( isset( $content['params']['VALUE'] )) {
-                if( 'DATE' == $content['params']['VALUE'] )
-                  $type = 'date';
-                elseif( 'PERIOD' == $content['params']['VALUE'] )
-                  $type = 'period';
-              }
-              if( 'period' == $type ) {
-                foreach( $content['value'] as & $rDates ) {
-                  if( (  isset( $rDates[0]['tz'] ) &&  // fix UTC-date if offset set
-                         iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) &&
-                       ( 'Z' != $rDates[0]['tz'] ))
-                   || (  isset( $content['params']['TZID'] ) &&
-                         iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                       ( 'Z' != $content['params']['TZID'] ))) {
-                    $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID'];
-                    $date = mktime( (int)  $rDates[0]['hour'],
-                                    (int)  $rDates[0]['min'],
-                                    (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                    (int)  $rDates[0]['month'],
-                                    (int)  $rDates[0]['day'],
-                                    (int)  $rDates[0]['year'] );
-                    unset( $rDates[0]['tz'] );
-                    $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                    unset( $rDates[0]['unparsedtext'] );
-                  }
-                  if( isset( $rDates[1]['year'] )) {
-                    if( (  isset( $rDates[1]['tz'] ) &&  // fix UTC-date if offset set
-                           iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) &&
-                         ( 'Z' != $rDates[1]['tz'] ))
-                     || (  isset( $content['params']['TZID'] ) &&
-                           iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                         ( 'Z' != $content['params']['TZID'] ))) {
-                      $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID'];
-                      $date = mktime( (int)  $rDates[1]['hour'],
-                                      (int)  $rDates[1]['min'],
-                                      (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                      (int)  $rDates[1]['month'],
-                                      (int)  $rDates[1]['day'],
-                                      (int)  $rDates[1]['year'] );
-                      unset( $rDates[1]['tz'] );
-                      $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                      unset( $rDates[1]['unparsedtext'] );
-                    }
-                  }
-                }
-              }
-              elseif( 'date-time' == $type ) {
-                foreach( $content['value'] as & $rDate ) {
-                  if( (  isset( $rDate['tz'] ) &&  // fix UTC-date if offset set
-                         iCalUtilityFunctions::_isOffset( $rDate['tz'] ) &&
-                       ( 'Z' != $rDate['tz'] ))
-                   || (  isset( $content['params']['TZID'] ) &&
-                         iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                       ( 'Z' != $content['params']['TZID'] ))) {
-                    $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID'];
-                    $date = mktime( (int)  $rDate['hour'],
-                                    (int)  $rDate['min'],
-                                    (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                    (int)  $rDate['month'],
-                                    (int)  $rDate['day'],
-                                    (int)  $rDate['year'] );
-                    unset( $rDate['tz'] );
-                    $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                    unset( $rDate['unparsedtext'] );
-                  }
-                }
-              }
-              unset( $content['params']['VALUE'] );
-              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-            }
-            break;
-          case 'categories':
-          case 'comment':
-          case 'contact':
-          case 'description':
-          case 'related-to':
-          case 'resources':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
-                if( $langComp )
-                  $content['params']['LANGUAGE'] = $langComp;
-                elseif( $langCal )
-                  $content['params']['LANGUAGE'] = $langCal;
-              }
-              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
-            }
-            break;
-          case 'x-prop':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
-            break;
-          case 'created':         // single occurence below, if set
-          case 'completed':
-          case 'dtstamp':
-          case 'last-modified':
-            $utcDate = TRUE;
-          case 'dtstart':
-          case 'dtend':
-          case 'due':
-          case 'recurrence-id':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) {
-                $type = 'date';
-                unset( $content['value']['hour'], $content['value']['min'], $content['value']['sec'] );
-              }
-              else {
-                $type = 'date-time';
-                if( isset( $utcDate ) && !isset( $content['value']['tz'] ))
-                  $content['value']['tz'] = 'Z';
-                if( (  isset( $content['value']['tz'] ) &&  // fix UTC-date if offset set
-                       iCalUtilityFunctions::_isOffset( $content['value']['tz'] ) &&
-                     ( 'Z' != $content['value']['tz'] ))
-                 || (  isset( $content['params']['TZID'] ) &&
-                       iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                     ( 'Z' != $content['params']['TZID'] ))) {
-                  $offset = isset( $content['value']['tz'] ) ? $content['value']['tz'] : $content['params']['TZID'];
-                  $date = mktime( (int)  $content['value']['hour'],
-                                  (int)  $content['value']['min'],
-                                  (int) ($content['value']['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                  (int)  $content['value']['month'],
-                                  (int)  $content['value']['day'],
-                                  (int)  $content['value']['year'] );
-                  unset( $content['value']['tz'], $content['params']['TZID'] );
-                  $content['value'] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                  unset( $content['value']['unparsedtext'] );
-                }
-                elseif( isset( $content['value']['tz'] ) && !empty( $content['value']['tz'] ) &&
-                      ( 'Z' != $content['value']['tz'] ) && !isset( $content['params']['TZID'] )) {
-                  $content['params']['TZID'] = $content['value']['tz'];
-                  unset( $content['value']['tz'] );
-                }
-              }
-              unset( $content['params']['VALUE'] );
-              if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
-                unset( $content['params']['TZID'] );
-              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-            }
-            unset( $utcDate );
-            break;
-          case 'duration':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
-            break;
-          case 'rrule':
-            while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
-            break;
-          case 'class':
-          case 'location':
-          case 'status':
-          case 'summary':
-          case 'transp':
-          case 'tzid':
-          case 'uid':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
-                if( $langComp )
-                  $content['params']['LANGUAGE'] = $langComp;
-                elseif( $langCal )
-                  $content['params']['LANGUAGE'] = $langCal;
-              }
-              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
-            }
-            break;
-          case 'geo':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
-            break;
-          case 'organizer':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
-              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
-                if( $langComp )
-                  $content['params']['LANGUAGE'] = $langComp;
-                elseif( $langCal )
-                  $content['params']['LANGUAGE'] = $langCal;
-              }
-              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
-            }
-            break;
-          case 'percent-complete':
-          case 'priority':
-          case 'sequence':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
-            break;
-          case 'tzurl':
-          case 'url':
-            if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
-              _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
-            break;
-        } // end switch( $prop )
-      } // end foreach( $props as $prop )
-            /** fix subComponent properties, if any */
-      foreach( $subComps as $subCompName ) {
-        while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) {
-          $child2     = $child->addChild( $subCompName );
-          $properties = $child2->addChild( 'properties' );
-          $langComp   = $subcomp->getConfig( 'language' );
-          foreach( $subCompProps as $prop ) {
-            switch( $prop ) {
-              case 'attach':          // may occur multiple times, below
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
-                  unset( $content['params']['VALUE'] );
-                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-                }
-                break;
-              case 'attendee':
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
-                    if( $langComp )
-                      $content['params']['LANGUAGE'] = $langComp;
-                    elseif( $langCal )
-                      $content['params']['LANGUAGE'] = $langCal;
-                  }
-                  _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
-                }
-                break;
-              case 'comment':
-              case 'tzname':
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  if( !isset( $content['params']['LANGUAGE'] )) {
-                    if( $langComp )
-                      $content['params']['LANGUAGE'] = $langComp;
-                    elseif( $langCal )
-                      $content['params']['LANGUAGE'] = $langCal;
-                  }
-                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
-                }
-                break;
-              case 'rdate':
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  $type = 'date-time';
-                  if( isset( $content['params']['VALUE'] )) {
-                    if( 'DATE' == $content['params']['VALUE'] )
-                      $type = 'date';
-                    elseif( 'PERIOD' == $content['params']['VALUE'] )
-                      $type = 'period';
-                  }
-                  if( 'period' == $type ) {
-                    foreach( $content['value'] as & $rDates ) {
-                      if( (  isset( $rDates[0]['tz'] ) &&  // fix UTC-date if offset set
-                             iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) &&
-                          ( 'Z' != $rDates[0]['tz'] ))
-                       || (  isset( $content['params']['TZID'] ) &&
-                             iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                           ( 'Z' != $content['params']['TZID'] ))) {
-                        $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID'];
-                        $date = mktime( (int)  $rDates[0]['hour'],
-                                        (int)  $rDates[0]['min'],
-                                        (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                        (int)  $rDates[0]['month'],
-                                        (int)  $rDates[0]['day'],
-                                        (int)  $rDates[0]['year'] );
-                        unset( $rDates[0]['tz'] );
-                        $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                        unset( $rDates[0]['unparsedtext'] );
-                      }
-                      if( isset( $rDates[1]['year'] )) {
-                        if( (  isset( $rDates[1]['tz'] ) &&  // fix UTC-date if offset set
-                               iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) &&
-                             ( 'Z' != $rDates[1]['tz'] ))
-                         || (  isset( $content['params']['TZID'] ) &&
-                               iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                             ( 'Z' != $content['params']['TZID'] ))) {
-                          $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID'];
-                          $date = mktime( (int)  $rDates[1]['hour'],
-                                          (int)  $rDates[1]['min'],
-                                          (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                          (int)  $rDates[1]['month'],
-                                          (int)  $rDates[1]['day'],
-                                          (int)  $rDates[1]['year'] );
-                          unset( $rDates[1]['tz'] );
-                          $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                          unset( $rDates[1]['unparsedtext'] );
-                        }
-                      }
-                    }
-                  }
-                  elseif( 'date-time' == $type ) {
-                    foreach( $content['value'] as & $rDate ) {
-                      if( (  isset( $rDate['tz'] ) &&  // fix UTC-date if offset set
-                             iCalUtilityFunctions::_isOffset( $rDate['tz'] ) &&
-                           ( 'Z' != $rDate['tz'] ))
-                       || (  isset( $content['params']['TZID'] ) &&
-                             iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
-                           ( 'Z' != $content['params']['TZID'] ))) {
-                        $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID'];
-                        $date = mktime( (int)  $rDate['hour'],
-                                        (int)  $rDate['min'],
-                                        (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
-                                        (int)  $rDate['month'],
-                                        (int)  $rDate['day'],
-                                        (int)  $rDate['year'] );
-                        unset( $rDate['tz'] );
-                        $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
-                        unset( $rDate['unparsedtext'] );
-                      }
-                    }
-                  }
-                  unset( $content['params']['VALUE'] );
-                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-                }
-                break;
-              case 'x-prop':
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
-                  _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
-                break;
-              case 'action':      // single occurence below, if set
-              case 'description':
-              case 'summary':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
-                    if( $langComp )
-                      $content['params']['LANGUAGE'] = $langComp;
-                    elseif( $langCal )
-                      $content['params']['LANGUAGE'] = $langCal;
-                  }
-                  _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
-                }
-                break;
-              case 'dtstart':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
-                  _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
-                }
-                break;
-              case 'duration':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
-                  _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
-                break;
-              case 'repeat':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
-                  _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
-                break;
-              case 'trigger':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
-                  if( isset( $content['value']['year'] )   &&
-                      isset( $content['value']['month'] )  &&
-                      isset( $content['value']['day'] ))
-                    $type = 'date-time';
-                  else
-                    $type = 'duration';
-                  _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
-                }
-                break;
-              case 'tzoffsetto':
-              case 'tzoffsetfrom':
-                if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
-                  _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
-                break;
-              case 'rrule':
-                while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
-                  _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
-                break;
-            } // switch( $prop )
-          } // end foreach( $subCompProps as $prop )
-        } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName )))
-      } // end foreach( $subCombs as $subCompName )
-    } // end while( FALSE !== ( $component = $calendar->getComponent( $compName )))
-  } // end foreach( $comps as $compName)
-  return $xml->asXML();
-}
-/**
- * Add children to a SimpleXMLelement
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.1 - 2012-01-16
- * @param object $parent,  reference to a SimpleXMLelement node
- * @param string $name,    new element node name
- * @param string $type,    content type, subelement(-s) name
- * @param string $content, new subelement content
- * @param array  $params,  new element 'attributes'
- * @return void
- */
-function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
-            /** create new child node */
-  $child = $parent->addChild( strtolower( $name ));
-            /** fix attributes */
-  if( is_array( $content ) && isset( $content['fbtype'] )) {
-    $params['FBTYPE'] = $content['fbtype'];
-    unset( $content['fbtype'] );
-  }
-  if( isset( $params['VALUE'] ))
-    unset( $params['VALUE'] );
-  if(( 'trigger' == $name ) && ( 'duration' == $type ) && ( TRUE !== $content['relatedStart'] ))
-    $params['RELATED'] = 'END';
-  if( !empty( $params )) {
-    $parameters = $child->addChild( 'parameters' );
-    foreach( $params as $param => $parVal ) {
-      $param = strtolower( $param );
-      if( 'x-' == substr( $param, 0, 2  )) {
-        $p1 = $parameters->addChild( $param );
-        $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
-      }
-      else {
-        $p1 = $parameters->addChild( $param );
-        switch( $param ) {
-          case 'altrep':
-          case 'dir':            $ptype = 'uri';            break;
-          case 'delegated-from':
-          case 'delegated-to':
-          case 'member':
-          case 'sent-by':        $ptype = 'cal-address';    break;
-          case 'rsvp':           $ptype = 'boolean';        break ;
-          default:               $ptype = 'text';           break;
-        }
-        if( is_array( $parVal )) {
-          foreach( $parVal as $pV )
-            $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
-        }
-        else
-          $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
-      }
-    }
-  }
-  if( empty( $content ) && ( '0' != $content ))
-    return;
-            /** store content */
-  switch( $type ) {
-    case 'binary':
-      $v = $child->addChild( $type, $content );
-      break;
-    case 'boolean':
-      break;
-    case 'cal-address':
-      $v = $child->addChild( $type, $content );
-      break;
-    case 'date':
-      if( array_key_exists( 'year', $content ))
-        $content = array( $content );
-      foreach( $content as $date ) {
-        $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
-        $v = $child->addChild( $type, $str );
-      }
-      break;
-    case 'date-time':
-      if( array_key_exists( 'year', $content ))
-        $content = array( $content );
-      foreach( $content as $dt ) {
-        if( !isset( $dt['hour'] )) $dt['hour'] = 0;
-        if( !isset( $dt['min'] ))  $dt['min']  = 0;
-        if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
-        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
-        if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
-          $str .= 'Z';
-        $v = $child->addChild( $type, $str );
-      }
-      break;
-    case 'duration':
-      $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
-      $v = $child->addChild( $type, $output.iCalUtilityFunctions::_format_duration( $content ) );
-      break;
-    case 'geo':
-      $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
-      $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
-      break;
-    case 'integer':
-      $v = $child->addChild( $type, $content );
-      break;
-    case 'period':
-      if( !is_array( $content ))
-        break;
-      foreach( $content as $period ) {
-        $v1 = $child->addChild( $type );
-        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
-        if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
-          $str .= 'Z';
-        $v2 = $v1->addChild( 'start', $str );
-        if( array_key_exists( 'year', $period[1] )) {
-          $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
-          if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
-            $str .= 'Z';
-          $v2 = $v1->addChild( 'end', $str );
-        }
-        else
-          $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_format_duration( $period[1] ));
-      }
-      break;
-    case 'recur':
-      foreach( $content as $rulelabel => $rulevalue ) {
-        $rulelabel = strtolower( $rulelabel );
-        switch( $rulelabel ) {
-          case 'until':
-            if( isset( $rulevalue['hour'] ))
-              $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
-            else
-              $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
-            $v = $child->addChild( $rulelabel, $str );
-            break;
-          case 'bysecond':
-          case 'byminute':
-          case 'byhour':
-          case 'bymonthday':
-          case 'byyearday':
-          case 'byweekno':
-          case 'bymonth':
-          case 'bysetpos': {
-            if( is_array( $rulevalue )) {
-              foreach( $rulevalue as $vix => $valuePart )
-                $v = $child->addChild( $rulelabel, $valuePart );
-            }
-            else
-              $v = $child->addChild( $rulelabel, $rulevalue );
-            break;
-          }
-          case 'byday': {
-            if( isset( $rulevalue['DAY'] )) {
-              $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
-              $str .= $rulevalue['DAY'];
-              $p    = $child->addChild( $rulelabel, $str );
-            }
-            else {
-              foreach( $rulevalue as $valuePart ) {
-                if( isset( $valuePart['DAY'] )) {
-                  $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
-                  $str .= $valuePart['DAY'];
-                  $p    = $child->addChild( $rulelabel, $str );
-                }
-                else
-                  $p    = $child->addChild( $rulelabel, $valuePart );
-              }
-            }
-            break;
-          }
-          case 'freq':
-          case 'count':
-          case 'interval':
-          case 'wkst':
-          default:
-            $p = $child->addChild( $rulelabel, $rulevalue );
-            break;
-        } // end switch( $rulelabel )
-      } // end foreach( $content as $rulelabel => $rulevalue )
-      break;
-    case 'rstatus':
-      $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
-      $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
-      if( isset( $content['extdata'] ))
-        $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
-      break;
-    case 'text':
-      if( !is_array( $content ))
-        $content = array( $content );
-      foreach( $content as $part )
-        $v = $child->addChild( $type, htmlspecialchars( $part ));
-      break;
-    case 'time':
-      break;
-    case 'uri':
-      $v = $child->addChild( $type, $content );
-      break;
-    case 'utc-offset':
-      if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
-        $str     = substr( $content, 0, 1 );
-        $content = substr( $content, 1 );
-      }
-      else
-        $str     = '+';
-      $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
-      if( 4 < strlen( $content ))
-        $str .= ':'.substr( $content, 4 );
-      $v = $child->addChild( $type, $str );
-      break;
-    case 'unknown':
-    default:
-      if( is_array( $content ))
-        $content = implode( '', $content );
-      $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
-      break;
-  }
-}
-/**
- * parse xml string into iCalcreator instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since 2.11.2 - 2012-01-31
- * @param  string $xmlstr
- * @param  array  $iCalcfg iCalcreator config array (opt)
- * @return mixed  iCalcreator instance or FALSE on error
- */
-function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
-  libxml_use_internal_errors( TRUE );
-  $xml = simplexml_load_string( $xmlstr );
-  if( !$xml ) {
-    $str    = '';
-    $return = FALSE;
-    foreach( libxml_get_errors() as $error ) {
-      switch ( $error->level ) {
-        case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break;
-        case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break;
-        case LIBXML_ERR_WARNING:
-        default:                 $str .= ' WARNING '; break;
-      }
-      $str .= PHP_EOL.'Error when loading XML';
-      if( !empty( $error->file ))
-        $str .= ',  file:'.$error->file.', ';
-      $str .= ', line:'.$error->line;
-      $str .= ', ('.$error->code.') '.$error->message;
-    }
-    error_log( $str );
-    if( LIBXML_ERR_WARNING != $error->level )
-      return $return;
-    libxml_clear_errors();
-  }
-  return xml2iCal( $xml, $iCalcfg );
-}
-/**
- * parse xml file into iCalcreator instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since  2.11.2 - 2012-01-20
- * @param  string $xmlfile
- * @param  array$iCalcfg iCalcreator config array (opt)
- * @return mixediCalcreator instance or FALSE on error
- */
-function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
-  libxml_use_internal_errors( TRUE );
-  $xml = simplexml_load_file( $xmlfile );
-  if( !$xml ) {
-    $str = '';
-    foreach( libxml_get_errors() as $error ) {
-      switch ( $error->level ) {
-        case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break;
-        case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break;
-        case LIBXML_ERR_WARNING:
-        default:                 $str .= 'WARNING '; break;
-      }
-      $str .= 'Failed loading XML'.PHP_EOL;
-      if( !empty( $error->file ))
-        $str .= ' file:'.$error->file.', ';
-      $str .= 'line:'.$error->line.PHP_EOL;
-      $str .= '('.$error->code.') '.$error->message.PHP_EOL;
-    }
-    error_log( $str );
-    if( LIBXML_ERR_WARNING != $error->level )
-      return FALSE;
-    libxml_clear_errors();
-  }
-  return xml2iCal( $xml, $iCalcfg );
-}
-/**
- * parse SimpleXMLElement xCal into iCalcreator instance
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since  2.11.2 - 2012-01-27
- * @param  object $xmlobj  SimpleXMLElement
- * @param  array  $iCalcfg iCalcreator config array (opt)
- * @return mixed  iCalcreator instance or FALSE on error
- */
-function & XML2iCal( $xmlobj, $iCalcfg=array()) {
-  $iCal = new vcalendar( $iCalcfg );
-  foreach( $xmlobj->children() as $icalendar ) { // vcalendar
-    foreach( $icalendar->children() as $calPart ) { // calendar properties and components
-      if( 'components' == $calPart->getName()) {
-        foreach( $calPart->children() as $component ) { // single components
-          if( 0 < $component->count())
-            _getXMLComponents( $iCal, $component );
-        }
-      }
-      elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) {
-        foreach( $calPart->children() as $calProp ) { // calendar properties
-         $propName = $calProp->getName();
-          if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 )))
-            continue;
-          $params = array();
-          foreach( $calProp->children() as $calPropElem ) { // single calendar property
-            if( 'parameters' == $calPropElem->getName())
-              $params = _getXMLParams( $calPropElem );
-            else
-              $iCal->setProperty( $propName, reset( $calPropElem ), $params );
-          } // end foreach( $calProp->children() as $calPropElem )
-        } // end foreach( $calPart->properties->children() as $calProp )
-      } // end if( 0 < $calPart->properties->count())
-    } // end foreach( $icalendar->children() as $calPart )
-  } // end foreach( $xmlobj->children() as $icalendar )
-  return $iCal;
-}
-/**
- * parse SimpleXMLElement xCal property parameters and return iCalcreator property parameter array
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since  2.11.2 - 2012-01-15
- * @param  object $parameters SimpleXMLElement
- * @return array  iCalcreator property parameter array
- */
-function _getXMLParams( & $parameters ) {
-  if( 1 > $parameters->count())
-    return array();
-  $params = array();
-  foreach( $parameters->children() as $parameter ) { // single parameter key
-    $key   = strtoupper( $parameter->getName());
-    $value = array();
-    foreach( $parameter->children() as $paramValue ) // skip parameter value type
-      $value[] = reset( $paramValue );
-    if( 2 > count( $value ))
-      $params[$key] = html_entity_decode( reset( $value ));
-    else
-      $params[$key] = $value;
-  }
-  return $params;
-}
-/**
- * parse SimpleXMLElement xCal components, create iCalcreator component and update
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since  2.11.2 - 2012-01-15
- * @param  array  $iCal iCalcreator calendar instance
- * @param  object $component SimpleXMLElement
- * @return void
- */
-function _getXMLComponents( & $iCal, & $component ) {
-  $compName = $component->getName();
-  $comp     = & $iCal->newComponent( $compName );
-  $subComponents = array( 'valarm', 'standard', 'daylight' );
-  foreach( $component->children() as $compPart ) { // properties and (opt) subComponents
-    if( 1 > $compPart->count())
-      continue;
-    if( in_array( $compPart->getName(), $subComponents ))
-      _getXMLComponents( $comp, $compPart );
-    elseif( 'properties' == $compPart->getName()) {
-      foreach( $compPart->children() as $property ) // properties as single property
-        _getXMLProperties( $comp, $property );
-    }
-  } // end foreach( $component->children() as $compPart )
-}
-/**
- * parse SimpleXMLElement xCal property, create iCalcreator component property
- *
- * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @since  2.11.2 - 2012-01-27
- * @param  array  $iCal iCalcreator calendar instance
- * @param  object $component SimpleXMLElement
- * @return void
- */
-function _getXMLProperties( & $iCal, & $property ) {
-  $propName  = $property->getName();
-  $value     = $params = array();
-  $valueType = '';
-  foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s)
-    $valueType = $propPart->getName();
-    if( 'parameters' == $valueType) {
-      $params = _getXMLParams( $propPart );
-      continue;
-    }
-    switch( $valueType ) {
-      case 'binary':
-        $value = reset( $propPart );
-        break;
-      case 'boolean':
-        break;
-      case 'cal-address':
-        $value = reset( $propPart );
-        break;
-      case 'date':
-        $params['VALUE'] = 'DATE';
-      case 'date-time':
-        if(( 'exdate' == $propName ) || ( 'rdate' == $propName ))
-          $value[] = reset( $propPart );
-        else
-          $value = reset( $propPart );
-        break;
-      case 'duration':
-        $value = reset( $propPart );
-        break;
-//        case 'geo':
-      case 'latitude':
-      case 'longitude':
-        $value[$valueType] = reset( $propPart );
-        break;
-      case 'integer':
-        $value = reset( $propPart );
-        break;
-      case 'period':
-        if( 'rdate' == $propName )
-          $params['VALUE'] = 'PERIOD';
-        $pData = array();
-        foreach( $propPart->children() as $periodPart )
-          $pData[] = reset( $periodPart );
-        if( !empty( $pData ))
-          $value[] = $pData;
-        break;
-//        case 'rrule':
-      case 'freq':
-      case 'count':
-      case 'until':
-      case 'interval':
-      case 'wkst':
-        $value[$valueType] = reset( $propPart );
-        break;
-      case 'bysecond':
-      case 'byminute':
-      case 'byhour':
-      case 'bymonthday':
-      case 'byyearday':
-      case 'byweekno':
-      case 'bymonth':
-      case 'bysetpos':
-        $value[$valueType][] = reset( $propPart );
-        break;
-      case 'byday':
-        $byday = reset( $propPart );
-        if( 2 == strlen( $byday ))
-          $value[$valueType][] = array( 'DAY' => $byday );
-        else {
-          $day = substr( $byday, -2 );
-          $key = substr( $byday, 0, ( strlen( $byday ) - 2 ));
-          $value[$valueType][] = array( $key, 'DAY' => $day );
-        }
-        break;
-//      case 'rstatus':
-      case 'code':
-        $value[0] = reset( $propPart );
-        break;
-      case 'description':
-        $value[1] = reset( $propPart );
-        break;
-      case 'data':
-        $value[2] = reset( $propPart );
-        break;
-      case 'text':
-        $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart ));
-        $value['text'][] = html_entity_decode( $text );
-        break;
-      case 'time':
-        break;
-      case 'uri':
-        $value = reset( $propPart );
-        break;
-      case 'utc-offset':
-        $value = str_replace( ':', '', reset( $propPart ));
-        break;
-      case 'unknown':
-      default:
-        $value = html_entity_decode( reset( $propPart ));
-        break;
-    } // end switch( $valueType )
-  } // end  foreach( $property->children() as $propPart )
-  if( 'freebusy' == $propName ) {
-    $fbtype = $params['FBTYPE'];
-    unset( $params['FBTYPE'] );
-    $iCal->setProperty( $propName, $fbtype, $value, $params );
-  }
-  elseif( 'geo' == $propName )
-    $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
-  elseif( 'request-status' == $propName ) {
-    if( !isset( $value[2] ))
-      $value[2] = FALSE;
-    $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params );
-  }
-  else {
-    if( isset( $value['text'] ) && is_array( $value['text'] )) {
-      if(( 'categories' == $propName ) || ( 'resources' == $propName ))
-        $value = $value['text'];
-      else
-        $value = reset( $value['text'] );
-    }
-    $iCal->setProperty( $propName, $value, $params );
-  }
-}
-/**
- * Additional functions to use with vtimezone components
- * For use with
- * iCalcreator (kigkonsult.se/iCalcreator/index.php)
- * copyright (c) 2011 Yitzchok Lavi
- * icalcreator@onebigsystem.com
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-/**
- * Additional functions to use with vtimezone components
- *
- * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
- *
- * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
- *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
- * @version 1.0.2 - 2011-02-24
- *
- */
-/**
- * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
- * timezone, according to the VTIMEZONE information in the input array.
- *
- * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
- * $param string $tzid,           time zone identifier
- * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
- * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
- *
- */
-function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
-    if( is_array( $timestamp )) {
-//$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] );
-      $timestamp = gmmktime(
-            $timestamp['hour'],
-            $timestamp['min'],
-            $timestamp['sec'],
-            $timestamp['month'],
-            $timestamp['day'],
-            $timestamp['year']
-            ) ;
-//echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ###
-    }
-    $tzoffset = array();
-    // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
-    $tzoffset['offsetHis'] = '+0000';
-    $tzoffset['offsetSec'] = 0;
-    $tzoffset['tzname']    = '?';
-    if( !isset( $timezonesarray[$tzid] ))
-      return $tzoffset;
-    $tzdatearray = $timezonesarray[$tzid];
-    if ( is_array($tzdatearray) ) {
-        sort($tzdatearray); // just in case
-        if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
-            // our date is before the first change
-            $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
-            $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
-            $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
-        } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
-            // our date is after the last change (we do this so our scan can stop at the last record but one)
-            $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
-            $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
-            $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
-        } else {
-            // our date somewhere in between
-            // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
-            // we don't include the last date in our loop as there isn't one after it to check
-            for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
-                if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
-                    $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
-                    $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
-                    $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
-                    break;
-                }
-            }
-        }
-    }
-    return $tzoffset;
-}
-/**
- * Returns an array containing all the timezone data in the vcalendar object
- *
- * @param object $vcalendar, iCalcreator calendar instance
- * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
- *                based on the timezone data in the vcalendar object
- *
- */
-function getTimezonesAsDateArrays($vcalendar) {
-    $timezonedata = array();
-    while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
-        $tzid       = $vtz->getProperty('tzid');
-        $alltzdates = array();
-        while ( $vtzc = $vtz->getComponent( 'standard' )) {
-            $newtzdates = expandTimezoneDates($vtzc);
-            $alltzdates = array_merge($alltzdates, $newtzdates);
-        }
-        while ( $vtzc = $vtz->getComponent( 'daylight' )) {
-            $newtzdates = expandTimezoneDates($vtzc);
-            $alltzdates = array_merge($alltzdates, $newtzdates);
-        }
-        sort($alltzdates);
-        $timezonedata[$tzid] = $alltzdates;
-    }
-    return $timezonedata;
-}
-/**
- * Returns an array containing time zone data from vtimezone standard/daylight instances
- *
- * @param object $vtzc, an iCalcreator calendar standard/daylight instance
- * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
- *
- */
-function expandTimezoneDates($vtzc) {
-    $tzdates = array();
-    // prepare time zone "description" to attach to each change
-    $tzbefore = array();
-    $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
-    $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
-    if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
-      $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
-    $tzafter = array();
-    $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
-    $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
-    if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
-      $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
-    if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
-      $tzafter['tzname'] = $tzafter['offsetHis'];
-    // find out where to start from
-    $dtstart = $vtzc->getProperty('dtstart');
-    $dtstarttimestamp = mktime(
-            $dtstart['hour'],
-            $dtstart['min'],
-            $dtstart['sec'],
-            $dtstart['month'],
-            $dtstart['day'],
-            $dtstart['year']
-            ) ;
-    if( !isset( $dtstart['unparsedtext'] )) // ??
-      $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
-    if ( $dtstarttimestamp == 0 ) {
-        // it seems that the dtstart string may not have parsed correctly
-        // let's set a timestamp starting from 1902, using the time part of the original string
-        // so that the time will change at the right time of day
-        // at worst we'll get midnight again
-        $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
-        $dtstarttimestamp = strtotime("19020101",0);
-        $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
-    }
-    // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
-    $diff  = -1 * $tzbefore['offsetSec'];
-    $dtstarttimestamp += $diff;
-                // add this (start) change to the array of changes
-    $tzdates[] = array(
-        'timestamp' => $dtstarttimestamp,
-        'tzbefore'  => $tzbefore,
-        'tzafter'   => $tzafter
-        );
-    $datearray = getdate($dtstarttimestamp);
-    // save original array to use time parts, because strtotime (used below) apparently loses the time
-    $changetime = $datearray ;
-    // generate dates according to an RRULE line
-    $rrule = $vtzc->getProperty('rrule') ;
-    if ( is_array($rrule) ) {
-        if ( $rrule['FREQ'] == 'YEARLY' ) {
-            // calculate transition dates starting from DTSTART
-            $offsetchangetimestamp = $dtstarttimestamp;
-            // calculate transition dates until 10 years in the future
-            $stoptimestamp = strtotime("+10 year",time());
-            // if UNTIL is set, calculate until then (however far ahead)
-            if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
-                $stoptimestamp = mktime(
-                    $rrule['UNTIL']['hour'],
-                    $rrule['UNTIL']['min'],
-                    $rrule['UNTIL']['sec'],
-                    $rrule['UNTIL']['month'],
-                    $rrule['UNTIL']['day'],
-                    $rrule['UNTIL']['year']
-                    ) ;
-            }
-            $count = 0 ;
-            $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
-            $daynames = array(
-                        'SU' => 'Sunday',
-                        'MO' => 'Monday',
-                        'TU' => 'Tuesday',
-                        'WE' => 'Wednesday',
-                        'TH' => 'Thursday',
-                        'FR' => 'Friday',
-                        'SA' => 'Saturday'
-                        );
-            // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
-            while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
-                // break up the timestamp into its parts
-                $datearray = getdate($offsetchangetimestamp);
-                if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
-                    // set the month
-                    $datearray['mon'] = $rrule['BYMONTH'] ;
-                }
-                if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
-                    // set specific day of month
-                    $datearray['mday']  = $rrule['BYMONTHDAY'];
-                } elseif ( is_array($rrule['BYDAY']) ) {
-                    // find the Xth WKDAY in the month
-                    // the starting point for this process is the first of the month set above
-                    $datearray['mday'] = 1 ;
-                    // turn $datearray as it is now back into a timestamp
-                    $offsetchangetimestamp = mktime(
-                        $datearray['hours'],
-                        $datearray['minutes'],
-                        $datearray['seconds'],
-                        $datearray['mon'],
-                        $datearray['mday'],
-                        $datearray['year']
-                            );
-                    if ($rrule['BYDAY'][0] > 0) {
-                        // to find Xth WKDAY in month, we find last WKDAY in month before
-                        // we do that by finding first WKDAY in this month and going back one week
-                        // then we add X weeks (below)
-                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
-                        $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
-                    } else {
-                        // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
-                        // we do that by going forward one month and going to WKDAY there
-                        // then we subtract X weeks (below)
-                        $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
-                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
-                    }
-                    // now move forward or back the appropriate number of weeks, into the month we want
-                    $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
-                    $datearray = getdate($offsetchangetimestamp);
-                }
-                // convert the date parts back into a timestamp, setting the time parts according to the
-                // original time data which we stored
-                $offsetchangetimestamp = mktime(
-                    $changetime['hours'],
-                    $changetime['minutes'],
-                    $changetime['seconds'] + $diff,
-                    $datearray['mon'],
-                    $datearray['mday'],
-                    $datearray['year']
-                        );
-                // add this change to the array of changes
-                $tzdates[] = array(
-                    'timestamp' => $offsetchangetimestamp,
-                    'tzbefore'  => $tzbefore,
-                    'tzafter'   => $tzafter
-                    );
-                // update counters (timestamp and count)
-                $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
-                $count += 1 ;
-            }
-        }
-    }
-    // generate dates according to RDATE lines
-    while ($rdates = $vtzc->getProperty('rdate')) {
-        if ( is_array($rdates) ) {
-
-            foreach ( $rdates as $rdate ) {
-                // convert the explicit change date to a timestamp
-                $offsetchangetimestamp = mktime(
-                        $rdate['hour'],
-                        $rdate['min'],
-                        $rdate['sec'] + $diff,
-                        $rdate['month'],
-                        $rdate['day'],
-                        $rdate['year']
-                        ) ;
-                // add this change to the array of changes
-                $tzdates[] = array(
-                    'timestamp' => $offsetchangetimestamp,
-                    'tzbefore'  => $tzbefore,
-                    'tzafter'   => $tzafter
-                    );
-            }
-        }
-    }
-    return $tzdates;
-}
-?>
\ No newline at end of file
diff --git a/dav/iCalcreator/lgpl.txt b/dav/iCalcreator/lgpl.txt
deleted file mode 100755 (executable)
index 5ab7695..0000000
+++ /dev/null
@@ -1,504 +0,0 @@
-                 GNU LESSER GENERAL PUBLIC LICENSE
-                      Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL.  It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
-                           Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it.  You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
-  When we speak of free software, we are referring to freedom of use,
-not price.  Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
-  To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights.  These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  To protect each distributor, we want to make it very clear that
-there is no warranty for the free library.  Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-\f
-  Finally, software patents pose a constant threat to the existence of
-any free program.  We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder.  Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
-  Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License.  This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License.  We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
-  When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library.  The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom.  The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
-  We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License.  It also provides other free software developers Less
-of an advantage over competing non-free programs.  These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries.  However, the Lesser license provides advantages in certain
-special circumstances.
-
-  For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard.  To achieve this, non-free programs must be
-allowed to use the library.  A more frequent case is that a free
-library does the same job as widely used non-free libraries.  In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
-  In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software.  For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
-  Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-\f
-                 GNU LESSER GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-  
-  1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
-  You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-\f
-  2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-\f
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
-  If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-\f
-  6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Use a suitable shared library mechanism for linking with the
-    Library.  A suitable mechanism is one that (1) uses at run time a
-    copy of the library already present on the user's computer system,
-    rather than copying library functions into the executable, and (2)
-    will operate properly with a modified version of the library, if
-    the user installs one, as long as the modified version is
-    interface-compatible with the version that the work was made with.
-
-    c) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    d) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    e) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-\f
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License.  However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
-  9. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-\f
-  11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded.  In such case, this License incorporates the limitation as if
-written in the body of this License.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation.  If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-\f
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission.  For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this.  Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
-                           NO WARRANTY
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-                    END OF TERMS AND CONDITIONS
-\f
-           How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.  It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
-
-
diff --git a/dav/iCalcreator/releaseNotes-2.12.txt b/dav/iCalcreator/releaseNotes-2.12.txt
deleted file mode 100755 (executable)
index 2a145dd..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-2.10.24 ######################
-upd returnCalendar, only create header 'content-length' when gziping output
-
-2.10.26 ######################
-Concatenate iCalcreator, utilityFunction and helper functions in iCalcreator.class.php file
-
-2.10.27 ######################
-parsing dates with extended (MS outlook) time zones
-
-2.10.28 ######################
-bug in function selectComponents, (continue if) missing dtstart
-
-2.10.29 ######################
-new function ms2phpTZ, (very) simple mapping of MS outlook time zones to PHP
-
-2.10.30 ###################### thanks Yitzchok
-external (iCalUtilityFunctions) time zone helper contributions:getTimezonesAsDateArrays (expandTimezoneDates) and getTzOffsetForDate functions
-
-2.11.1 ######################
-creation of rfc6321 XML output, input iCalcreator instance
-new (helper) function iCal2XML ('inner' _addXMLchild),
-
-2.11.2 ######################
-parse of rfc6321 XML input (string/file), output iCalcreator instance
-new (helper) functions: XMLstr2iCal, XMLfile2iCal, XML2iCal
-('inner' functions:_getXMLParams, _getXMLComponents, _getXMLProperties)
-
-2.11.3 ######################
-bug in getProperty, management of properties with multiple ocurrences
-
-2.11.4 ######################
-bug in _tz2offset, plus/minus error
-
-2.11.5 ######################
-updated _setDate _setDate2, setExdate, setRdate, utc offset management, Z-suffix
-
-2.11.7 ######################
-bug in function getConfig, 'directory' or 'filename' couldn't accept '0' (zero)
-
-2.11.8 ######################
-upd createTimezone (_setTZrrule), fixing from/to UTC offset and rdate(-s)
-upd functions: _format_date_time, _setDate, _setDate2, setRdate, setExdate, 
-               _setRexrule, _isArrayDate
-new function:  _strDate2arr
-
-2.11.9 ######################
-check x-props names, start with 'x-'/'X-'
-
-2.11.10 ######################
-function parse, management of list content in TEXT properties
-
-2.11.11 ######################
-always update PRODID when (re-)setting 'unique_id'
-
-2.11.12 ######################
-update management of (Attendee) parameter value lists
-update rfc5545 parameters with 'DQUOTE' settings
-function createAttendee and _createParams
-
-2.11.13 ######################
-upd _size75, correct line break when property content ends with '0' at pos 76
-
-2.11.14 ######################
-upd function transformDateTime, now accepting input date in array format
-
-2.11.15 ######################
-bug in function _setRexrule, UNTIL in DATE format
-
-2.11.16 ######################
-property ATTACH and large file attachments
-
-2.11.17 ######################
-bug in ATTENDEE, parsing error
-
-2.11.19 ######################
-upd using.html
-
-2.11.20 ######################
-upd createComponent, sorting standard/daylight subComponents
-
-2.11.21 ######################
-bug in selectComponents: long 'event' starting before period
-
-2.11.23 ######################
-bug: create FREEBUSY, empty property
-
-2.11.24 ######################
-bug: set/create RELATED-TO mgnt
diff --git a/dav/iCalcreator/releaseSummary.txt b/dav/iCalcreator/releaseSummary.txt
deleted file mode 100755 (executable)
index c78b193..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-A major subrelease:
-- Concatenate iCalcreator, utilityFunction and helper functions in iCalcreator.class.php file
-- new functionality:
--- external time zone helper contributions:getTimezonesAsDateArrays
-   (expandTimezoneDates) and getTzOffsetForDate functions
--- create and parse of rfc6321 XML
--- ms2phpTZ, mapping of MS outlook time zones to PHP
--- createComponent, sorting standard/daylight subComponents
-- uppdates:
--- parsing dates with extended (MS outlook) time zones
--- returnCalendar, only create header 'content-length' when gziping output
--- _setDate _setDate2, setExdate, setRdate, utc offset management, Z-suffix
--- createTimezone (_setTZrrule), fixing from/to UTC offset and rdate(-s)
--- checking x-props names, start with 'x-'/'X-'
--- parse, management of list content in TEXT properties
--- all rfc5545 parameters with 'DQUOTE' settings
--- transformDateTime, accepting input date in array format
--- property ATTACH and large file attachments
-- fixed bugs:
--- selectComponents, (continue if) missing dtstart
--- selectComponents: long 'event' starting before period
--- getProperty, management of properties with multiple ocurrences
--- _tz2offset, plus/minus error
--- getConfig, 'directory' or 'filename' couldn't accept '0' (zero)
--- _size75, correct line break when property content ends with '0' at pos 76-- 
--- _setRexrule, UNTIL in DATE format
--- ATTENDEE, parsing error
--- setFREEBUSY, empty property
--- set/create RELATED-TO mgnt
--- update of PRODID when (re-)setting 'unique_id'
-- updated using manual
diff --git a/dav/iCalcreator/summary.html b/dav/iCalcreator/summary.html
deleted file mode 100755 (executable)
index 40e5f6c..0000000
+++ /dev/null
@@ -1,387 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
-<html>
-<head>
-<title>iCalcreator 2.12 summary</title>
-<meta name="author"      content="Kjell-Inge Gustafsson - kigkonsult" />
-<meta name="copyright"   content="2007-2012 Kjell-Inge Gustafsson - kigkonsult" />
-<meta name="keywords"    content="ical, calendar, calender, xcal, xml, icalender, rfc2445, rfc5545, vcalender, php, create" />
-<meta name="description" content="iCalcreator summary" />
-<style type="text/css">
-body {
-  FONT-FAMILY     : "Lucida Grande","Lucida Sans Unicode", "Bitstream Vera Sans", Lucida, Arial, Geneva, Helvetica, sans-serif;
-  FONT-SIZE       : small;
-  MARGIN          : 10px;
-  WIDTH           : 800px;
-}
-h1 {
-  FONT-FAMILY     : "Lucida Grande","Lucida Sans Unicode", "Bitstream Vera Sans", Lucida, Arial, Geneva, Helvetica, sans-serif;
-  FONT-SIZE       : large;
-}
-h2 {
-  FONT-FAMILY     : "Lucida Grande","Lucida Sans Unicode", "Bitstream Vera Sans", Lucida, Arial, Geneva, Helvetica, sans-serif;
-  FONT-SIZE       : large;
-}
-h4 {
-  FONT-FAMILY     : "Lucida Grande","Lucida Sans Unicode", "Bitstream Vera Sans", Lucida, Arial, Geneva, Helvetica, sans-serif;
-  FONT-SIZE       : small;
-  FONT-WEIGHT     : bold;
-}
-.code {
-  FONT-FAMILY     : monospace;
-  FONT-SIZE       : medium;
-  WHITE-SPACE     : pre;
-}
-.comment {
-  FONT-FAMILY     : arial;
-  FONT-SIZE       : small;
-  FONT-STYLE      : italic;
-}
-</style>
-</head>
-<body>
-
-<h1>iCalcreator v2.12</h1>
-iCalcreator v2.12<br />
-copyright (c) 2007-2012 Kjell-Inge Gustafsson, kigkonsult<br />
-<a href="http://kigkonsult.se/iCalcreator/index.php" title="kigkonsult.se/iCalcreator" target="_blank">kigkonsult.se iCalcreator</a><br>
-<a href="http://kigkonsult.se/contact/index.php" title="kigkonsult.se/contact" target="_blank">kigkonsult.se contact</a><br>
-<br />
-iCalcreator is a <em>PHP</em> class package managing iCal files, supporting (non-)<strong>calendar</strong> 
-systems and applications to process and communicate <strong>calendar</strong> information like 
-events, agendas, tasks, reports, totos and journaling information.
-<br /><br />
-This is a <b>short summary</b> how to use iCalcreator; create, parse, edit, select and output functionality.
-<br /><br />
-The iCalcreator package, built of a <strong>calendar</strong> class with support of a function class and helper functions, are <strong>calendar</strong>
-component property oriented. Development environment is <em>PHP</em> version 5.x but coding is done
-to meet 4.x backward compatibility and may work. Some functions requires <em>PHP</em> >= 5.2.0.
-<br /><br />
-The iCalcreator main class, utility class and helper functions are included in the &quot;iCalcreator.class.php&quot; file.
-<br /><br />
-More iCalcreator supplementary and &quot;howto&quot; information will be found at 
-kigkonsult.se iCalcreator implement examples and test <a href="http://kigkonsult.se/test/index.php" title="kigkonsult.se iCalcreator implement and test examples" target="_blank">pages</a>. 
-A strong recommendation is to have the document <a href="http://kigkonsult.se/iCalcreator/docs/using.html" title="iCalcreator user's Manual" target="_blank">user's manual</a> 
-open in parallell when exploiting the link.
-
-<h4>iCal</h4>
-A short iCal description is found at <a href="http://en.wikipedia.org/wiki/ICalendar#Core_object" title="iCalendar From Wikipedia, the free encyclopedia" target="_blank">Wikipedia</a>. If You are not familiar with iCal, read this first!<br />
-Knowledge of <strong>calendar</strong> protocol rfc5545/rfc5546  is to recommend;<br />
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc5545" title="RFC5545" target="_blank">rfc5545</a>
- - Internet Calendaring and Scheduling Core Object Specification (iCalendar)<br />
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc5546" title="RFC5546" target="_blank">rfc5546</a>
- - iCalendar Transport-Independent Interoperability Protocol (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries <br />
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc5545" title="Download RFC5545 in text format">rfc5545</a> and
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc5546" title="Download RFC5546 in text format">rfc5546</a>
-obsoletes, respectively,
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc2445" title="Download RFC2445 in text format">rfc2445</a> and
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc2446" title="Download RFC2446 in text format">rfc2446</a>.
-<br />
-
-<h4>xCal</h4>
-iCalcreator also supports xCal (iCal xml)<br />
-<a href="http://kigkonsult.se/downloads/dl.php?f=rfc6321" title="Download RFC6321 in text format" target="_blank">rfc6321</a>
- - &quot;xCal: The XML Format for <strong>iCalendar</strong>&quot;
-<br />
-
-<h4>SUPPORT</h4>
-The main support channel is using iCalcreator
-<a title="Sourceforge" href="http://sourceforge.net/projects/icalcreator/forums/" target="_blank">Sourceforge</a> forum.
-<br />
-<br />
-kigkonsult offer services for software support, design and development of customizations and adaptations of <em>PHP</em>/<em>MySQL</em> solutions 
-with a special focus on software long term utility and reliability,
-supported through our agile acquire/design/transition process model.
-<br />
-
-<h4>DONATE</h4>
-You can show your appreciation for our free software,
-and can support future development by making a donation to the kigkonsult GPL/LGPL projects.
-<br />
-<br />
-Make a donation of any size by clicking <a href="http://kigkonsult.se/contact/index.php#Donate" title="Donate" target="_blank">here</a>.
-Thanks in advance!
-<br />
-
-<h4>Contact</h4>
-Use the contact <a href="http://kigkonsult.se/contact/index.php" title="kigkonsult.se/contact" target="_blank">page</a>
-for queries, improvement/development issues or professional support and development.
-Please note that paid support or consulting service has the highest priority.
-<br />
-
-<h4>Downloads and usage examples</h4>
-On <a href="http://kigkonsult.se/iCalcreator/index.php" title="kigkonsult iCalcreator" target="_blank">kigkonsult.se</a> can you download the 
-<a href="http://kigkonsult.se/downloads/index.php#iCalcreator" title="iCalcreator complete manual" target="_blank"><b>complete manual</b></a>
-and review 
-<a href="http://kigkonsult.se/test/index.php" title="kigkonsult.se iCalcreator implement and test examples" target="_blank">coding and test examples</b></a>.
-<br />
-
-<h4>INSTALL</h4>
-Unpack to any folder<br />
-- add this folder to your include-path<br />
-- or unpack to your application-(include)-folder<br />
-Add &quot;require_once '[folder/]iCalcreator.class.php';&quot; to your php-script.
-<br />
-<br />
-If using <em>PHP</em> version 5.1 or higher, the default timezone need to be set/altered, now &quot;Europe/Stockholm&quot;,
-line 50 in the iCalcreator.class.php file.
-<br />
-When creating a new calendar/component instance, review config settings.
-<br />
-<br />
-To really boost performance, visit kigkonsult.se contact <a href="http://kigkonsult.se/contact/index.php"><u>page</u></a> for information.
-<br />
-<br />
-
-
-<h2>CREATE</h2>
-
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot; );         // <span class="comment">set a (site) unique id</span>
-$v = new vcalendar( $config );                             // <span class="comment">create a new calendar instance</span>
-$tz = &quot;Europe/Stockholm&quot;;                                  // <span class="comment">define time zone</span>
-
-$v->setProperty( &quot;method&quot;, &quot;PUBLISH&quot; );                    // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;x-wr-calname&quot;, &quot;Calendar Sample&quot; );      // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-CALDESC&quot;, &quot;Calendar Description&quot; ); // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-TIMEZONE&quot;, $tz );                   // <span class="comment">required of some <strong>calendar</strong> software</span>
-.. .
-$xprops = array( &quot;X-LIC-LOCATION&quot; => $tz );                // <span class="comment">required of some <strong>calendar</strong> software</span>
-iCalUtilityFunctions::createTimezone( $v, $tz, $xprops );  // <span class="comment">create timezone component(-s) <b>opt. 1</b></span>
-.. .                                                       // <span class="comment">based on present date</span>
-.. .
-$vevent = &amp; $v->newComponent( &quot;vevent&quot; );                  // <span class="comment">create an event <strong>calendar</strong> component</span>
-$vevent->setProperty( &quot;dtstart&quot;, array( &quot;year&quot;=&gt;2007, &quot;month&quot;=&gt;4, &quot;day&quot;=&gt;1, &quot;hour&quot;=&gt;19, &quot;min&quot;=&gt;0,  &quot;sec&quot;=&gt;0 ));
-$vevent->setProperty( &quot;dtend&quot;,   array( &quot;year&quot;=&gt;2007, &quot;month&quot;=&gt;4, &quot;day&quot;=&gt;1, &quot;hour&quot;=&gt;22, &quot;min&quot;=&gt;30, &quot;sec&quot;=&gt;0 ));
-$vevent->setProperty( &quot;LOCATION&quot;, &quot;Central Placa&quot; );       // <span class="comment">property name - case independent</span>
-$vevent->setProperty( &quot;summary&quot;, &quot;PHP summit&quot; );
-$vevent->setProperty( &quot;description&quot;, &quot;This is a description&quot; );
-$vevent->setProperty( &quot;comment&quot;, &quot;This is a comment&quot; );
-$vevent->setProperty( &quot;attendee&quot;, &quot;attendee1@icaldomain.net&quot; );
-.. .
-$valarm = &amp; $vevent->newComponent( &quot;valarm&quot; );             // <span class="comment">create an event alarm</span>
-$valarm->setProperty(&quot;action&quot;, &quot;DISPLAY&quot; );
-$valarm->setProperty(&quot;description&quot;, $vevent->getProperty( &quot;description&quot; );
-.. .                                                       // <span class="comment">reuse the event description</span>
-.. .
-$d = sprintf( '%04d%02d%02d %02d%02d%02d', 2007, 3, 31, 15, 0, 0 );
-iCalUtilityFunctions::transformDateTime( $d, $tz, &quot;UTC&quot;, &quot;Ymd\THis\Z&quot;);
-$valarm->setProperty( &quot;trigger&quot;, $d );                     // <span class="comment">create alarm trigger (in UTC datetime)</span>
-.. .
-$vevent = & $v->newComponent( &quot;vevent&quot; );                  // <span class="comment">create next event calendar component</span>
-$vevent->setProperty( &quot;dtstart&quot;, &quot;20070401&quot;, array(&quot;VALUE&quot; =&gt; &quot;DATE&quot;));// <span class="comment">alt. date format, now for an all-day event</span>
-$vevent->setProperty( &quot;organizer&quot; , &quot;boss@icaldomain.com&quot; );
-$vevent->setProperty( &quot;summary&quot;, &quot;ALL-DAY event&quot; );
-$vevent->setProperty( &quot;description&quot;, &quot;This is a description for an all-day event&quot; );
-$vevent->setProperty( &quot;resources&quot;, &quot;COMPUTER PROJECTOR&quot; );
-$vevent->setProperty( &quot;rrule&quot;, array( &quot;FREQ&quot; =&gt; &quot;WEEKLY&quot;, &quot;count&quot; =&gt; 4));// <span class="comment">weekly, four occasions</span>
-$vevent->parse( &quot;LOCATION:1CP Conference Room 4350&quot; );     // <span class="comment">supporting parse of strict rfc5545 formatted text</span>
-.. .
-.. .// <span class="comment">all calendar components are described in <a href="http://kigkonsult.se/downloads/dl.php?f=rfc5545" title="RFC5545" target="_blank">rfc5545</a></span>
-.. .// <span class="comment">a complete iCalcreator function list (ex. setProperty) in <a href="http://kigkonsult.se/downloads/index.php#iCalcreator" title="iCalcreator complete manual" target="_blank">iCalcreator manual</a></span>
-.. .
-iCalUtilityFunctions::createTimezone( $v, $tz, $xprops);   // <span class="comment">create timezone component(-s) <b>opt. 2</b></span>
-.. .                                                       // <span class="comment">based on all start dates in events (i.e. dtstart)</span>
-.. .
-.. .
-</p>
-<br />
-<br />
-
-<h2>PARSE</h2>
-<h4>iCal, rfc5545 / rfc2445 </h4>
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot; );         // <span class="comment">set a (site) unique id, required if any component UID is missing</span>
-$v = new vcalendar( $config );                             // <span class="comment">create a new <strong>calendar</strong> instance</span>
-
- /* start parse of local iCal file */
-$config = array( &quot;directory&quot; =&gt; &quot;calendar&quot;, &quot;filename&quot; =&gt; &quot;file.ics&quot; );
-$v->setConfig( $config );                                  // <span class="comment">set directory and file name</span>
-$v->parse();
-
- /* start parse of remote iCal file */
-$v->setConfig( &quot;url&quot;, &quot;http://www.aDomain.net/file.ics&quot; ); // <span class="comment">iCalcreator also support parse of remote files</span>
-$v->parse();
-
-$v->setProperty( &quot;method&quot;, &quot;PUBLISH&quot; );                    // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;x-wr-calname&quot;, &quot;Calendar Sample&quot; );      // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-CALDESC&quot;, &quot;Calendar Description&quot; ); // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-TIMEZONE&quot;, &quot;Europe/Stockholm&quot; );    // <span class="comment">required of some <strong>calendar</strong> software</span>
-
-.. .
-$v->sort();                                                // <span class="comment">ensure start date order</span>
-.. .
-.. .                                                       // <span class="comment">continue process (edit, parse,select) the iCalcreator instance</span>
-.. .
-</p>
-
-<h4>xCal, rfc6321 (XML)</h4>
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot; );         // <span class="comment">set a (site) unique id, required if any component UID is missing</span>
-.. .
-$filename = 'xmlfile.xml';                                 // <span class="comment">use a local xCal file</span>
-// $filename = 'http://kigkonsult.se/xcal.php?a=1&amp;b=2&amp;c=3';// <span class="comment">or a remote xCal resource</span>
-if( FALSE === ( $v = XMLfile2iCal(  $filename, $config ))) // <span class="comment">convert the XML resource to an iCalcreator instance</span>
-  exit( &quot;Error when parsing $filename" );
-.. .
-.. .                                                       // <span class="comment">continue process (edit, parse,select) the iCalcreator instance</span>
-.. .
-</p>
-<br />
-
-<h2>EDIT</h2>
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot;, &quot;directory&quot; =&gt; &quot;calendar&quot;, &quot;filename&quot; =&gt; &quot;file.ics&quot; );
-                                                           // <span class="comment">set the (site) unique id, the import directory and file name</span>
-$v = new vcalendar( $config );                             // <span class="comment">create a new calendar instance</span>
-
-$v->parse();
-
-$v->setProperty( &quot;method&quot;, &quot;PUBLISH&quot; );                    // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;x-wr-calname&quot;, &quot;Calendar Sample&quot; );      // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-CALDESC&quot;, &quot;Calendar Description&quot; ); // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-TIMEZONE&quot;, &quot;Europe/Stockholm&quot; );    // <span class="comment">required of some <strong>calendar</strong> software</span>
-
-while( $vevent = $v->getComponent( &quot;vevent&quot; )) {           // <span class="comment">read events, one by one</span>
-  $uid = $vevent->getProperty( &quot;uid&quot; );                    // <span class="comment">uid required, one occurrence (unique id/key for component)</span>
-  .. .
-  $dtstart = $vevent->getProperty( &quot;dtstart&quot; );            // <span class="comment">dtstart required, one occurrence</span>
-  .. .
-  if( $description = $vevent->getProperty( &quot;description&quot;, 1 )) { // <span class="comment">description optional, first occurrence</span>
-    .. .                                                   // <span class="comment">edit the description</span>
-    $vevent->setProperty( &quot;description&quot;, $description, FALSE, 1 ); // <span class="comment">update/replace the description</span>
-  }
-  while( $comment = $vevent->getProperty( &quot;comment&quot; )) {   // <span class="comment">comment optional, may occur more than once </span>
-    .. .                                                   // <span class="comment">manage comments</span>
-  }
-  .. .
-  while( $vevent->deleteProperty( &quot;attendee&quot; ))
-    continue;                                              // <span class="comment">remove all ATTENDEE properties .. .</span>
-  .. .
-  $v->setComponent ( $vevent, $uid );                      // <span class="comment">update/replace event in calendar with <b>UID</b> as key </span>
-}
-.. .
-.. .// <span class="comment">a complete iCalcreator function list (ex. getProperty, deleteProperty) in <a href="http://kigkonsult.se/downloads/index.php#iCalcreator" title="iCalcreator complete manual" target="_blank">iCalcreator manual</a></span>
-.. .
-</p>
-<br />
-<br />
-
-<h2>SELECT</h2>
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot; );         // <span class="comment">set a (site) unique id</span>
-$v = new vcalendar( $config );                             // <span class="comment">create a new <strong>calendar</strong> instance</span>
-
-$v->setConfig( &quot;url&quot;, &quot;http://www.aDomain.net/file.ics&quot; ); // <span class="comment">iCalcreator also support remote files</span>
-$v->parse();
-$v->sort();                                                // <span class="comment">ensure start date order</span>
-
-$v->setProperty( &quot;method&quot;, &quot;PUBLISH&quot; );                    // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;x-wr-calname&quot;, &quot;Calendar Sample&quot; );      // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-CALDESC&quot;, &quot;Calendar Description&quot; ); // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-TIMEZONE&quot;, &quot;Europe/Stockholm&quot; );    // <span class="comment">required of some <strong>calendar</strong> software</span>
-</p>
-<h4>Select components based on specific date period</h4>
-<p class="code">$eventArray = $v->selectComponents();                      // <span class="comment">select components occurring <b>today</b></span>
-                                                           // <span class="comment">(including components with recurrence pattern)</span>
-foreach( $eventArray as $year =&gt; $yearArray) {
- foreach( $yearArray as $month =&gt; $monthArray ) {
-  foreach( $monthArray as $day =&gt; $dailyEventsArray ) {
-   foreach( $dailyEventsArray as $vevent ) {
-    $currddate = $event->getProperty( &quot;x-current-dtstart&quot; );
-                                                           // <span class="comment">if member of a recurrence set (2nd occurrence etc)</span>
-                                                           // <span class="comment">returns array( &quot;x-current-dtstart&quot;</span>
-                                                           // <span class="comment">      , &lt;(string) date(&quot;Y-m-d [H:i:s][timezone/UTC offset]&quot;)&gt;)</span>
-    $dtstart = $vevent->getProperty( &quot;dtstart&quot; );          // <span class="comment">dtstart required, one occurrence, (orig. start date)</span>
-    $summary = $vevent->getProperty( &quot;summary&quot; );
-    $description = $vevent->getProperty( &quot;description&quot; );
-    .. .
-    .. .
-   }
-  }
- }
-}
-</p>
-<h4>Select specific property values</h4>
-<p class="code">$valueOccur = $v->getProperty( &quot;RESOURCES&quot; );              // <span class="comment">fetch specific property (unique) values and occurrences</span>
-                                                           // <span class="comment">ATTENDEE, CATEGORIES, DTSTART, LOCATION,</span>
-                                                           // <span class="comment">ORGANIZER, PRIORITY, RESOURCES, STATUS,</span>
-                                                           // <span class="comment">SUMMARY, UID</span>
-foreach( $valueOccur as $uniqueValue =&gt; $occurCnt ) {
-  echo &quot;The RESOURCES value &lt;b&gt;$uniqueValue&lt;/b&gt; occurs &lt;b&gt;$occurCnt&lt;/b&gt; times&lt;br /&gt;&quot;;
-  .. .
-}
-</p>
-<h4>Select components based on specific property value</h4>
-<p class="code">$selectSpec = array( &quot;CATEGORIES&quot; =&gt; &quot;course1&quot; );
-$specComps = $v->selectComponents( $selectSpec );          // <span class="comment">selects components based on specific property value(-s)</span>
-                                                           // <span class="comment">ATTENDEE, CATEGORIES, LOCATION, ORGANIZER,</span>
-                                                           // <span class="comment">PRIORITY, RESOURCES, STATUS, SUMMARY, UID</span>
-foreach( $specComps as $comp ) {
- .. .
-}
-</p>
-<br />
-<br />
-
-<h2>OUTPUT</h2>
-<p class="code">require_once( &quot;iCalcreator.class.php&quot; );
-$config = array( &quot;unique_id&quot; =&gt; &quot;kigkonsult.se&quot; );         // <span class="comment">set a (site) unique id</span>
-$v = new vcalendar( $config );                             // <span class="comment">create a new calendar instance</span>
-
-$v->setProperty( &quot;method&quot;, &quot;PUBLISH&quot; );                    // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;x-wr-calname&quot;, &quot;Calendar Sample&quot; );      // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-CALDESC&quot;, &quot;Calendar Description&quot; ); // <span class="comment">required of some <strong>calendar</strong> software</span>
-$v->setProperty( &quot;X-WR-TIMEZONE&quot;, &quot;Europe/Stockholm&quot; );    // <span class="comment">required of some <strong>calendar</strong> software</span>
-.. .
-.. .                                                       // <span class="comment">continue process (edit, parse,select) the iCalcreator instance</span>
-.. .
-</p>
-<h4>opt 1</h4>
-<p class="code">.. .
-$v->returnCalendar();                                      // <span class="comment">redirect calendar file to browser</span>
-</p>
-
-<h4>opt 2</h4>
-<p class="code">.. .
-$config = array( &quot;directory&quot; =&gt; &quot;depot&quot;, &quot;filename&quot; =&gt; &quot;calendar.ics&quot; );
-$v->setConfig( $config );                                  // <span class="comment">set output directory and file name</span>
-$v->saveCalendar();                                        // <span class="comment">save calendar to (local) file</span>
-.. .
-</p>
-
-<h4>opt 3, xCal</h4>
-<p class="code">.. .
-$xmlstr = iCal2XML( $v );                                  // <span class="comment">create well-formed XML, rfc6321</span>
-...
-</p>
-<br />
-<br />
-
-
-<h2>COPYRIGHT AND LICENSE</h2>
-
-<h4>Copyright</h4>
-iCalcreator v2.12<br />
-copyright (c) 2007-2012 Kjell-Inge Gustafsson, kigkonsult<br />
-<a href="http://kigkonsult.se/iCalcreator/index.php" title="kigkonsult.se/iCalcreator" target="_blank">kigkonsult.se iCalcreator</a><br>
-<a href="http://kigkonsult.se/contact/index.php" title="kigkonsult.se/contact" target="_blank">kigkonsult.se contact</a><br>
-<br />
-
-<h4>License</h4>
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Lesser General Public
-License as published by the Free Software Foundation; either
-version 2.1 of the License, or (at your option) any later version.
-<br /><br />
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-Lesser General Public License for more details.
-<br /><br />
-You should have received a copy of the GNU Lesser General Public
-License along with this library; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-or download it <a href="http://kigkonsult.se/downloads/dl.php?f=LGPL" target="_blank">here</a>.
-</body>
-</html>
\ No newline at end of file
diff --git a/dav/jqueryui/jquery-ui-1.8.20.custom.css b/dav/jqueryui/jquery-ui-1.8.20.custom.css
deleted file mode 100644 (file)
index 089d68e..0000000
+++ /dev/null
@@ -1,354 +0,0 @@
-/*!
- * jQuery UI CSS Framework 1.8.20
- *
- * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Theming/API
- */
-
-/* Layout helpers
-----------------------------------*/
-.ui-helper-hidden { display: none; }
-.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
-.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
-.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
-.ui-helper-clearfix:after { clear: both; }
-.ui-helper-clearfix { zoom: 1; }
-.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
-
-
-/* Interaction Cues
-----------------------------------*/
-.ui-state-disabled { cursor: default !important; }
-
-
-/* Icons
-----------------------------------*/
-
-/* states and images */
-.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
-
-
-/* Misc visuals
-----------------------------------*/
-
-/* Overlays */
-.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
-
-
-/*!
- * jQuery UI CSS Framework 1.8.20
- *
- * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Theming/API
- *
- * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
- */
-
-
-/* Component containers
-----------------------------------*/
-.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
-.ui-widget .ui-widget { font-size: 1em; }
-.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
-.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
-.ui-widget-content a { color: #333333; }
-.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
-.ui-widget-header a { color: #ffffff; }
-
-/* Interaction states
-----------------------------------*/
-.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
-.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
-.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
-.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
-.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
-.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
-.ui-widget :active { outline: none; }
-
-/* Interaction Cues
-----------------------------------*/
-.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
-.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
-.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
-.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
-.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
-.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
-.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
-.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
-
-/* Icons
-----------------------------------*/
-
-/* states and images */
-.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
-.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
-.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
-.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
-.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
-.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
-.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
-.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
-
-/* positioning */
-.ui-icon-carat-1-n { background-position: 0 0; }
-.ui-icon-carat-1-ne { background-position: -16px 0; }
-.ui-icon-carat-1-e { background-position: -32px 0; }
-.ui-icon-carat-1-se { background-position: -48px 0; }
-.ui-icon-carat-1-s { background-position: -64px 0; }
-.ui-icon-carat-1-sw { background-position: -80px 0; }
-.ui-icon-carat-1-w { background-position: -96px 0; }
-.ui-icon-carat-1-nw { background-position: -112px 0; }
-.ui-icon-carat-2-n-s { background-position: -128px 0; }
-.ui-icon-carat-2-e-w { background-position: -144px 0; }
-.ui-icon-triangle-1-n { background-position: 0 -16px; }
-.ui-icon-triangle-1-ne { background-position: -16px -16px; }
-.ui-icon-triangle-1-e { background-position: -32px -16px; }
-.ui-icon-triangle-1-se { background-position: -48px -16px; }
-.ui-icon-triangle-1-s { background-position: -64px -16px; }
-.ui-icon-triangle-1-sw { background-position: -80px -16px; }
-.ui-icon-triangle-1-w { background-position: -96px -16px; }
-.ui-icon-triangle-1-nw { background-position: -112px -16px; }
-.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
-.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
-.ui-icon-arrow-1-n { background-position: 0 -32px; }
-.ui-icon-arrow-1-ne { background-position: -16px -32px; }
-.ui-icon-arrow-1-e { background-position: -32px -32px; }
-.ui-icon-arrow-1-se { background-position: -48px -32px; }
-.ui-icon-arrow-1-s { background-position: -64px -32px; }
-.ui-icon-arrow-1-sw { background-position: -80px -32px; }
-.ui-icon-arrow-1-w { background-position: -96px -32px; }
-.ui-icon-arrow-1-nw { background-position: -112px -32px; }
-.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
-.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
-.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
-.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
-.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
-.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
-.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
-.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
-.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
-.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
-.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
-.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
-.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
-.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
-.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
-.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
-.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
-.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
-.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
-.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
-.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
-.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
-.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
-.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
-.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
-.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
-.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
-.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
-.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
-.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
-.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
-.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
-.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
-.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
-.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
-.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
-.ui-icon-arrow-4 { background-position: 0 -80px; }
-.ui-icon-arrow-4-diag { background-position: -16px -80px; }
-.ui-icon-extlink { background-position: -32px -80px; }
-.ui-icon-newwin { background-position: -48px -80px; }
-.ui-icon-refresh { background-position: -64px -80px; }
-.ui-icon-shuffle { background-position: -80px -80px; }
-.ui-icon-transfer-e-w { background-position: -96px -80px; }
-.ui-icon-transferthick-e-w { background-position: -112px -80px; }
-.ui-icon-folder-collapsed { background-position: 0 -96px; }
-.ui-icon-folder-open { background-position: -16px -96px; }
-.ui-icon-document { background-position: -32px -96px; }
-.ui-icon-document-b { background-position: -48px -96px; }
-.ui-icon-note { background-position: -64px -96px; }
-.ui-icon-mail-closed { background-position: -80px -96px; }
-.ui-icon-mail-open { background-position: -96px -96px; }
-.ui-icon-suitcase { background-position: -112px -96px; }
-.ui-icon-comment { background-position: -128px -96px; }
-.ui-icon-person { background-position: -144px -96px; }
-.ui-icon-print { background-position: -160px -96px; }
-.ui-icon-trash { background-position: -176px -96px; }
-.ui-icon-locked { background-position: -192px -96px; }
-.ui-icon-unlocked { background-position: -208px -96px; }
-.ui-icon-bookmark { background-position: -224px -96px; }
-.ui-icon-tag { background-position: -240px -96px; }
-.ui-icon-home { background-position: 0 -112px; }
-.ui-icon-flag { background-position: -16px -112px; }
-.ui-icon-calendar { background-position: -32px -112px; }
-.ui-icon-cart { background-position: -48px -112px; }
-.ui-icon-pencil { background-position: -64px -112px; }
-.ui-icon-clock { background-position: -80px -112px; }
-.ui-icon-disk { background-position: -96px -112px; }
-.ui-icon-calculator { background-position: -112px -112px; }
-.ui-icon-zoomin { background-position: -128px -112px; }
-.ui-icon-zoomout { background-position: -144px -112px; }
-.ui-icon-search { background-position: -160px -112px; }
-.ui-icon-wrench { background-position: -176px -112px; }
-.ui-icon-gear { background-position: -192px -112px; }
-.ui-icon-heart { background-position: -208px -112px; }
-.ui-icon-star { background-position: -224px -112px; }
-.ui-icon-link { background-position: -240px -112px; }
-.ui-icon-cancel { background-position: 0 -128px; }
-.ui-icon-plus { background-position: -16px -128px; }
-.ui-icon-plusthick { background-position: -32px -128px; }
-.ui-icon-minus { background-position: -48px -128px; }
-.ui-icon-minusthick { background-position: -64px -128px; }
-.ui-icon-close { background-position: -80px -128px; }
-.ui-icon-closethick { background-position: -96px -128px; }
-.ui-icon-key { background-position: -112px -128px; }
-.ui-icon-lightbulb { background-position: -128px -128px; }
-.ui-icon-scissors { background-position: -144px -128px; }
-.ui-icon-clipboard { background-position: -160px -128px; }
-.ui-icon-copy { background-position: -176px -128px; }
-.ui-icon-contact { background-position: -192px -128px; }
-.ui-icon-image { background-position: -208px -128px; }
-.ui-icon-video { background-position: -224px -128px; }
-.ui-icon-script { background-position: -240px -128px; }
-.ui-icon-alert { background-position: 0 -144px; }
-.ui-icon-info { background-position: -16px -144px; }
-.ui-icon-notice { background-position: -32px -144px; }
-.ui-icon-help { background-position: -48px -144px; }
-.ui-icon-check { background-position: -64px -144px; }
-.ui-icon-bullet { background-position: -80px -144px; }
-.ui-icon-radio-off { background-position: -96px -144px; }
-.ui-icon-radio-on { background-position: -112px -144px; }
-.ui-icon-pin-w { background-position: -128px -144px; }
-.ui-icon-pin-s { background-position: -144px -144px; }
-.ui-icon-play { background-position: 0 -160px; }
-.ui-icon-pause { background-position: -16px -160px; }
-.ui-icon-seek-next { background-position: -32px -160px; }
-.ui-icon-seek-prev { background-position: -48px -160px; }
-.ui-icon-seek-end { background-position: -64px -160px; }
-.ui-icon-seek-start { background-position: -80px -160px; }
-/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
-.ui-icon-seek-first { background-position: -80px -160px; }
-.ui-icon-stop { background-position: -96px -160px; }
-.ui-icon-eject { background-position: -112px -160px; }
-.ui-icon-volume-off { background-position: -128px -160px; }
-.ui-icon-volume-on { background-position: -144px -160px; }
-.ui-icon-power { background-position: 0 -176px; }
-.ui-icon-signal-diag { background-position: -16px -176px; }
-.ui-icon-signal { background-position: -32px -176px; }
-.ui-icon-battery-0 { background-position: -48px -176px; }
-.ui-icon-battery-1 { background-position: -64px -176px; }
-.ui-icon-battery-2 { background-position: -80px -176px; }
-.ui-icon-battery-3 { background-position: -96px -176px; }
-.ui-icon-circle-plus { background-position: 0 -192px; }
-.ui-icon-circle-minus { background-position: -16px -192px; }
-.ui-icon-circle-close { background-position: -32px -192px; }
-.ui-icon-circle-triangle-e { background-position: -48px -192px; }
-.ui-icon-circle-triangle-s { background-position: -64px -192px; }
-.ui-icon-circle-triangle-w { background-position: -80px -192px; }
-.ui-icon-circle-triangle-n { background-position: -96px -192px; }
-.ui-icon-circle-arrow-e { background-position: -112px -192px; }
-.ui-icon-circle-arrow-s { background-position: -128px -192px; }
-.ui-icon-circle-arrow-w { background-position: -144px -192px; }
-.ui-icon-circle-arrow-n { background-position: -160px -192px; }
-.ui-icon-circle-zoomin { background-position: -176px -192px; }
-.ui-icon-circle-zoomout { background-position: -192px -192px; }
-.ui-icon-circle-check { background-position: -208px -192px; }
-.ui-icon-circlesmall-plus { background-position: 0 -208px; }
-.ui-icon-circlesmall-minus { background-position: -16px -208px; }
-.ui-icon-circlesmall-close { background-position: -32px -208px; }
-.ui-icon-squaresmall-plus { background-position: -48px -208px; }
-.ui-icon-squaresmall-minus { background-position: -64px -208px; }
-.ui-icon-squaresmall-close { background-position: -80px -208px; }
-.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
-.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
-.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
-.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
-.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
-.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
-
-
-/* Misc visuals
-----------------------------------*/
-
-/* Corner radius */
-.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
-.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
-.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
-.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
-
-/* Overlays */
-.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
-.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*!
- * jQuery UI Datepicker 1.8.20
- *
- * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Datepicker#theming
- */
-.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
-.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
-.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
-.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
-.ui-datepicker .ui-datepicker-prev { left:2px; }
-.ui-datepicker .ui-datepicker-next { right:2px; }
-.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
-.ui-datepicker .ui-datepicker-next-hover { right:1px; }
-.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
-.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
-.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
-.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
-.ui-datepicker select.ui-datepicker-month, 
-.ui-datepicker select.ui-datepicker-year { width: 49%;}
-.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
-.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
-.ui-datepicker td { border: 0; padding: 1px; }
-.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
-.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
-.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
-.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
-
-/* with multiple calendars */
-.ui-datepicker.ui-datepicker-multi { width:auto; }
-.ui-datepicker-multi .ui-datepicker-group { float:left; }
-.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
-.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
-.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
-.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
-.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
-.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
-.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
-.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
-
-/* RTL support */
-.ui-datepicker-rtl { direction: rtl; }
-.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
-.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
-.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
-.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
-.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
-.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
-.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
-.ui-datepicker-rtl .ui-datepicker-group { float:right; }
-.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
-.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
-
-/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
-.ui-datepicker-cover {
-    display: none; /*sorry for IE5*/
-    display/**/: block; /*sorry for IE5*/
-    position: absolute; /*must have*/
-    z-index: -1; /*must have*/
-    filter: mask(); /*must have*/
-    top: -4px; /*must have*/
-    left: -4px; /*must have*/
-    width: 200px; /*must have*/
-    height: 200px; /*must have*/
-}
\ No newline at end of file
diff --git a/dav/jqueryui/jquery-ui-1.8.20.custom.min.js b/dav/jqueryui/jquery-ui-1.8.20.custom.min.js
deleted file mode 100644 (file)
index bb6ed07..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-/*! jQuery UI - v1.8.20 - 2012-04-30
-* https://github.com/jquery/jquery-ui
-* Includes: jquery.ui.core.js
-* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
-(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.20",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;return b[d]>0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}})})(jQuery);;/*! jQuery UI - v1.8.20 - 2012-04-30
-* https://github.com/jquery/jquery-ui
-* Includes: jquery.ui.datepicker.js
-* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
-(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.20"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);return c&&!c.inline&&this._setDateFromField(c,b),c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;return c&&s++,c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;return r+=f[0].length,parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase())return f=c[0],r+=d.length,!1});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;return c&&m++,c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;return c&&e++,c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()==a.lastVal)return;var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;return b.setDate(b.getDate()+a),b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());return f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0)),this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){return a?(a.setHours(a.getHours()>12?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', -"+i+", 'M');\""+' title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', +"+i+", 'M');\""+' title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+a.id+"');\""+">"+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+a.id+"',"+Y.getMonth()+","+Y.getFullYear()+', this);return false;"')+">"+(bb&&!G?"&#xa0;":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?"&#xa0;":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" "+">";for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?"&#xa0;":"")+m),l+="</div>",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;return e=d&&e>d?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.20",window["DP_jQuery_"+dpuuid]=$})(jQuery);;
\ No newline at end of file
diff --git a/dav/jqueryui/jquery-ui-1.8.21.custom.css b/dav/jqueryui/jquery-ui-1.8.21.custom.css
new file mode 100644 (file)
index 0000000..d7a7842
--- /dev/null
@@ -0,0 +1,375 @@
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*!
+ * jQuery UI Dialog 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*!
+ * jQuery UI Datepicker 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}
\ No newline at end of file
diff --git a/dav/jqueryui/jquery-ui-1.8.21.custom.min.js b/dav/jqueryui/jquery-ui-1.8.21.custom.min.js
new file mode 100644 (file)
index 0000000..424d9cf
--- /dev/null
@@ -0,0 +1,21 @@
+/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;return b[d]>0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.widget.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){return c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}}),d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;return e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e,f&&e.charAt(0)==="_"?h:(f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b)return h=f,!1}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))}),h)}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}return this._setOptions(e),this},_setOptions:function(b){var c=this;return a.each(b,function(a,b){c._setOption(a,b)}),this},_setOption:function(a,b){return this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);return this.element.trigger(c,d),!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.position.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.dialog.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||"&#160;",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("<span></span>").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('<button type="button"></button>').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||"&#160;"))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return!1})},1),a(document).bind("keydown.dialog-overlay",function(c){b.options.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}),a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize));var c=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b<c?a(window).height()+"px":b+"px"):a(document).height()+"px"},width:function(){var b,c;return a.browser.msie?(b=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),c=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),b<c?a(window).width()+"px":b+"px"):a(document).width()+"px"},resize:function(){var b=a([]);a.each(a.ui.dialog.overlay.instances,function(){b=b.add(this)}),b.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}}),a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.datepicker.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);return c&&!c.inline&&this._setDateFromField(c,b),c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;return c&&s++,c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;return r+=f[0].length,parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase())return f=c[0],r+=d.length,!1});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;return c&&m++,c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;return c&&e++,c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()==a.lastVal)return;var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;return b.setDate(b.getDate()+a),b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());return f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0)),this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){return a?(a.setHours(a.getHours()>12?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', -"+i+", 'M');\""+' title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', +"+i+", 'M');\""+' title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+a.id+"');\""+">"+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+a.id+"',"+Y.getMonth()+","+Y.getFullYear()+', this);return false;"')+">"+(bb&&!G?"&#xa0;":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?"&#xa0;":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" "+">";for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?"&#xa0;":"")+m),l+="</div>",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;return e=d&&e>d?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;
\ No newline at end of file
diff --git a/dav/jqueryui/jquery.ui.datepicker-de.js b/dav/jqueryui/jquery.ui.datepicker-de.js
new file mode 100644 (file)
index 0000000..ac2d516
--- /dev/null
@@ -0,0 +1,23 @@
+/* German initialisation for the jQuery UI date picker plugin. */
+/* Written by Milian Wolff (mail@milianw.de). */
+jQuery(function($){
+       $.datepicker.regional['de'] = {
+               closeText: 'schließen',
+               prevText: '&#x3c;zurück',
+               nextText: 'Vor&#x3e;',
+               currentText: 'heute',
+               monthNames: ['Januar','Februar','März','April','Mai','Juni',
+               'Juli','August','September','Oktober','November','Dezember'],
+               monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun',
+               'Jul','Aug','Sep','Okt','Nov','Dez'],
+               dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
+               dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
+               dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'],
+               weekHeader: 'Wo',
+               dateFormat: 'dd.mm.yy',
+               firstDay: 1,
+               isRTL: false,
+               showMonthAfterYear: false,
+               yearSuffix: ''};
+       $.datepicker.setDefaults($.datepicker.regional['de']);
+});
diff --git a/dav/layout.fnk.php b/dav/layout.fnk.php
deleted file mode 100644 (file)
index 550f90b..0000000
+++ /dev/null
@@ -1,426 +0,0 @@
-<?php
-
-
-/**
- *
- */
-function wdcal_addRequiredHeaders()
-{
-       $a = get_app();
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.min.js"></script>' . "\r\n";
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal.js"></script>' . "\r\n";
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal/css/calendar.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal/css/main.css' . '" media="all" />' . "\r\n";
-
-       switch (get_config("system", "language")) {
-               case "de":
-                       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_DE.js"></script>' . "\r\n";
-                       break;
-               default:
-                       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_EN.js"></script>' . "\r\n";
-       }
-
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/jquery.calendar.js"></script>' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/main.js"></script>' . "\r\n";
-}
-
-/**
- *
- */
-function wdcal_addRequiredHeadersEdit()
-{
-       $a = get_app();
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.min.js"></script>' . "\r\n";
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/colorpicker/colorPicker.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/colorpicker/jquery.colorPicker.min.js"></script>' . "\r\n";
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/timepicker/timePicker.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/timepicker/jquery.timePicker.min.js"></script>' . "\r\n";
-
-       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal.css' . '" media="all" />' . "\r\n";
-       $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal.js"></script>' . "\r\n";
-
-}
-
-
-/**
- * @param array|DBClass_friendica_calendars[] $calendars
- * @param array $calendar_preselected
- * @param string $data_feed_url
- * @param string $view
- * @param int $theme
- * @param int $height_diff
- * @param bool $readonly
- * @param string $curr_day
- * @param array $add_params
- * @param bool $show_nav
- * @return string
- */
-function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url, $view = "week", $theme = 0, $height_diff = 175, $readonly = false, $curr_day = "", $add_params = array(), $show_nav = true)
-{
-
-       $a            = get_app();
-       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
-
-       $cals_avail = array();
-       foreach ($calendars as $c) $cals_avail[] = array("ns" => $c->namespace, "id" => $c->namespace_id, "displayname" => $c->displayname);
-       $opts = array(
-               "view"             => $view,
-               "theme"            => $theme,
-               "readonly"         => $readonly,
-               "height_diff"      => $height_diff,
-               "weekstartday"     => $localization->getFirstDayOfWeek(),
-               "data_feed_url"    => $data_feed_url,
-               "date_format_dm1"  => $localization->dateformat_js_dm1(),
-               "date_format_dm2"  => $localization->dateformat_js_dm2(),
-               "date_format_dm3"  => $localization->dateformat_js_dm3(),
-               "date_format_full" => $localization->dateformat_datepicker_js(),
-               "baseurl"          => $a->get_baseurl() . "/dav/wdcal/",
-       );
-
-       $x = '
-<script>
-       $(function() {
-               $("#animexxcalendar").animexxCalendar(' . json_encode($opts) . ');
-       });
-</script>
-
-<div id="animexxcalendar" class="animexxcalendar">
-       <div class="calselect"><strong>Available Calendars:</strong>';
-
-       foreach ($cals_avail as $cal) {
-               $x .= '<label style="margin-left: 10px; margin-right: 10px;"><input type="checkbox" name="cals[]" value="' . $cal["ns"] . '-' . $cal["id"] . '"';
-               $found = false;
-               foreach ($calendar_preselected as $pre) if ($pre["ns"] == $cal["ns"] && $pre["id"] == $cal["id"]) $found = true;
-               if ($found) $x .= ' checked';
-               $x .= '> ' . escape_tags($cal["displayname"]) . '</label> ';
-       }
-
-       $x .= '</div>
-       <div class="calhead" style="padding-left:1px;padding-right:1px;">
-               <div class="ptogtitle loaderror" style="display: none;">Sorry, could not load your data, please try again later</div>
-       </div>';
-
-       if ($show_nav) {
-
-               $x .= '<div class="ctoolbar">
-               <div class="fbutton faddbtn" style="float: right;">
-                       <div><a href="' . $a->get_baseurl() . '/dav/settings/"><span>' . t("Settings") . ' / ' . t("Help") . '</span></a></div>
-               </div>
-               <div class="fbutton addcal">
-                       <div><a href="' . $a->get_baseurl() . '/dav/wdcal/new/" class="addcal">' . t("New event") . '</a></div>
-               </div>
-               <div class="btnseparator"></div>
-               <div class="fbutton showtodaybtn">
-                       <div><span class="showtoday">' . t("Today") . '</span></div>
-               </div>
-               <div class="btnseparator"></div>
-
-               <div class="fbutton showdaybtn">
-                       <div><span title="Day" class="showdayview ';
-
-               if ($view == "day") $x .= 'fcurrent';
-
-               $x .= '">' . t("Day") . '</span></div>
-               </div>
-               <div class="fbutton showweekbtn ';
-
-               if ($view == "week") $x .= "fcurrent";
-
-               $x .= '">
-                       <div><span title="Week" class="showweekview">' . t("Week") . '</span></div>
-               </div>
-               <div class="showmonthbtn fbutton ';
-
-               if ($view == "month") $x .= 'fcurrent';
-
-               $x .= '">
-                       <div><span title="Month" class="showmonthview">' . t("Month") . '</span></div>
-
-               </div>
-               <div class="btnseparator"></div>
-               <div class="fbutton showreflashbtn">
-                       <div><span class="showdayflash">' . t("Reload") . '</span></div>
-               </div>
-               <div class="btnseparator"></div>
-               <div title="' . t("Previous") . '"  class="fbutton sfprevbtn">
-                       <span class="fprev"></span>
-               </div>
-               <div title="' . t("Next") . '" class="fbutton sfnextbtn">
-                       <span class="fnext"></span>
-               </div>
-               <div class="fshowdatep fbutton" style="white-space: nowrap; position: relative;">
-                       <input name="txtshow" class="hdtxtshow" style="position: absolute; bottom: 0; left: 0; width: 0; height: 0; border: 0; padding: 0; margin: 0;">
-                       <span class="txtdatetimeshow">' . t("Date") . '</span>
-               </div>
-               <div style="float: right;">
-                       <div class="clear"></div>
-               </div>
-       </div>';
-       }
-       $x .= '
-       <div style="padding:1px;">
-               <div class="calmain printborder">
-                       <div class="gridcontainer" style="overflow-y: visible;"></div>
-               </div>
-       </div>
-</div>';
-
-       return $x;
-}
-
-
-/**
- * @param string $uri
- * @param string $recurr_uri
- * @return string
- */
-function wdcal_getDetailPage($uri, $recurr_uri)
-{
-       $a = get_app();
-
-       $details = null;
-       $cals    = dav_getMyCals($a->user["uid"]);
-       foreach ($cals as $c) {
-               $cs = wdcal_calendar_factory($a->user["uid"], $c->namespace, $c->namespace_id);
-               $p  = $cs->getPermissionsItem($a->user["uid"], $uri, $recurr_uri);
-               if ($p["read"]) try {
-                       $redirect = $cs->getItemDetailRedirect($uri);
-                       if ($redirect !== null) goaway($redirect);
-                       $details = $cs->getItemByUri($uri);
-               } catch (Exception $e) {
-                       notification(t("Error") . ": " . $e);
-                       goaway($a->get_baseurl() . "/dav/wdcal/");
-               }
-       }
-
-
-       return $uri . " / " . $recurr_uri . "<br>" . print_r($details, true);
-}
-
-/**
- * @param string $uri
- * @param string $recurr_uri
- * @return string
- */
-function wdcal_getEditPage($uri, $recurr_uri = "")
-{
-
-       $a            = get_app();
-       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
-
-       if ($uri != "" && $uri != "new") {
-               $o = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
-               );
-               if (count($o) != 1) return t('Not found');
-               $event = $o[0];
-
-               $calendarSource = wdcal_calendar_factory($a->user["uid"], $event["namespace"], $event["namespace_id"]);
-
-               $permissions = $calendarSource->getPermissionsItem($a->user["uid"], $uri, $recurr_uri, $event);
-
-               if (!$permissions["write"]) return t('No access');
-
-               $n = q("SELECT * FROM %s%snotifications WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
-               );
-               if (count($n) > 0) {
-                       $notification_type  = $n[0]["rel_type"];
-                       $notification_value = -1 * $n[0]["rel_value"];
-                       $notification       = true;
-               } else {
-                       if ($event["IsAllDayEvent"]) {
-                               $notification_type  = "hour";
-                               $notification_value = 24;
-                       } else {
-                               $notification_type  = "minute";
-                               $notification_value = 60;
-                       }
-                       $notification = false;
-               }
-
-
-       } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) {
-               $event = array(
-                       "id"            => 0,
-                       "Subject"       => $_REQUEST["title"],
-                       "Location"      => "",
-                       "Description"   => "",
-                       "StartTime"     => wdcal_php2MySqlTime($_REQUEST["start"]),
-                       "EndTime"       => wdcal_php2MySqlTime($_REQUEST["end"]),
-                       "IsAllDayEvent" => $_REQUEST["isallday"],
-                       "Color"         => null,
-                       "RecurringRule" => null,
-               );
-               if ($_REQUEST["isallday"]) {
-                       $notification_type  = "hour";
-                       $notification_value = 24;
-               } else {
-                       $notification_type  = "hour";
-                       $notification_value = 1;
-               }
-
-               $notification = true;
-       } else {
-               $event              = array(
-                       "id"            => 0,
-                       "Subject"       => "",
-                       "Location"      => "",
-                       "Description"   => "",
-                       "StartTime"     => date("Y-m-d H:i:s"),
-                       "EndTime"       => date("Y-m-d H:i:s", time() + 3600),
-                       "IsAllDayEvent" => "0",
-                       "Color"         => "#5858ff",
-                       "RecurringRule" => null,
-               );
-               $notification_type  = "hour";
-               $notification_value = 1;
-               $notification       = true;
-       }
-
-       $postto = $a->get_baseurl() . "/dav/wdcal/" . ($uri == "new" ? "new/" : $uri . "/edit/");
-
-       $out = "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
-       $out .= "<form method='POST' action='$postto'><input type='hidden' name='form_security_token' value='" . get_form_security_token('caledit') . "'>\n";
-
-       $out .= "<label for='cal_subject'>Subject:</label>
-               <input name='color' id='cal_color' value='" . (strlen($event["Color"]) != 7 ? "#5858ff" : escape_tags($event["Color"])) . "'>
-               <input name='subject' id='cal_subject' value='" . escape_tags($event["Subject"]) . "'><br>\n";
-       $out .= "<label for='cal_allday'>Is All-Day event:</label><input type='checkbox' name='allday' id='cal_allday' " . ($event["IsAllDayEvent"] ? "checked" : "") . "><br>\n";
-
-       $out .= "<label for='cal_startdate'>" . t("Starts") . ":</label>";
-       $out .= "<input name='start_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["StartTime"])) . "' id='cal_start_date'>";
-       $out .= "<input name='start_time' value='" . substr($event["StartTime"], 11, 5) . "' id='cal_start_time'>";
-       $out .= "<br>\n";
-
-       $out .= "<label for='cal_enddate'>" . t("Ends") . ":</label>";
-       $out .= "<input name='end_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["EndTime"])) . "' id='cal_end_date'>";
-       $out .= "<input name='end_time' value='" . substr($event["EndTime"], 11, 5) . "' id='cal_end_time'>";
-       $out .= "<br>\n";
-
-       $out .= "<label for='cal_location'>" . t("Location") . ":</label><input name='location' id='cal_location' value='" . escape_tags($event["Location"]) . "'><br>\n";
-
-       $out .= "<label for='event-desc-textarea'>" . t("Description") . ":</label> <textarea id='event-desc-textarea' name='wdcal_desc' style='vertical-align: top; width: 400px; height: 100px;'>" . escape_tags($event["Description"]) . "</textarea>";
-       $out .= "<br style='clear: both;'>";
-
-       $out .= "<label for='notification'>" . t('Notification') . ":</label>";
-       $out .= '<input type="checkbox" name="notification" id="notification" ';
-       if ($notification) $out .= "checked";
-       $out .= '> ';
-       $out .= '<span id="notification_detail" style="display: none;">
-                       <input name="notification_value" value="' . $notification_value . '" size="3">
-                       <select name="notification_type" size="1">
-                               <option value="minute" ';
-       if ($notification_type == "minute") $out .= "selected";
-       $out .= '> ' . t('Minutes') . '</option>
-                               <option value="hour" ';
-       if ($notification_type == "hour") $out .= "selected";
-       $out .= '> ' . t('Hours') . '</option>
-                               <option value="day" ';
-       if ($notification_type == "day") echo "selected";
-       $out .= '> ' . t('Days') . '</option>
-                       </select> ' . t('before') . '
-               </span><br><br>';
-
-
-       $out .= "<script>\$(function() {
-               wdcal_edit_init('" . $localization->dateformat_datepicker_js() . "');
-       });</script>";
-
-       $out .= "<input type='submit' name='save' value='Save'></form>";
-
-       return $out;
-}
-
-
-/**
- * @param App $a
- * @return string
- */
-function wdcal_getSettingsPage(&$a)
-{
-
-       if (!local_user()) {
-               notice(t('Permission denied.') . EOL);
-               return '';
-       }
-
-       if (isset($_REQUEST["save"])) {
-               check_form_security_token_redirectOnErr($a->get_baseurl() . '/dav/settings/', 'calprop');
-               set_pconfig($a->user["uid"], "dav", "dateformat", $_REQUEST["wdcal_date_format"]);
-               info(t('The new values have been saved.'));
-       }
-
-       $o = "";
-
-       $o .= "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
-
-       $o .= '<h3>' . t('Calendar Settings') . '</h3>';
-
-       $current_format = wdcal_local::getInstanceByUser($a->user["uid"]);
-       $o .= '<form method="POST" action="' . $a->get_baseurl() . '/dav/settings/">';
-       $o .= "<input type='hidden' name='form_security_token' value='" . get_form_security_token('calprop') . "'>\n";
-
-       $o .= '<label for="wdcal_date_format">' . t('Date format') . ':</label><select name="wdcal_date_format" id="wdcal_date_format" size="1">';
-       $classes = wdcal_local::getInstanceClasses();
-       foreach ($classes as $c) {
-               $o .= '<option value="' . $c::getID() . '" ';
-               if ($c::getID() == $current_format::getID()) $o .= 'selected';
-               $o .= '>' . escape_tags($c::getName()) . '</option>';
-       }
-       $o .= '</select><br>';
-
-       $o .= '<label for="wdcal_time_zone">' . t('Time zone') . ':</label><input id="wdcal_time_zone" value="' . $a->timezone . '" disabled><br>';
-
-       $o .= '<input type="submit" name="save" value="' . t('Save') . '">';
-       $o .= '</form>';
-
-       $o .= "<br><h3>" . t("Limitations") . "</h3>";
-
-       $o .= "- The native friendica events are embedded as read-only, half-transparent in the calendar.<br>";
-
-       $o .= "<br><h3>" . t("Warning") . "</h3>";
-
-       $o .= "This plugin still is in a very early stage of development. Expect major bugs!<br>";
-
-       $o .= "<br><h3>" . t("Synchronization (iPhone, Thunderbird Lightning, Android, ...)") . "</h3>";
-
-       $o .= 'This plugin enables synchronization of your dates and contacts with CalDAV- and CardDAV-enabled programs or devices.<br>
-               As an example, the instructions how to set up two-way synchronization with an iPhone/iPodTouch are provided below.<br>
-               Unfortunately, Android does not have native support for CalDAV or CardDAV, so an app has to be installed.<br>
-               On desktops, the Lightning-extension to Mozilla Thunderbird should be able to use this plugin as a backend.<br><br>';
-
-       $o .= '<h4>' . t('Synchronizing this calendar with the iPhone') . '</h4>';
-
-       $o .= "<ul>
-       <li>Go to the settings</li>
-       <li>Mail, contacts, settings</li>
-       <li>Add a new account</li>
-       <li>Other...</li>
-       <li>Calendar -> CalDAV-Account</li>
-       <li><b>Server:</b> " . $a->get_baseurl() . "/dav/ / <b>Username/Password:</b> <em>the same as your friendica-login</em></li>
-       </ul>";
-
-       $o .= '<h4>' . t('Synchronizing your Friendica-Contacts with the iPhone') . '</h4>';
-
-       $o .= "<ul>
-       <li>Go to the settings</li>
-       <li>Mail, contacts, settings</li>
-       <li>Add a new account</li>
-       <li>Other...</li>
-       <li>Contacts -> CardDAV-Account</li>
-       <li><b>Server:</b> " . $a->get_baseurl() . "/dav/ / <b>Username/Password:</b> <em>the same as your friendica-login</em></li>
-       </ul>";
-
-       return $o;
-}
-
diff --git a/dav/main.php b/dav/main.php
deleted file mode 100644 (file)
index 6635d18..0000000
+++ /dev/null
@@ -1,303 +0,0 @@
-<?php
-
-require_once('include/security.php');
-
-function dav_install()
-{
-       register_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook');
-       register_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook');
-       register_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook');
-}
-
-
-function dav_uninstall()
-{
-       unregister_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook');
-       unregister_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook');
-       unregister_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook');
-}
-
-
-function dav_module()
-{
-}
-
-function dav_include_files()
-{
-       require_once (__DIR__ . "/common/dbclasses/dbclass_animexx.class.php");
-       require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendars.class.php");
-       require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.jqcalendar.class.php");
-       require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.notifications.class.php");
-       require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendarobjects.class.php");
-
-       /*
-                       require_once (__DIR__ . "/SabreDAV/lib/Sabre.includes.php");
-                       require_once (__DIR__ . "/SabreDAV/lib/Sabre/VObject/includes.php");
-                       require_once (__DIR__ . "/SabreDAV/lib/Sabre/DAVACL/includes.php");
-                       require_once (__DIR__ . "/SabreDAV/lib/Sabre/CalDAV/includes.php");
-                       */
-       require_once (__DIR__ . "/SabreDAV/lib/Sabre/autoload.php");
-
-       $tz_before = date_default_timezone_get();
-       require_once (__DIR__ . "/iCalcreator/iCalcreator.class.php");
-       date_default_timezone_set($tz_before);
-
-       require_once (__DIR__ . "/common/calendar.fnk.php");
-       require_once (__DIR__ . "/common/dav_caldav_backend_common.inc.php");
-       require_once (__DIR__ . "/common/dav_caldav_backend.inc.php");
-       require_once (__DIR__ . "/common/dav_caldav_root.inc.php");
-       require_once (__DIR__ . "/common/dav_user_calendars.inc.php");
-       require_once (__DIR__ . "/common/dav_carddav_root.inc.php");
-       require_once (__DIR__ . "/common/dav_carddav_backend_std.inc.php");
-       require_once (__DIR__ . "/common/dav_user_addressbooks.inc.php");
-       require_once (__DIR__ . "/common/virtual_cal_source_backend.inc.php");
-       require_once (__DIR__ . "/common/wdcal_configuration.php");
-       require_once (__DIR__ . "/common/wdcal_cal_source.inc.php");
-       require_once (__DIR__ . "/common/wdcal_cal_source_private.inc.php");
-
-       require_once (__DIR__ . "/dav_friendica_principal.inc.php");
-       require_once (__DIR__ . "/dav_friendica_auth.inc.php");
-       require_once (__DIR__ . "/dav_carddav_backend_friendica_community.inc.php");
-       require_once (__DIR__ . "/dav_caldav_backend_friendica.inc.php");
-       require_once (__DIR__ . "/virtual_cal_source_friendica.inc.php");
-       require_once (__DIR__ . "/wdcal_cal_source_friendicaevents.inc.php");
-       require_once (__DIR__ . "/FriendicaACLPlugin.inc.php");
-
-       require_once (__DIR__ . "/calendar.friendica.fnk.php");
-       require_once (__DIR__ . "/layout.fnk.php");
-}
-
-
-/**
- * @param App $a
- */
-function dav_init(&$a)
-{
-
-       /*
-        * Recommended settings:
-        * ALTER TABLE `photo` ADD INDEX ( `contact-id` )
-        */
-
-       dav_include_files();
-
-       if (false) {
-               dbg(true);
-               error_reporting(E_ALL);
-               ini_set("display_errors", 1);
-       }
-
-       wdcal_create_std_calendars();
-
-
-       if ($a->argc >= 2 && $a->argv[1] == "wdcal") {
-
-               if ($a->argc >= 3 && $a->argv[2] == "feed") {
-                       wdcal_print_feed($a->get_baseurl() . "/dav/wdcal/");
-                       killme();
-               } elseif ($a->argc >= 3 && strlen($a->argv[2]) > 0) {
-                       wdcal_addRequiredHeadersEdit();
-               } else {
-                       wdcal_addRequiredHeaders();
-               }
-               return;
-       }
-
-       if ($a->argc >= 2 && $a->argv[1] == "settings") {
-               return;
-       }
-
-       $authBackend              = new Sabre_DAV_Auth_Backend_Friendica();
-       $principalBackend         = new Sabre_DAVACL_PrincipalBackend_Friendica($authBackend);
-       $caldavBackend_std        = new Sabre_CalDAV_Backend_Std();
-       $caldavBackend_community  = new Sabre_CalDAV_Backend_Friendica();
-       $carddavBackend_std       = new Sabre_CardDAV_Backend_Std();
-       $carddavBackend_community = new Sabre_CardDAV_Backend_FriendicaCommunity();
-
-       if (isset($_SERVER["PHP_AUTH_USER"])) {
-               $tree = new Sabre_DAV_SimpleCollection('root', array(
-                       new Sabre_DAV_SimpleCollection('principals', array(
-                               new Sabre_CalDAV_Principal_Collection($principalBackend, "principals/users"),
-                       )),
-                       new Sabre_CalDAV_AnimexxCalendarRootNode($principalBackend, array(
-                               $caldavBackend_std,
-                               $caldavBackend_community,
-                       )),
-                       new Sabre_CardDAV_AddressBookRootFriendica($principalBackend, array(
-                               $carddavBackend_std,
-                               $carddavBackend_community,
-                       )),
-               ));
-       } else {
-               $tree = new Sabre_DAV_SimpleCollection('root', array());
-       }
-
-// The object tree needs in turn to be passed to the server class
-       $server = new Sabre_DAV_Server($tree);
-
-       $url = parse_url($a->get_baseurl());
-       $server->setBaseUri(CALDAV_URL_PREFIX);
-
-       $authPlugin = new Sabre_DAV_Auth_Plugin($authBackend, 'SabreDAV');
-       $server->addPlugin($authPlugin);
-
-       $aclPlugin                      = new Sabre_DAVACL_Plugin_Friendica();
-       $aclPlugin->defaultUsernamePath = "principals/users";
-       $server->addPlugin($aclPlugin);
-
-       $caldavPlugin = new Sabre_CalDAV_Plugin();
-       $server->addPlugin($caldavPlugin);
-
-       $carddavPlugin = new Sabre_CardDAV_Plugin();
-       $server->addPlugin($carddavPlugin);
-
-       $browser = new Sabre_DAV_Browser_Plugin();
-       $server->addPlugin($browser);
-
-       $server->exec();
-
-       killme();
-}
-
-/**
- * @return string
- */
-function dav_content()
-{
-       $a = get_app();
-       if (!isset($a->user["uid"]) || $a->user["uid"] == 0) {
-               return login();
-       }
-
-       $x = "";
-
-       if ($a->argv[1] == "settings") {
-               return wdcal_getSettingsPage($a);
-       } elseif ($a->argv[1] == "wdcal") {
-               if ($a->argc >= 3 && strlen($a->argv[2]) > 0) {
-                       $uri = $a->argv[2];
-
-                       if ($uri == "new") {
-                               $o = "";
-                               if (isset($_REQUEST["save"])) {
-                                       check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit");
-                                       $o .= wdcal_postEditPage("new", "", $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
-                               }
-                               $o .= wdcal_getEditPage("new");
-                               return $o;
-                       } else {
-                               $recurr_uri = ""; // @TODO
-                               if (isset($a->argv[3]) && $a->argv[3] == "edit") {
-                                       $o = "";
-                                       if (isset($_REQUEST["save"])) {
-                                               check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit");
-                                               $o .= wdcal_postEditPage($uri, $recurr_uri, $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
-                                       }
-                                       $o .= wdcal_getEditPage($uri, $recurr_uri);
-                                       return $o;
-                               } else {
-                                       return wdcal_getDetailPage($uri, $recurr_uri);
-                               }
-                       }
-               } else {
-                       $cals      = dav_getMyCals($a->user["uid"]);
-                       $cals_show = array();
-                       foreach ($cals as $e) $cals_show[] = array("ns" => $e->namespace, "id" => $e->namespace_id, "displayname" => $e->displayname);
-                       $x = wdcal_printCalendar($cals, $cals_show, $a->get_baseurl() . "/dav/wdcal/feed/", "week", 0, 200);
-               }
-       }
-       return $x;
-}
-
-
-/**
- * @param App $a
- * @param object $b
- */
-function dav_event_created_hook(&$a, &$b)
-{
-       dav_include_files();
-       // @TODO Updating the cache instead of completely invalidating and rebuilding it
-       FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
-       FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
-}
-
-/**
- * @param App $a
- * @param object $b
- */
-function dav_event_updated_hook(&$a, &$b)
-{
-       dav_include_files();
-       // @TODO Updating the cache instead of completely invalidating and rebuilding it
-       FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
-       FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
-}
-
-/**
- * @param App $a
- * @param object $b
- */
-function dav_profile_tabs_hook(&$a, &$b)
-{
-       $b["tabs"][] = array(
-               "label" => t('Calendar'),
-               "url"   => $a->get_baseurl() . "/dav/wdcal/",
-               "sel"   => "",
-               "title" => t('Extended calendar with CalDAV-support'),
-       );
-}
-
-/**
- * @param App $a
- * @param null|object $o
- */
-function dav_plugin_admin_post(&$a = null, &$o = null)
-{
-       check_form_security_token_redirectOnErr('/admin/plugins/dav', 'dav_admin_save');
-
-       require_once(__DIR__ . "/database-init.inc.php");
-
-       if (isset($_REQUEST["install"])) {
-               $errs = dav_create_tables();
-               if (count($errs) == 0) info(t('The database tables have been installed.') . EOL);
-               else notice(t("An error occurred during the installation.") . EOL);
-       }
-}
-
-/**
- * @param App $a
- * @param null|object $o
- */
-function dav_plugin_admin(&$a, &$o)
-{
-
-       require_once(__DIR__ . "/database-init.inc.php");
-
-       $dbstatus = dav_check_tables();
-
-       $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("dav_admin_save") . '">';
-       $o .= '<i>' . t("No system-wide settings yet.") . '</i><br><br>';
-
-
-       $o .= '<h3>' . t('Database status') . '</h3>';
-       switch ($dbstatus) {
-               case 0:
-                       $o .= t('Installed');
-                       break;
-               case 1:
-                       $o .= t('Upgrade needed') . "<br><br><input type='submit' name='upgrade' value='" . t('Upgrade') . "'>";
-                       break;
-               case -1:
-                       $o .= t('Not installed') . "<br><br><input type='submit' name='install' value='" . t('Install') . "'>";
-                       break;
-       }
-       $o .= "<br><br>";
-
-       $o .= "<h3>" . t("Troubleshooting") . "</h3>";
-       $o .= "<h4>" . t("Manual creation of the database tables:") . "</h4>";
-       $o .= "<a href='#' onClick='\$(\"#sqlstatements\").show(); return false;'>" . t("Show SQL-statements") . "</a><blockquote style='display: none;' id='sqlstatements'><pre>";
-       $tables = dav_get_create_statements();
-       foreach ($tables as $t) $o .= escape_tags($t . "\n\n");
-       $o .= "</pre></blockquote>";
-}
diff --git a/dav/sabre-vobject/.travis.yml b/dav/sabre-vobject/.travis.yml
new file mode 100644 (file)
index 0000000..aca128b
--- /dev/null
@@ -0,0 +1,8 @@
+language: php
+php:
+  - 5.3
+  - 5.4
+
+script: phpunit --configuration tests/phpunit.xml
+
+before_script: composer install
diff --git a/dav/sabre-vobject/ChangeLog b/dav/sabre-vobject/ChangeLog
new file mode 100644 (file)
index 0000000..0f26abb
--- /dev/null
@@ -0,0 +1,7 @@
+2.0.0-stable (2012-08-08)
+       * VObject is now a separate project from SabreDAV. See the SabreDAV
+         changelog for version information before 2.0.
+       * New: VObject library now uses PHP 5.3 namespaces.
+       * New: It's possible to specify lists of parameters when constructing
+         properties.
+       * New: made it easier to construct the FreeBusyGenerator.
diff --git a/dav/sabre-vobject/LICENSE b/dav/sabre-vobject/LICENSE
new file mode 100644 (file)
index 0000000..8e09aec
--- /dev/null
@@ -0,0 +1,27 @@
+Copyright (C) 2007-2012 Rooftop Solutions.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the Sabre nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+    POSSIBILITY OF SUCH DAMAGE.
diff --git a/dav/sabre-vobject/README.md b/dav/sabre-vobject/README.md
new file mode 100644 (file)
index 0000000..1310703
--- /dev/null
@@ -0,0 +1,388 @@
+# SabreTooth VObject library
+
+[![Build Status](https://secure.travis-ci.org/evert/sabre-vobject.png?branch=master)](http://travis-ci.org/evert/sabre-vobject)
+
+The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545)
+and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP.
+The goal of the VObject library is to create a very complete library, with an easy to use API.
+
+This project is a spin-off from [SabreDAV](http://code.google.com/p/sabredav/), where it has
+been used for several years. The VObject library has 100% unittest coverage.
+
+# Installation
+
+VObject requires PHP 5.3, and should be installed using composer.
+The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website).
+
+After that, just declare the vobject dependency as follows:
+
+```
+"require" : {
+    "sabre/vobject" : "dev-master"
+}
+```
+
+Then, run `composer.phar update` and you should be good. As soon as the first release is out, you should switch `dev-master` to `2.0.*` though.
+
+# Usage
+
+## Parsing
+
+For our example, we will be using the following vcard:
+
+```
+BEGIN:VCARD
+VERSION:3.0
+PRODID:-//Sabre//Sabre VObject 2.0//EN
+N:Planck;Max;;;
+FN:Max Planck
+EMAIL;TYPE=WORK:mplanck@example.org
+item1.TEL;TYPE=CELL:(+49)3144435678
+item1.X-ABLabel:Private cell
+item2.TEL;TYPE=WORK:(+49)5554564744
+item2.X-ABLabel:Work
+END:VCARD
+```
+
+
+If we want to just print out Max' full name, you can just use property access:
+
+
+```php
+
+use Sabre\VObject;
+
+$card = VObject\Reader::read($data);
+echo $card->FN;
+
+```
+
+## Changing properties
+
+Creating properties is pretty similar. If we like to add his birthday, we just
+set the property:
+
+```php
+
+$card->BDAY = '1858-04-23';
+
+```
+
+Note that in the previous example, we're actually updating any existing BDAY that
+may already exist. If we want to add a new property, without overwriting the previous
+we can do this with the `add` method. 
+
+```php
+
+$card->add('EMAIL','max@example.org');
+
+```
+
+## Parameters
+
+If we want to also specify that this is max' home email addresses, we can do this with
+a third parameter:
+
+```
+
+$card->add('EMAIL', 'max@example'org', array('type' => 'HOME'));
+
+```
+
+If we want to read out all the email addresses and their type, this would look something
+like this:
+
+```
+foreach($card->EMAIL as $email) {
+
+    echo $email['TYPE'], ' - ', $email;
+
+}
+```
+
+## Groups
+
+In our example, you can see that the TEL properties are prefixed. These are 'groups' and
+allow you to group multiple related properties together. The group can be any user-defined
+name.
+
+This particular example as generated by the OS X addressbook, which uses the `X-ABLabel`
+to allow the user to specify custom labels for properties. OS X addressbook uses groups
+to tie the label to the property.
+
+The VObject library simply ignores the group if you don't specify it, so this will work:
+
+```php
+
+foreach($card->TEL as $tel) {
+    echo $tel, "\n";
+}
+```
+
+But if you would like to target a specific group + property, this is possible too:
+
+```php
+
+echo $card->{'ITEM1.TEL'};
+
+```
+
+So if you would like to show all the phone numbers, along with their custom label, the
+following syntax is used:
+
+```
+foreach($card->TEL as $tel) {
+
+    echo $card->{$tel->group . '.X-ABLABEL'}, ": ";
+    echo $tel, "\n";
+
+}
+```
+
+## Serializing / Saving
+
+If you want to generate your updated VObject, you can simply call the serialize() method.
+
+```
+
+echo $card->serialize();
+
+```
+
+## Components
+
+iCalendar, unlike vCards always have sub-components. Where vCards are often just a flat
+list, iCalendar objects tend to have a tree-like structure. For the following paragraphs,
+we will use the following object as the example:
+
+```
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 2.0//EN
+BEGIN:VEVENT
+SUMMARY:Curiosity landing
+DTSTART:20120806T051439Z
+LOCATION:Mars
+END:VEVENT
+END:VCALENDAR
+```
+
+Since events, tasks and journals are always in a sub component, this is also how we
+access them.
+
+```php
+
+use Sabre\VObject;
+
+$calendar = VObject\Reader::read($data);
+echo $calendar->VEVENT->SUMMARY;
+
+```
+
+Adding components to a calendar is done with a factory method:
+
+```php
+
+$event = VObject\Component::create('VEVENT');
+$calendar->add($event);
+
+$event->SUMMARY = 'Curiosity launch';
+$event->DTSTART = '20111126T150202Z';
+$event->LOCATION = 'Cape Carnival';
+
+```
+
+By the way.. cloning also works as expected, as the entire structure is cloned along with it:
+
+```php
+
+$clonedEvent = clone $calendar->VEVENT[0];
+$calendar->add($clonedEvent);
+
+```
+
+## Date and time handling
+
+If you ever had to deal with iCalendar timezones, you know it can be complicated.
+The way timezones are specified is flawed, which is something I may write an essay about some
+day. VObject does its best to determine the correct timezone. Many standard formats
+have been tested and verified, and special code has been implemented for special-casing
+microsoft generated timezone information, and others.
+
+To get a real php `DateTime` object, you can request this as follows:
+
+```
+$event = $calendar->VEVENT;
+$start = $event->DTSTART->getDateTime();
+echo $start->format(\DateTime::W3C);
+```
+
+To set the property with a DateTime object, you can use the following syntax:
+
+```
+$dateTime = new \DateTime('2012-08-07 23:53:00', new DateTimeZone('Europe/Amsterdam'));
+$event->DTSTART->setDateTime($dateTime, VObject\Property\DateTime::DATE);
+```
+
+The second argument specifies the type of date you're setting. The following three
+options exist:
+
+1. `LOCAL` This is a floating time, with no timezone information. This basically specifies that the event happens in whatever the timezone's currently in. This would be encoded as `DTSTART;VALUE=DATE-TIME:20120807235300`
+2. `UTC` This specifies that the time should be encoded as a UTC time. This is encoded as `DTSTART;VALUE=DATE-TIME:20120807205300Z`. Note the extra Z and the fact that it's two hours 'earlier'.
+3. `LOCALTZ` specifies that it's supposed to be encoded in its local timezone. For example `DTSTART;VALUE=DATE-TIME;TZID=Europe/Amsterdam:20120807235300`.
+4. `DATE` This is a date-only, and does not contain the time. In this case this would be encoded as `DTSTART;VALUE=DATE:20120807`.
+
+A few important notes:
+
+* When a `TZID` is specified, there should also be a matching `VTIMEZONE` object with all the timezone information. VObject cannot currently automatically embed this. However, in reality other clients seem to do fine without this information. Yet, for completeness, this will be added in the future.
+* As mentioned, the timezone-determination process may sometimes fail. Report any issues you find, and I'll be quick to add workarounds!
+
+## Recurrence rules
+
+Recurrence rules allow events to recur, for example for a weekly meeting, or an anniversary.
+This is done with the `RRULE` property. The `RRULE` property allows for a LOT of different
+rules. VObject only implements the ones that actually appear in calendar software.
+
+To read more about `RRULE` and all the options, check out [RFC5545](https://tools.ietf.org/html/rfc5545#section-3.8.5).
+VObject supports the following options:
+
+1. `UNTIL` for an end date.
+2. `INTERVAL` for for example "every 2 days".
+3. `COUNT` to stop recurring after x items.
+4. `FREQ=DAILY` to recur every day, and `BYDAY` to limit it to certain days.
+5. `FREQ=WEEKLY` to recur every week, `BYDAY` to expand this to multiple weekdays in every week and `WKST` to specify on which day the week starts.
+6. `FREQ=MONTHLY` to recur every month, `BYMONTHDAY` to expand this to certain days in a month, `BYDAY` to expand it to certain weekdays occuring in a month, and `BYSETPOS` to limit the last two expansions.
+7. `FREQ=YEARLY` to recur every year, `BYMONTH` to expand that to certain months in a year, and `BYDAY` and `BYWEEKDAY` to expand the `BYMONTH` rule even further.
+
+VObject supports the `EXDATE` property for exclusions, but not yet the `RDATE` and `EXRULE` 
+properties. If you're interested in this, please file a github issue, as this will put it
+on my radar.
+
+This is a bit of a complex subject to go in excruciating detail. The
+[RFC](https://tools.ietf.org/html/rfc5545#section-3.8.5) has a lot of examples though.
+
+The hard part is not to write the RRULE, it is to expand them. The most complex and
+hard-to-read code is hidden in this component. Dragons be here.
+
+So, if we have a meeting every 2nd monday of the month, this would be specified as such:
+
+```
+BEGIN:VCALENDAR
+  VERSION:2.0
+  BEGIN:VEVENT
+    UID:1102c450-e0d7-11e1-9b23-0800200c9a66
+    DTSTART:20120109T140000Z
+    RRULE:FREQ=MONTHLY;BYDAY=MO;BYSETPOS=2
+  END:VEVENT
+END:VCALENDAR
+```
+
+Note that normally it's not allowed to indent the object like this, but it does make
+it easier to read. This is also the first time I added in a UID, which is required
+for all VEVENT, VTODO and VJOURNAL objects!
+
+To figure out all the meetings for this year, we can use the following syntax:
+
+```php
+use Sabre\VObject;
+
+$calendar = VObject\Reader::read($data);
+$calendar->expand(new DateTime('2012-01-01'), new DateTime('2012-12-31'));
+```
+
+What the expand method does, is look at its inner events, and expand the recurring
+rule. Our calendar now contains 12 events. The first will have its RRULE stripped,
+and every subsequent VEVENT has the correct meeting date and a `RECURRENCE-ID` set.
+
+This results in something like this:
+
+```
+BEGIN:VCALENDAR
+  VERSION:2.0
+  BEGIN:VEVENT
+    UID:1102c450-e0d7-11e1-9b23-0800200c9a66
+    DTSTART:20120109T140000Z
+  END:VEVENT
+  BEGIN:VEVENT
+    UID:1102c450-e0d7-11e1-9b23-0800200c9a66
+    RECURRENCE-ID:20120213T140000Z
+    DTSTART:20120213T140000Z
+  END:VEVENT
+  BEGIN:VEVENT
+    UID:1102c450-e0d7-11e1-9b23-0800200c9a66
+    RECURRENCE-ID:20120312T140000Z
+    DTSTART:20120312T140000Z
+  END:VEVENT
+  ..etc..
+END:VCALENDAR
+```
+
+To show the list of dates, we would do this as such:
+
+```
+foreach($calendar->VEVENT as $event) {
+    echo $event->DTSTART->getDateTime()->format(\DateTime::ATOM);
+}
+```
+
+In a recurring event, single instances can also be overriden. VObject also takes these
+into consideration. The reason we needed to specify a start and end-date, is because
+some recurrence rules can be 'never ending'.
+
+You should make sure you pick a sane date-range. Because if you pick a 50 year
+time-range, for a daily recurring event; this would result in over 18K objects.
+
+# Free-busy report generation
+
+Some calendaring software can make use of FREEBUSY reports to show when people are
+available.
+
+You can automatically generate these reports from calendars using the `FreeBusyGenerator`.
+
+Example based on our last event:
+
+```
+
+// We're giving it the calendar object. It's also possible to specify multiple objects,
+// by setting them as an array.
+//
+// We must also specify a start and end date, because recurring events are expanded.
+$fbGenerator = new VObject\FreeBusyGenerator(
+    new DateTime('2012-01-01'),
+    new DateTime('2012-12-31'),
+    $calendar
+);
+
+// Grabbing the report
+$freebusy = $fbGenerator->result();
+
+// The freebusy report is another VCALENDAR object, so we can serialize it as usual:
+echo $freebusy->serialize();
+```
+
+The output of this script will look like this:
+
+```
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 2.0//EN
+CALSCALE:GREGORIAN
+BEGIN:VFREEBUSY
+DTSTART;VALUE=DATE-TIME:20111231T230000Z
+DTEND;VALUE=DATE-TIME:20111231T230000Z
+DTSTAMP;VALUE=DATE-TIME:20120808T131628Z
+FREEBUSY;FBTYPE=BUSY:20120109T140000Z/20120109T140000Z
+FREEBUSY;FBTYPE=BUSY:20120213T140000Z/20120213T140000Z
+FREEBUSY;FBTYPE=BUSY:20120312T140000Z/20120312T140000Z
+FREEBUSY;FBTYPE=BUSY:20120409T140000Z/20120409T140000Z
+FREEBUSY;FBTYPE=BUSY:20120514T140000Z/20120514T140000Z
+FREEBUSY;FBTYPE=BUSY:20120611T140000Z/20120611T140000Z
+FREEBUSY;FBTYPE=BUSY:20120709T140000Z/20120709T140000Z
+FREEBUSY;FBTYPE=BUSY:20120813T140000Z/20120813T140000Z
+FREEBUSY;FBTYPE=BUSY:20120910T140000Z/20120910T140000Z
+FREEBUSY;FBTYPE=BUSY:20121008T140000Z/20121008T140000Z
+FREEBUSY;FBTYPE=BUSY:20121112T140000Z/20121112T140000Z
+FREEBUSY;FBTYPE=BUSY:20121210T140000Z/20121210T140000Z
+END:VFREEBUSY
+END:VCALENDAR
+```
diff --git a/dav/sabre-vobject/composer.json b/dav/sabre-vobject/composer.json
new file mode 100644 (file)
index 0000000..9ecdf45
--- /dev/null
@@ -0,0 +1,27 @@
+{
+    "name": "sabre/vobject",
+    "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
+    "keywords" : [ "VObject", "iCalendar", "vCard" ],
+    "homepage" : "https://github.com/evert/sabre-vobject",
+    "license" : "BSD-3-Clause",
+    "require" : {
+        "php" : ">=5.3.1"
+    },
+    "authors" : [
+        {
+            "name" : "Evert Pot",
+            "email" : "evert@rooftopsolutions.nl",
+            "homepage" : "http://www.rooftopsolutions.nl/",
+            "role" : "Developer"
+        }
+    ],
+    "support" : {
+        "forum" : "https://groups.google.com/group/sabredav-discuss",
+        "source" : "https://github.com/evert/sabre-vobject"
+    },
+    "autoload" : {
+        "psr-0" : {
+            "Sabre\\VObject" : "lib/"
+        }
+    }
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component.php b/dav/sabre-vobject/lib/Sabre/VObject/Component.php
new file mode 100644 (file)
index 0000000..d17cb89
--- /dev/null
@@ -0,0 +1,412 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * VObject Component
+ *
+ * This class represents a VCALENDAR/VCARD component. A component is for example
+ * VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
+ * ends with END:COMPONENTNAME
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Component extends Element {
+
+    /**
+     * Name, for example VEVENT
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Children properties and components
+     *
+     * @var array
+     */
+    public $children = array();
+
+    /**
+     * The following constants are used by the validate() method.
+     */
+    const REPAIR = 1;
+
+    /**
+     * If components are added to this map, they will be automatically mapped
+     * to their respective classes, if parsed by the reader or constructed with
+     * the 'create' method.
+     *
+     * @var array
+     */
+    static public $classMap = array(
+        'VALARM'        => 'Sabre\\VObject\\Component\\VAlarm',
+        'VCALENDAR'     => 'Sabre\\VObject\\Component\\VCalendar',
+        'VCARD'         => 'Sabre\\VObject\\Component\\VCard',
+        'VEVENT'        => 'Sabre\\VObject\\Component\\VEvent',
+        'VJOURNAL'      => 'Sabre\\VObject\\Component\\VJournal',
+        'VTODO'         => 'Sabre\\VObject\\Component\\VTodo',
+    );
+
+    /**
+     * Creates the new component by name, but in addition will also see if
+     * there's a class mapped to the property name.
+     *
+     * @param string $name
+     * @param string $value
+     * @return Component
+     */
+    static public function create($name, $value = null) {
+
+        $name = strtoupper($name);
+
+        if (isset(self::$classMap[$name])) {
+            return new self::$classMap[$name]($name, $value);
+        } else {
+            return new self($name, $value);
+        }
+
+    }
+
+    /**
+     * Creates a new component.
+     *
+     * By default this object will iterate over its own children, but this can
+     * be overridden with the iterator argument
+     *
+     * @param string $name
+     * @param ElementList $iterator
+     */
+    public function __construct($name, ElementList $iterator = null) {
+
+        $this->name = strtoupper($name);
+        if (!is_null($iterator)) $this->iterator = $iterator;
+
+    }
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        $str = "BEGIN:" . $this->name . "\r\n";
+
+        /**
+         * Gives a component a 'score' for sorting purposes.
+         *
+         * This is solely used by the childrenSort method.
+         *
+         * A higher score means the item will be lower in the list.
+         * To avoid score collisions, each "score category" has a reasonable
+         * space to accomodate elements. The $key is added to the $score to
+         * preserve the original relative order of elements.
+         *
+         * @param int $key
+         * @param array $array
+         * @return int
+         */
+        $sortScore = function($key, $array) {
+
+            if ($array[$key] instanceof Component) {
+
+                // We want to encode VTIMEZONE first, this is a personal
+                // preference.
+                if ($array[$key]->name === 'VTIMEZONE') {
+                    $score=300000000;
+                    return $score+$key;
+                } else {
+                    $score=400000000;
+                    return $score+$key;
+                }
+            } else {
+                // Properties get encoded first
+                // VCARD version 4.0 wants the VERSION property to appear first
+                if ($array[$key] instanceof Property) {
+                    if ($array[$key]->name === 'VERSION') {
+                        $score=100000000;
+                        return $score+$key;
+                    } else {
+                        // All other properties
+                        $score=200000000;
+                        return $score+$key;
+                    }
+                }
+            }
+
+        };
+
+        $tmp = $this->children;
+        uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
+
+            $sA = $sortScore($a, $tmp);
+            $sB = $sortScore($b, $tmp);
+
+            if ($sA === $sB) return 0;
+
+            return ($sA < $sB) ? -1 : 1;
+
+        });
+
+        foreach($this->children as $child) $str.=$child->serialize();
+        $str.= "END:" . $this->name . "\r\n";
+
+        return $str;
+
+    }
+
+    /**
+     * Adds a new component or element
+     *
+     * You can call this method with the following syntaxes:
+     *
+     * add(Element $element)
+     * add(string $name, $value, array $parameters = array())
+     *
+     * The first version adds an Element
+     * The second adds a property as a string.
+     *
+     * @param mixed $item
+     * @param mixed $itemValue
+     * @return void
+     */
+    public function add($item, $itemValue = null, array $parameters = array()) {
+
+        if ($item instanceof Element) {
+            if (!is_null($itemValue)) {
+                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
+            }
+            $item->parent = $this;
+            $this->children[] = $item;
+        } elseif(is_string($item)) {
+
+            if (!is_scalar($itemValue)) {
+                throw new \InvalidArgumentException('The second argument must be scalar');
+            }
+            $item = Property::create($item,$itemValue, $parameters);
+            $item->parent = $this;
+            $this->children[] = $item;
+
+        } else {
+
+            throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Element or a string');
+
+        }
+
+    }
+
+    /**
+     * Returns an iterable list of children
+     *
+     * @return ElementList
+     */
+    public function children() {
+
+        return new ElementList($this->children);
+
+    }
+
+    /**
+     * Returns an array with elements that match the specified name.
+     *
+     * This function is also aware of MIME-Directory groups (as they appear in
+     * vcards). This means that if a property is grouped as "HOME.EMAIL", it
+     * will also be returned when searching for just "EMAIL". If you want to
+     * search for a property in a specific group, you can select on the entire
+     * string ("HOME.EMAIL"). If you want to search on a specific property that
+     * has not been assigned a group, specify ".EMAIL".
+     *
+     * Keys are retained from the 'children' array, which may be confusing in
+     * certain cases.
+     *
+     * @param string $name
+     * @return array
+     */
+    public function select($name) {
+
+        $group = null;
+        $name = strtoupper($name);
+        if (strpos($name,'.')!==false) {
+            list($group,$name) = explode('.', $name, 2);
+        }
+
+        $result = array();
+        foreach($this->children as $key=>$child) {
+
+            if (
+                strtoupper($child->name) === $name &&
+                (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
+            ) {
+
+                $result[$key] = $child;
+
+            }
+        }
+
+        reset($result);
+        return $result;
+
+    }
+
+    /**
+     * This method only returns a list of sub-components. Properties are
+     * ignored.
+     *
+     * @return array
+     */
+    public function getComponents() {
+
+        $result = array();
+        foreach($this->children as $child) {
+            if ($child instanceof Component) {
+                $result[] = $child;
+            }
+        }
+
+        return $result;
+
+    }
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   - Component::REPAIR - If something is broken, and automatic repair may
+     *                         be attempted.
+     *
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $result = array();
+        foreach($this->children as $child) {
+            $result = array_merge($result, $child->validate());
+        }
+        return $result;
+
+    }
+
+    /* Magic property accessors {{{ */
+
+    /**
+     * Using 'get' you will either get a property or component,
+     *
+     * If there were no child-elements found with the specified name,
+     * null is returned.
+     *
+     * @param string $name
+     * @return Property
+     */
+    public function __get($name) {
+
+        $matches = $this->select($name);
+        if (count($matches)===0) {
+            return null;
+        } else {
+            $firstMatch = current($matches);
+            /** @var $firstMatch Property */
+            $firstMatch->setIterator(new ElementList(array_values($matches)));
+            return $firstMatch;
+        }
+
+    }
+
+    /**
+     * This method checks if a sub-element with the specified name exists.
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function __isset($name) {
+
+        $matches = $this->select($name);
+        return count($matches)>0;
+
+    }
+
+    /**
+     * Using the setter method you can add properties or subcomponents
+     *
+     * You can either pass a Component, Property
+     * object, or a string to automatically create a Property.
+     *
+     * If the item already exists, it will be removed. If you want to add
+     * a new item with the same name, always use the add() method.
+     *
+     * @param string $name
+     * @param mixed $value
+     * @return void
+     */
+    public function __set($name, $value) {
+
+        $matches = $this->select($name);
+        $overWrite = count($matches)?key($matches):null;
+
+        if ($value instanceof Component || $value instanceof Property) {
+            $value->parent = $this;
+            if (!is_null($overWrite)) {
+                $this->children[$overWrite] = $value;
+            } else {
+                $this->children[] = $value;
+            }
+        } elseif (is_scalar($value)) {
+            $property = Property::create($name,$value);
+            $property->parent = $this;
+            if (!is_null($overWrite)) {
+                $this->children[$overWrite] = $property;
+            } else {
+                $this->children[] = $property;
+            }
+        } else {
+            throw new \InvalidArgumentException('You must pass a \\Sabre\\VObject\\Component, \\Sabre\\VObject\\Property or scalar type');
+        }
+
+    }
+
+    /**
+     * Removes all properties and components within this component.
+     *
+     * @param string $name
+     * @return void
+     */
+    public function __unset($name) {
+
+        $matches = $this->select($name);
+        foreach($matches as $k=>$child) {
+
+            unset($this->children[$k]);
+            $child->parent = null;
+
+        }
+
+    }
+
+    /* }}} */
+
+    /**
+     * This method is automatically called when the object is cloned.
+     * Specifically, this will ensure all child elements are also cloned.
+     *
+     * @return void
+     */
+    public function __clone() {
+
+        foreach($this->children as $key=>$child) {
+            $this->children[$key] = clone $child;
+            $this->children[$key]->parent = $this;
+        }
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VAlarm.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VAlarm.php
new file mode 100644 (file)
index 0000000..6bf7f5a
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+
+namespace Sabre\VObject\Component;
+use Sabre\VObject;
+
+/**
+ * VAlarm component
+ *
+ * This component contains some additional functionality specific for VALARMs.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VAlarm extends VObject\Component {
+
+    /**
+     * Returns a DateTime object when this alarm is going to trigger.
+     *
+     * This ignores repeated alarm, only the first trigger is returned.
+     *
+     * @return DateTime
+     */
+    public function getEffectiveTriggerTime() {
+
+        $trigger = $this->TRIGGER;
+        if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
+            $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
+            $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
+
+            $parentComponent = $this->parent;
+            if ($related === 'START') {
+                $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+                $effectiveTrigger->add($triggerDuration);
+            } else {
+                if ($parentComponent->name === 'VTODO') {
+                    $endProp = 'DUE';
+                } elseif ($parentComponent->name === 'VEVENT') {
+                    $endProp = 'DTEND';
+                } else {
+                    throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
+                }
+
+                if (isset($parentComponent->$endProp)) {
+                    $effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
+                    $effectiveTrigger->add($triggerDuration);
+                } elseif (isset($parentComponent->DURATION)) {
+                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+                    $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
+                    $effectiveTrigger->add($duration);
+                    $effectiveTrigger->add($triggerDuration);
+                } else {
+                    $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+                    $effectiveTrigger->add($triggerDuration);
+                }
+            }
+        } else {
+            $effectiveTrigger = $trigger->getDateTime();
+        }
+        return $effectiveTrigger;
+
+    }
+
+    /**
+     * Returns true or false depending on if the event falls in the specified
+     * time-range. This is used for filtering purposes.
+     *
+     * The rules used to determine if an event falls within the specified
+     * time-range is based on the CalDAV specification.
+     *
+     * @param \DateTime $start
+     * @param \DateTime $end
+     * @return bool
+     */
+    public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+        $effectiveTrigger = $this->getEffectiveTriggerTime();
+
+        if (isset($this->DURATION)) {
+            $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
+            $repeat = (string)$this->repeat;
+            if (!$repeat) {
+                $repeat = 1;
+            }
+
+            $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
+
+            foreach($period as $occurrence) {
+
+                if ($start <= $occurrence && $end > $occurrence) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
+        }
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VCalendar.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VCalendar.php
new file mode 100644 (file)
index 0000000..73f2f6d
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * The VCalendar component
+ *
+ * This component adds functionality to a component, specific for a VCALENDAR.
+ * 
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VCalendar extends VObject\Component {
+
+    /**
+     * Returns a list of all 'base components'. For instance, if an Event has 
+     * a recurrence rule, and one instance is overridden, the overridden event 
+     * will have the same UID, but will be excluded from this list.
+     *
+     * VTIMEZONE components will always be excluded. 
+     *
+     * @param string $componentName filter by component name 
+     * @return array 
+     */
+    public function getBaseComponents($componentName = null) {
+
+        $components = array();
+        foreach($this->children as $component) {
+
+            if (!$component instanceof VObject\Component)
+                continue;
+
+            if (isset($component->{'RECURRENCE-ID'})) 
+                continue;
+
+            if ($componentName && $component->name !== strtoupper($componentName)) 
+                continue;
+
+            if ($component->name === 'VTIMEZONE')
+                continue;
+
+            $components[] = $component;
+
+        }
+
+        return $components;
+
+    }
+
+    /**
+     * If this calendar object, has events with recurrence rules, this method 
+     * can be used to expand the event into multiple sub-events.
+     *
+     * Each event will be stripped from it's recurrence information, and only 
+     * the instances of the event in the specified timerange will be left 
+     * alone.
+     *
+     * In addition, this method will cause timezone information to be stripped, 
+     * and normalized to UTC.
+     *
+     * This method will alter the VCalendar. This cannot be reversed.
+     *
+     * This functionality is specifically used by the CalDAV standard. It is 
+     * possible for clients to request expand events, if they are rather simple 
+     * clients and do not have the possibility to calculate recurrences.
+     *
+     * @param DateTime $start
+     * @param DateTime $end 
+     * @return void
+     */
+    public function expand(\DateTime $start, \DateTime $end) {
+
+        $newEvents = array();
+
+        foreach($this->select('VEVENT') as $key=>$vevent) {
+
+            if (isset($vevent->{'RECURRENCE-ID'})) {
+                unset($this->children[$key]);
+                continue;
+            } 
+
+
+            if (!$vevent->rrule) {
+                unset($this->children[$key]);
+                if ($vevent->isInTimeRange($start, $end)) {
+                    $newEvents[] = $vevent;
+                }
+                continue;
+            }
+
+            $uid = (string)$vevent->uid;
+            if (!$uid) {
+                throw new \LogicException('Event did not have a UID!');
+            }
+
+            $it = new VObject\RecurrenceIterator($this, $vevent->uid);
+            $it->fastForward($start);
+
+            while($it->valid() && $it->getDTStart() < $end) {
+
+                if ($it->getDTEnd() > $start) {
+
+                    $newEvents[] = $it->getEventObject();
+
+                }
+                $it->next();
+
+            }
+            unset($this->children[$key]);
+
+        }
+
+        foreach($newEvents as $newEvent) {
+
+            foreach($newEvent->children as $child) {
+                if ($child instanceof VObject\Property\DateTime &&
+                    $child->getDateType() == VObject\Property\DateTime::LOCALTZ) {
+                        $child->setDateTime($child->getDateTime(),VObject\Property\DateTime::UTC);
+                    }
+            }
+
+            $this->add($newEvent);
+
+        }
+
+        // Removing all VTIMEZONE components
+        unset($this->VTIMEZONE);
+
+    } 
+
+    /**
+     * Validates the node for correctness.
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     * 
+     * @return array 
+     */
+    /*
+    public function validate() {
+
+        $warnings = array();
+
+        $version = $this->select('VERSION');
+        if (count($version)!==1) {
+            $warnings[] = array(
+                'level' => 1,
+                'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time',
+                'node' => $this,
+            );
+        } else {
+            if ((string)$this->VERSION !== '2.0') {
+                $warnings[] = array(
+                    'level' => 1,
+                    'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
+                    'node' => $this,
+                );
+            }
+        } 
+        $version = $this->select('PRODID');
+        if (count($version)!==1) {
+            $warnings[] = array(
+                'level' => 2,
+                'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time',
+                'node' => $this,
+            );
+        }
+        if (count($this->CALSCALE) > 1) {
+            $warnings[] = array(
+                'level' => 2,
+                'message' => 'The CALSCALE property must not be specified more than once.',
+                'node' => $this,
+            );
+        }
+        if (count($this->METHOD) > 1) {
+            $warnings[] = array(
+                'level' => 2,
+                'message' => 'The METHOD property must not be specified more than once.',
+                'node' => $this,
+            );
+        }
+
+        $allowedComponents = array(
+            'VEVENT',
+            'VTODO',
+            'VJOURNAL',
+            'VFREEBUSY',
+            'VTIMEZONE',
+        );
+        $allowedProperties = array(
+            'PRODID',
+            'VERSION',
+            'CALSCALE',
+            'METHOD',
+        );
+        $componentsFound = 0;
+        foreach($this->children as $child) {
+            if($child instanceof Component) {
+                $componentsFound++;
+                if (!in_array($child->name, $allowedComponents)) {
+                    $warnings[] = array(
+                        'level' => 1,
+                        'message' => 'The ' . $child->name . " component is not allowed in the VCALENDAR component",
+                        'node' => $this,
+                    );
+                }
+            }
+            if ($child instanceof Property) {
+                if (!in_array($child->name, $allowedProperties)) {
+                    $warnings[] = array(
+                        'level' => 2,
+                        'message' => 'The ' . $child->name . " property is not allowed in the VCALENDAR component",
+                        'node' => $this,
+                    );
+                }
+            }
+        }
+
+        if ($componentsFound===0) {
+            $warnings[] = array(
+                'level' => 1,
+                'message' => 'An iCalendar object must have at least 1 component.',
+                'node' => $this,
+            );
+        }
+
+        return array_merge(
+            $warnings,
+            parent::validate()
+        );
+
+    }
+     */
+
+}
+
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VCard.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VCard.php
new file mode 100644 (file)
index 0000000..002c4db
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * The VCard component
+ *
+ * This component represents the BEGIN:VCARD and END:VCARD found in every
+ * vcard.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VCard extends VObject\Component {
+
+    /**
+     * VCards with version 2.1, 3.0 and 4.0 are found.
+     *
+     * If the VCARD doesn't know its version, 4.0 is assumed.
+     */
+    const DEFAULT_VERSION = '4.0';
+
+    /**
+     * Validates the node for correctness.
+     *
+     * The following options are supported:
+     *   - Component::REPAIR - If something is broken, and automatic repair may
+     *                         be attempted.
+     *
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     *
+     * @param int $options
+     * @return array
+     */
+    public function validate($options = 0) {
+
+        $warnings = array();
+
+        $version = $this->select('VERSION');
+        if (count($version)!==1) {
+            $warnings[] = array(
+                'level' => 1,
+                'message' => 'The VERSION property must appear in the VCARD component exactly 1 time',
+                'node' => $this,
+            );
+            if ($options & self::REPAIR) {
+                $this->VERSION = self::DEFAULT_VERSION;
+            }
+        } else {
+            $version = (string)$this->VERSION;
+            if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
+                $warnings[] = array(
+                    'level' => 1,
+                    'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
+                    'node' => $this,
+                );
+                if ($options & self::REPAIR) {
+                    $this->VERSION = '4.0';
+                }
+            }
+
+        }
+        $version = $this->select('FN');
+        if (count($version)!==1) {
+            $warnings[] = array(
+                'level' => 1,
+                'message' => 'The FN property must appear in the VCARD component exactly 1 time',
+                'node' => $this,
+            );
+            if (($options & self::REPAIR) && count($version) === 0) {
+                // We're going to try to see if we can use the contents of the
+                // N property.
+                if (isset($this->N)) {
+                    $value = explode(';', (string)$this->N);
+                    if (isset($value[1]) && $value[1]) {
+                        $this->FN = $value[1] . ' ' . $value[0];
+                    } else {
+                        $this->FN = $value[0];
+                    }
+
+                // Otherwise, the ORG property may work
+                } elseif (isset($this->ORG)) {
+                    $this->FN = (string)$this->ORG;
+                }
+
+            }
+        }
+
+        return array_merge(
+            parent::validate($options),
+            $warnings
+        );
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VEvent.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VEvent.php
new file mode 100644 (file)
index 0000000..9d10966
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+namespace Sabre\VObject\Component;
+use Sabre\VObject;
+
+/**
+ * VEvent component
+ *
+ * This component contains some additional functionality specific for VEVENT's.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VEvent extends VObject\Component {
+
+    /**
+     * Returns true or false depending on if the event falls in the specified
+     * time-range. This is used for filtering purposes.
+     *
+     * The rules used to determine if an event falls within the specified
+     * time-range is based on the CalDAV specification.
+     *
+     * @param \DateTime $start
+     * @param \DateTime $end
+     * @return bool
+     */
+    public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+        if ($this->RRULE) {
+            $it = new VObject\RecurrenceIterator($this);
+            $it->fastForward($start);
+
+            // We fast-forwarded to a spot where the end-time of the
+            // recurrence instance exceeded the start of the requested
+            // time-range.
+            //
+            // If the starttime of the recurrence did not exceed the
+            // end of the time range as well, we have a match.
+            return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
+
+        }
+
+        $effectiveStart = $this->DTSTART->getDateTime();
+        if (isset($this->DTEND)) {
+
+            // The DTEND property is considered non inclusive. So for a 3 day
+            // event in july, dtstart and dtend would have to be July 1st and
+            // July 4th respectively.
+            //
+            // See:
+            // http://tools.ietf.org/html/rfc5545#page-54
+            $effectiveEnd = $this->DTEND->getDateTime();
+
+        } elseif (isset($this->DURATION)) {
+            $effectiveEnd = clone $effectiveStart;
+            $effectiveEnd->add( VObject\DateTimeParser::parseDuration($this->DURATION) );
+        } elseif ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
+            $effectiveEnd = clone $effectiveStart;
+            $effectiveEnd->modify('+1 day');
+        } else {
+            $effectiveEnd = clone $effectiveStart;
+        }
+        return (
+            ($start <= $effectiveEnd) && ($end > $effectiveStart)
+        );
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VJournal.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VJournal.php
new file mode 100644 (file)
index 0000000..f104a1f
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * VJournal component
+ *
+ * This component contains some additional functionality specific for VJOURNALs.
+ * 
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VJournal extends VObject\Component {
+
+    /**
+     * Returns true or false depending on if the event falls in the specified 
+     * time-range. This is used for filtering purposes. 
+     *
+     * The rules used to determine if an event falls within the specified 
+     * time-range is based on the CalDAV specification.
+     *
+     * @param DateTime $start
+     * @param DateTime $end 
+     * @return bool 
+     */
+    public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+        $dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
+        if ($dtstart) {
+            $effectiveEnd = clone $dtstart;
+            if ($this->DTSTART->getDateType() == VObject\Property\DateTime::DATE) {
+                $effectiveEnd->modify('+1 day');
+            }
+
+            return ($start <= $effectiveEnd && $end > $dtstart);
+
+        }
+        return false;
+
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Component/VTodo.php b/dav/sabre-vobject/lib/Sabre/VObject/Component/VTodo.php
new file mode 100644 (file)
index 0000000..5f879ae
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+/**
+ * VTodo component
+ *
+ * This component contains some additional functionality specific for VTODOs.
+ * 
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/) 
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class VTodo extends VObject\Component {
+
+    /**
+     * Returns true or false depending on if the event falls in the specified 
+     * time-range. This is used for filtering purposes. 
+     *
+     * The rules used to determine if an event falls within the specified 
+     * time-range is based on the CalDAV specification.
+     *
+     * @param DateTime $start
+     * @param DateTime $end 
+     * @return bool 
+     */
+    public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+        $dtstart = isset($this->DTSTART)?$this->DTSTART->getDateTime():null;
+        $duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null;
+        $due = isset($this->DUE)?$this->DUE->getDateTime():null;
+        $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
+        $created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
+
+        if ($dtstart) {
+            if ($duration) {
+                $effectiveEnd = clone $dtstart;
+                $effectiveEnd->add($duration);
+                return $start <= $effectiveEnd && $end > $dtstart;
+            } elseif ($due) {
+                return
+                    ($start < $due || $start <= $dtstart) &&
+                    ($end > $dtstart || $end >= $due);
+            } else {
+                return $start <= $dtstart && $end > $dtstart;
+            }
+        }
+        if ($due) {
+            return ($start < $due && $end >= $due);
+        }
+        if ($completed && $created) {
+            return
+                ($start <= $created || $start <= $completed) &&
+                ($end >= $created || $end >= $completed);
+        }
+        if ($completed) {
+            return ($start <= $completed && $end >= $completed);
+        }
+        if ($created) {
+            return ($end > $created);
+        }
+        return true;
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/DateTimeParser.php b/dav/sabre-vobject/lib/Sabre/VObject/DateTimeParser.php
new file mode 100644 (file)
index 0000000..0ef8460
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * DateTimeParser
+ *
+ * This class is responsible for parsing the several different date and time
+ * formats iCalendar and vCards have.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class DateTimeParser {
+
+    /**
+     * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
+     *
+     * Specifying a reference timezone is optional. It will only be used
+     * if the non-UTC format is used. The argument is used as a reference, the
+     * returned DateTime object will still be in the UTC timezone.
+     *
+     * @param string $dt
+     * @param DateTimeZone $tz
+     * @return DateTime
+     */
+    static public function parseDateTime($dt,\DateTimeZone $tz = null) {
+
+        // Format is YYYYMMDD + "T" + hhmmss
+        $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
+
+        if (!$result) {
+            throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
+        }
+
+        if ($matches[7]==='Z' || is_null($tz)) {
+            $tz = new \DateTimeZone('UTC');
+        }
+        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
+
+        // Still resetting the timezone, to normalize everything to UTC
+        $date->setTimeZone(new \DateTimeZone('UTC'));
+        return $date;
+
+    }
+
+    /**
+     * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
+     *
+     * @param string $date
+     * @return DateTime
+     */
+    static public function parseDate($date) {
+
+        // Format is YYYYMMDD
+        $result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
+
+        if (!$result) {
+            throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
+        }
+
+        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
+        return $date;
+
+    }
+
+    /**
+     * Parses an iCalendar (RFC5545) formatted duration value.
+     *
+     * This method will either return a DateTimeInterval object, or a string
+     * suitable for strtotime or DateTime::modify.
+     *
+     * @param string $duration
+     * @param bool $asString
+     * @return DateInterval|string
+     */
+    static public function parseDuration($duration, $asString = false) {
+
+        $result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
+        if (!$result) {
+            throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
+        }
+
+        if (!$asString) {
+            $invert = false;
+            if ($matches['plusminus']==='-') {
+                $invert = true;
+            }
+
+
+            $parts = array(
+                'week',
+                'day',
+                'hour',
+                'minute',
+                'second',
+            );
+            foreach($parts as $part) {
+                $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
+            }
+
+
+            // We need to re-construct the $duration string, because weeks and
+            // days are not supported by DateInterval in the same string.
+            $duration = 'P';
+            $days = $matches['day'];
+            if ($matches['week']) {
+                $days+=$matches['week']*7;
+            }
+            if ($days)
+                $duration.=$days . 'D';
+
+            if ($matches['minute'] || $matches['second'] || $matches['hour']) {
+                $duration.='T';
+
+                if ($matches['hour'])
+                    $duration.=$matches['hour'].'H';
+
+                if ($matches['minute'])
+                    $duration.=$matches['minute'].'M';
+
+                if ($matches['second'])
+                    $duration.=$matches['second'].'S';
+
+            }
+
+            if ($duration==='P') {
+                $duration = 'PT0S';
+            }
+            $iv = new \DateInterval($duration);
+            if ($invert) $iv->invert = true;
+
+            return $iv;
+
+        }
+
+
+
+        $parts = array(
+            'week',
+            'day',
+            'hour',
+            'minute',
+            'second',
+        );
+
+        $newDur = '';
+        foreach($parts as $part) {
+            if (isset($matches[$part]) && $matches[$part]) {
+                $newDur.=' '.$matches[$part] . ' ' . $part . 's';
+            }
+        }
+
+        $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
+        if ($newDur === '+') { $newDur = '+0 seconds'; };
+        return $newDur;
+
+    }
+
+    /**
+     * Parses either a Date or DateTime, or Duration value.
+     *
+     * @param string $date
+     * @param DateTimeZone|string $referenceTZ
+     * @return DateTime|DateInterval
+     */
+    static public function parse($date, $referenceTZ = null) {
+
+        if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
+            return self::parseDuration($date);
+        } elseif (strlen($date)===8) {
+            return self::parseDate($date);
+        } else {
+            return self::parseDateTime($date, $referenceTZ);
+        }
+
+    }
+
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Element.php b/dav/sabre-vobject/lib/Sabre/VObject/Element.php
new file mode 100644 (file)
index 0000000..151ae76
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Base class for all elements
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+abstract class Element extends Node {
+
+    public $parent = null;
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/ElementList.php b/dav/sabre-vobject/lib/Sabre/VObject/ElementList.php
new file mode 100644 (file)
index 0000000..b7f1c8e
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * VObject ElementList
+ *
+ * This class represents a list of elements. Lists are the result of queries,
+ * such as doing $vcalendar->vevent where there's multiple VEVENT objects.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class ElementList implements \Iterator, \Countable, \ArrayAccess {
+
+    /**
+     * Inner elements
+     *
+     * @var array
+     */
+    protected $elements = array();
+
+    /**
+     * Creates the element list.
+     *
+     * @param array $elements
+     */
+    public function __construct(array $elements) {
+
+        $this->elements = $elements;
+
+    }
+
+    /* {{{ Iterator interface */
+
+    /**
+     * Current position
+     *
+     * @var int
+     */
+    private $key = 0;
+
+    /**
+     * Returns current item in iteration
+     *
+     * @return Element
+     */
+    public function current() {
+
+        return $this->elements[$this->key];
+
+    }
+
+    /**
+     * To the next item in the iterator
+     *
+     * @return void
+     */
+    public function next() {
+
+        $this->key++;
+
+    }
+
+    /**
+     * Returns the current iterator key
+     *
+     * @return int
+     */
+    public function key() {
+
+        return $this->key;
+
+    }
+
+    /**
+     * Returns true if the current position in the iterator is a valid one
+     *
+     * @return bool
+     */
+    public function valid() {
+
+        return isset($this->elements[$this->key]);
+
+    }
+
+    /**
+     * Rewinds the iterator
+     *
+     * @return void
+     */
+    public function rewind() {
+
+        $this->key = 0;
+
+    }
+
+    /* }}} */
+
+    /* {{{ Countable interface */
+
+    /**
+     * Returns the number of elements
+     *
+     * @return int
+     */
+    public function count() {
+
+        return count($this->elements);
+
+    }
+
+    /* }}} */
+
+    /* {{{ ArrayAccess Interface */
+
+
+    /**
+     * Checks if an item exists through ArrayAccess.
+     *
+     * @param int $offset
+     * @return bool
+     */
+    public function offsetExists($offset) {
+
+        return isset($this->elements[$offset]);
+
+    }
+
+    /**
+     * Gets an item through ArrayAccess.
+     *
+     * @param int $offset
+     * @return mixed
+     */
+    public function offsetGet($offset) {
+
+        return $this->elements[$offset];
+
+    }
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * @param int $offset
+     * @param mixed $value
+     * @return void
+     */
+    public function offsetSet($offset,$value) {
+
+        throw new \LogicException('You can not add new objects to an ElementList');
+
+    }
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return void
+     */
+    public function offsetUnset($offset) {
+
+        throw new \LogicException('You can not remove objects from an ElementList');
+
+    }
+
+    /* }}} */
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/FreeBusyGenerator.php b/dav/sabre-vobject/lib/Sabre/VObject/FreeBusyGenerator.php
new file mode 100644 (file)
index 0000000..bfb89b0
--- /dev/null
@@ -0,0 +1,322 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * This class helps with generating FREEBUSY reports based on existing sets of
+ * objects.
+ *
+ * It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
+ * generates a single VFREEBUSY object.
+ *
+ * VFREEBUSY components are described in RFC5545, The rules for what should
+ * go in a single freebusy report is taken from RFC4791, section 7.10.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class FreeBusyGenerator {
+
+    /**
+     * Input objects
+     *
+     * @var array
+     */
+    protected $objects;
+
+    /**
+     * Start of range
+     *
+     * @var DateTime|null
+     */
+    protected $start;
+
+    /**
+     * End of range
+     *
+     * @var DateTime|null
+     */
+    protected $end;
+
+    /**
+     * VCALENDAR object
+     *
+     * @var Component
+     */
+    protected $baseObject;
+
+    /**
+     * Creates the generator.
+     *
+     * Check the setTimeRange and setObjects methods for details about the
+     * arguments.
+     *
+     * @param DateTime $start
+     * @param DateTime $end
+     * @param mixed $objects
+     * @return void
+     */
+    public function __construct(\DateTime $start = null, \DateTime $end = null, $objects = null) {
+
+        if ($start && $end) {
+            $this->setTimeRange($start, $end);
+        }
+
+        if ($objects) {
+            $this->setObjects($objects);
+        }
+
+    }
+
+    /**
+     * Sets the VCALENDAR object.
+     *
+     * If this is set, it will not be generated for you. You are responsible
+     * for setting things like the METHOD, CALSCALE, VERSION, etc..
+     *
+     * The VFREEBUSY object will be automatically added though.
+     *
+     * @param Component $vcalendar
+     * @return void
+     */
+    public function setBaseObject(Component $vcalendar) {
+
+        $this->baseObject = $vcalendar;
+
+    }
+
+    /**
+     * Sets the input objects
+     *
+     * You must either specify a valendar object as a strong, or as the parse
+     * Component.
+     * It's also possible to specify multiple objects as an array.
+     *
+     * @param mixed $objects
+     * @return void
+     */
+    public function setObjects($objects) {
+
+        if (!is_array($objects)) {
+            $objects = array($objects);
+        }
+
+        $this->objects = array();
+        foreach($objects as $object) {
+
+            if (is_string($object)) {
+                $this->objects[] = Reader::read($object);
+            } elseif ($object instanceof Component) {
+                $this->objects[] = $object;
+            } else {
+                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
+            }
+
+        }
+
+    }
+
+    /**
+     * Sets the time range
+     *
+     * Any freebusy object falling outside of this time range will be ignored.
+     *
+     * @param DateTime $start
+     * @param DateTime $end
+     * @return void
+     */
+    public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
+
+        $this->start = $start;
+        $this->end = $end;
+
+    }
+
+    /**
+     * Parses the input data and returns a correct VFREEBUSY object, wrapped in
+     * a VCALENDAR.
+     *
+     * @return Component
+     */
+    public function getResult() {
+
+        $busyTimes = array();
+
+        foreach($this->objects as $object) {
+
+            foreach($object->getBaseComponents() as $component) {
+
+                switch($component->name) {
+
+                    case 'VEVENT' :
+
+                        $FBTYPE = 'BUSY';
+                        if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
+                            break;
+                        }
+                        if (isset($component->STATUS)) {
+                            $status = strtoupper($component->STATUS);
+                            if ($status==='CANCELLED') {
+                                break;
+                            }
+                            if ($status==='TENTATIVE') {
+                                $FBTYPE = 'BUSY-TENTATIVE';
+                            }
+                        }
+
+                        $times = array();
+
+                        if ($component->RRULE) {
+
+                            $iterator = new RecurrenceIterator($object, (string)$component->uid);
+                            if ($this->start) {
+                                $iterator->fastForward($this->start);
+                            }
+
+                            $maxRecurrences = 200;
+
+                            while($iterator->valid() && --$maxRecurrences) {
+
+                                $startTime = $iterator->getDTStart();
+                                if ($this->end && $startTime > $this->end) {
+                                    break;
+                                }
+                                $times[] = array(
+                                    $iterator->getDTStart(),
+                                    $iterator->getDTEnd(),
+                                );
+
+                                $iterator->next();
+
+                            }
+
+                        } else {
+
+                            $startTime = $component->DTSTART->getDateTime();
+                            if ($this->end && $startTime > $this->end) {
+                                break;
+                            }
+                            $endTime = null;
+                            if (isset($component->DTEND)) {
+                                $endTime = $component->DTEND->getDateTime();
+                            } elseif (isset($component->DURATION)) {
+                                $duration = DateTimeParser::parseDuration((string)$component->DURATION);
+                                $endTime = clone $startTime;
+                                $endTime->add($duration);
+                            } elseif ($component->DTSTART->getDateType() === Property\DateTime::DATE) {
+                                $endTime = clone $startTime;
+                                $endTime->modify('+1 day');
+                            } else {
+                                // The event had no duration (0 seconds)
+                                break;
+                            }
+
+                            $times[] = array($startTime, $endTime);
+
+                        }
+
+                        foreach($times as $time) {
+
+                            if ($this->end && $time[0] > $this->end) break;
+                            if ($this->start && $time[1] < $this->start) break;
+
+                            $busyTimes[] = array(
+                                $time[0],
+                                $time[1],
+                                $FBTYPE,
+                            );
+                        }
+                        break;
+
+                    case 'VFREEBUSY' :
+                        foreach($component->FREEBUSY as $freebusy) {
+
+                            $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
+
+                            // Skipping intervals marked as 'free'
+                            if ($fbType==='FREE')
+                                continue;
+
+                            $values = explode(',', $freebusy);
+                            foreach($values as $value) {
+                                list($startTime, $endTime) = explode('/', $value);
+                                $startTime = DateTimeParser::parseDateTime($startTime);
+
+                                if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
+                                    $duration = DateTimeParser::parseDuration($endTime);
+                                    $endTime = clone $startTime;
+                                    $endTime->add($duration);
+                                } else {
+                                    $endTime = DateTimeParser::parseDateTime($endTime);
+                                }
+
+                                if($this->start && $this->start > $endTime) continue;
+                                if($this->end && $this->end < $startTime) continue;
+                                $busyTimes[] = array(
+                                    $startTime,
+                                    $endTime,
+                                    $fbType
+                                );
+
+                            }
+
+
+                        }
+                        break;
+
+
+
+                }
+
+
+            }
+
+        }
+
+        if ($this->baseObject) {
+            $calendar = $this->baseObject;
+        } else {
+            $calendar = new Component('VCALENDAR');
+            $calendar->version = '2.0';
+            $calendar->prodid = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
+            $calendar->calscale = 'GREGORIAN';
+        }
+
+        $vfreebusy = new Component('VFREEBUSY');
+        $calendar->add($vfreebusy);
+
+        if ($this->start) {
+            $dtstart = new Property\DateTime('DTSTART');
+            $dtstart->setDateTime($this->start,Property\DateTime::UTC);
+            $vfreebusy->add($dtstart);
+        }
+        if ($this->end) {
+            $dtend = new Property\DateTime('DTEND');
+            $dtend->setDateTime($this->start,Property\DateTime::UTC);
+            $vfreebusy->add($dtend);
+        }
+        $dtstamp = new Property\DateTime('DTSTAMP');
+        $dtstamp->setDateTime(new \DateTime('now'), Property\DateTime::UTC);
+        $vfreebusy->add($dtstamp);
+
+        foreach($busyTimes as $busyTime) {
+
+            $busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
+            $busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
+
+            $prop = new Property(
+                'FREEBUSY',
+                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
+            );
+            $prop['FBTYPE'] = $busyTime[2];
+            $vfreebusy->add($prop);
+
+        }
+
+        return $calendar;
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Node.php b/dav/sabre-vobject/lib/Sabre/VObject/Node.php
new file mode 100644 (file)
index 0000000..88d1d8c
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Base class for all nodes
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+abstract class Node implements \IteratorAggregate, \ArrayAccess, \Countable {
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    abstract function serialize();
+
+    /**
+     * Iterator override
+     *
+     * @var ElementList
+     */
+    protected $iterator = null;
+
+    /**
+     * A link to the parent node
+     *
+     * @var Node
+     */
+    public $parent = null;
+
+    /**
+     * Validates the node for correctness.
+     * An array is returned with warnings.
+     *
+     * Every item in the array has the following properties:
+     *    * level - (number between 1 and 3 with severity information)
+     *    * message - (human readable message)
+     *    * node - (reference to the offending node)
+     *
+     * @return array
+     */
+    public function validate() {
+
+        return array();
+
+    }
+
+    /* {{{ IteratorAggregator interface */
+
+    /**
+     * Returns the iterator for this object
+     *
+     * @return ElementList
+     */
+    public function getIterator() {
+
+        if (!is_null($this->iterator))
+            return $this->iterator;
+
+        return new ElementList(array($this));
+
+    }
+
+    /**
+     * Sets the overridden iterator
+     *
+     * Note that this is not actually part of the iterator interface
+     *
+     * @param ElementList $iterator
+     * @return void
+     */
+    public function setIterator(ElementList $iterator) {
+
+        $this->iterator = $iterator;
+
+    }
+
+    /* }}} */
+
+    /* {{{ Countable interface */
+
+    /**
+     * Returns the number of elements
+     *
+     * @return int
+     */
+    public function count() {
+
+        $it = $this->getIterator();
+        return $it->count();
+
+    }
+
+    /* }}} */
+
+    /* {{{ ArrayAccess Interface */
+
+
+    /**
+     * Checks if an item exists through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return bool
+     */
+    public function offsetExists($offset) {
+
+        $iterator = $this->getIterator();
+        return $iterator->offsetExists($offset);
+
+    }
+
+    /**
+     * Gets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return mixed
+     */
+    public function offsetGet($offset) {
+
+        $iterator = $this->getIterator();
+        return $iterator->offsetGet($offset);
+
+    }
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @param mixed $value
+     * @return void
+     */
+    public function offsetSet($offset,$value) {
+
+        $iterator = $this->getIterator();
+        $iterator->offsetSet($offset,$value);
+
+    }
+
+    /**
+     * Sets an item through ArrayAccess.
+     *
+     * This method just forwards the request to the inner iterator
+     *
+     * @param int $offset
+     * @return void
+     */
+    public function offsetUnset($offset) {
+
+        $iterator = $this->getIterator();
+        $iterator->offsetUnset($offset);
+
+    }
+
+    /* }}} */
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Parameter.php b/dav/sabre-vobject/lib/Sabre/VObject/Parameter.php
new file mode 100644 (file)
index 0000000..0355b0a
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * VObject Parameter
+ *
+ * This class represents a parameter. A parameter is always tied to a property.
+ * In the case of:
+ *   DTSTART;VALUE=DATE:20101108
+ * VALUE=DATE would be the parameter name and value.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Parameter extends Node {
+
+    /**
+     * Parameter name
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Parameter value
+     *
+     * @var string
+     */
+    public $value;
+
+    /**
+     * Sets up the object
+     *
+     * @param string $name
+     * @param string $value
+     */
+    public function __construct($name, $value = null) {
+
+        $this->name = strtoupper($name);
+        $this->value = $value;
+
+    }
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        if (is_null($this->value)) {
+            return $this->name;
+        }
+        $src = array(
+            '\\',
+            "\n",
+            ';',
+            ',',
+        );
+        $out = array(
+            '\\\\',
+            '\n',
+            '\;',
+            '\,',
+        );
+
+        return $this->name . '=' . str_replace($src, $out, $this->value);
+
+    }
+
+    /**
+     * Called when this object is being cast to a string
+     *
+     * @return string
+     */
+    public function __toString() {
+
+        return $this->value;
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/ParseException.php b/dav/sabre-vobject/lib/Sabre/VObject/ParseException.php
new file mode 100644 (file)
index 0000000..91386fe
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Exception thrown by Reader if an invalid object was attempted to be parsed.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class ParseException extends \Exception { }
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Property.php b/dav/sabre-vobject/lib/Sabre/VObject/Property.php
new file mode 100644 (file)
index 0000000..d5b95de
--- /dev/null
@@ -0,0 +1,365 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * VObject Property
+ *
+ * A property in VObject is usually in the form PARAMNAME:paramValue.
+ * An example is : SUMMARY:Weekly meeting
+ *
+ * Properties can also have parameters:
+ * SUMMARY;LANG=en:Weekly meeting.
+ *
+ * Parameters can be accessed using the ArrayAccess interface.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Property extends Element {
+
+    /**
+     * Propertyname
+     *
+     * @var string
+     */
+    public $name;
+
+    /**
+     * Group name
+     *
+     * This may be something like 'HOME' for vcards.
+     *
+     * @var string
+     */
+    public $group;
+
+    /**
+     * Property parameters
+     *
+     * @var array
+     */
+    public $parameters = array();
+
+    /**
+     * Property value
+     *
+     * @var string
+     */
+    public $value;
+
+    /**
+     * If properties are added to this map, they will be automatically mapped
+     * to their respective classes, if parsed by the reader or constructed with
+     * the 'create' method.
+     *
+     * @var array
+     */
+    static public $classMap = array(
+        'COMPLETED'     => 'Sabre\\VObject\\Property\\DateTime',
+        'CREATED'       => 'Sabre\\VObject\\Property\\DateTime',
+        'DTEND'         => 'Sabre\\VObject\\Property\\DateTime',
+        'DTSTAMP'       => 'Sabre\\VObject\\Property\\DateTime',
+        'DTSTART'       => 'Sabre\\VObject\\Property\\DateTime',
+        'DUE'           => 'Sabre\\VObject\\Property\\DateTime',
+        'EXDATE'        => 'Sabre\\VObject\\Property\\MultiDateTime',
+        'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\DateTime',
+        'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\DateTime',
+        'TRIGGER'       => 'Sabre\\VObject\\Property\\DateTime',
+    );
+
+    /**
+     * Creates the new property by name, but in addition will also see if
+     * there's a class mapped to the property name.
+     *
+     * Parameters can be specified with the optional third argument. Parameters
+     * must be a key->value map of the parameter name, and value. If the value
+     * is specified as an array, it is assumed that multiple parameters with
+     * the same name should be added.
+     *
+     * @param string $name
+     * @param string $value
+     * @param array $parameters
+     * @return Property
+     */
+    static public function create($name, $value = null, array $parameters = array()) {
+
+        $name = strtoupper($name);
+        $shortName = $name;
+        $group = null;
+        if (strpos($shortName,'.')!==false) {
+            list($group, $shortName) = explode('.', $shortName);
+        }
+
+        if (isset(self::$classMap[$shortName])) {
+            return new self::$classMap[$shortName]($name, $value, $parameters);
+        } else {
+            return new self($name, $value, $parameters);
+        }
+
+    }
+
+    /**
+     * Creates a new property object
+     *
+     * Parameters can be specified with the optional third argument. Parameters
+     * must be a key->value map of the parameter name, and value. If the value
+     * is specified as an array, it is assumed that multiple parameters with
+     * the same name should be added.
+     *
+     * @param string $name
+     * @param string $value
+     * @param array $parameters
+     */
+    public function __construct($name, $value = null, array $parameters = array()) {
+
+        $name = strtoupper($name);
+        $group = null;
+        if (strpos($name,'.')!==false) {
+            list($group, $name) = explode('.', $name);
+        }
+        $this->name = $name;
+        $this->group = $group;
+        $this->setValue($value);
+
+        foreach($parameters as $paramName => $paramValues) {
+
+            if (!is_array($paramValues)) {
+                $paramValues = array($paramValues);
+            }
+
+            foreach($paramValues as $paramValue) {
+                $this->add($paramName, $paramValue);
+            }
+
+        }
+
+    }
+
+    /**
+     * Updates the internal value
+     *
+     * @param string $value
+     * @return void
+     */
+    public function setValue($value) {
+
+        $this->value = $value;
+
+    }
+
+    /**
+     * Turns the object back into a serialized blob.
+     *
+     * @return string
+     */
+    public function serialize() {
+
+        $str = $this->name;
+        if ($this->group) $str = $this->group . '.' . $this->name;
+
+        if (count($this->parameters)) {
+            foreach($this->parameters as $param) {
+
+                $str.=';' . $param->serialize();
+
+            }
+        }
+        $src = array(
+            '\\',
+            "\n",
+        );
+        $out = array(
+            '\\\\',
+            '\n',
+        );
+        $str.=':' . str_replace($src, $out, $this->value);
+
+        $out = '';
+        while(strlen($str)>0) {
+            if (strlen($str)>75) {
+                $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
+                $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
+            } else {
+                $out.=$str . "\r\n";
+                $str='';
+                break;
+            }
+        }
+
+        return $out;
+
+    }
+
+    /**
+     * Adds a new componenten or element
+     *
+     * You can call this method with the following syntaxes:
+     *
+     * add(Parameter $element)
+     * add(string $name, $value)
+     *
+     * The first version adds an Parameter
+     * The second adds a property as a string.
+     *
+     * @param mixed $item
+     * @param mixed $itemValue
+     * @return void
+     */
+    public function add($item, $itemValue = null) {
+
+        if ($item instanceof Parameter) {
+            if (!is_null($itemValue)) {
+                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
+            }
+            $item->parent = $this;
+            $this->parameters[] = $item;
+        } elseif(is_string($item)) {
+
+            if (!is_scalar($itemValue) && !is_null($itemValue)) {
+                throw new \InvalidArgumentException('The second argument must be scalar');
+            }
+            $parameter = new Parameter($item,$itemValue);
+            $parameter->parent = $this;
+            $this->parameters[] = $parameter;
+
+        } else {
+
+            throw new \InvalidArgumentException('The first argument must either be a Element or a string');
+
+        }
+
+    }
+
+    /* ArrayAccess interface {{{ */
+
+    /**
+     * Checks if an array element exists
+     *
+     * @param mixed $name
+     * @return bool
+     */
+    public function offsetExists($name) {
+
+        if (is_int($name)) return parent::offsetExists($name);
+
+        $name = strtoupper($name);
+
+        foreach($this->parameters as $parameter) {
+            if ($parameter->name == $name) return true;
+        }
+        return false;
+
+    }
+
+    /**
+     * Returns a parameter, or parameter list.
+     *
+     * @param string $name
+     * @return Element
+     */
+    public function offsetGet($name) {
+
+        if (is_int($name)) return parent::offsetGet($name);
+        $name = strtoupper($name);
+
+        $result = array();
+        foreach($this->parameters as $parameter) {
+            if ($parameter->name == $name)
+                $result[] = $parameter;
+        }
+
+        if (count($result)===0) {
+            return null;
+        } elseif (count($result)===1) {
+            return $result[0];
+        } else {
+            $result[0]->setIterator(new ElementList($result));
+            return $result[0];
+        }
+
+    }
+
+    /**
+     * Creates a new parameter
+     *
+     * @param string $name
+     * @param mixed $value
+     * @return void
+     */
+    public function offsetSet($name, $value) {
+
+        if (is_int($name)) parent::offsetSet($name, $value);
+
+        if (is_scalar($value)) {
+            if (!is_string($name))
+                throw new \InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.');
+
+            $this->offsetUnset($name);
+            $parameter = new Parameter($name, $value);
+            $parameter->parent = $this;
+            $this->parameters[] = $parameter;
+
+        } elseif ($value instanceof Parameter) {
+            if (!is_null($name))
+                throw new \InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a \\Sabre\\VObject\\Parameter. Add using $array[]=$parameterObject.');
+
+            $value->parent = $this;
+            $this->parameters[] = $value;
+        } else {
+            throw new \InvalidArgumentException('You can only add parameters to the property object');
+        }
+
+    }
+
+    /**
+     * Removes one or more parameters with the specified name
+     *
+     * @param string $name
+     * @return void
+     */
+    public function offsetUnset($name) {
+
+        if (is_int($name)) parent::offsetUnset($name);
+        $name = strtoupper($name);
+
+        foreach($this->parameters as $key=>$parameter) {
+            if ($parameter->name == $name) {
+                $parameter->parent = null;
+                unset($this->parameters[$key]);
+            }
+
+        }
+
+    }
+
+    /* }}} */
+
+    /**
+     * Called when this object is being cast to a string
+     *
+     * @return string
+     */
+    public function __toString() {
+
+        return (string)$this->value;
+
+    }
+
+    /**
+     * This method is automatically called when the object is cloned.
+     * Specifically, this will ensure all child elements are also cloned.
+     *
+     * @return void
+     */
+    public function __clone() {
+
+        foreach($this->parameters as $key=>$child) {
+            $this->parameters[$key] = clone $child;
+            $this->parameters[$key]->parent = $this;
+        }
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Property/DateTime.php b/dav/sabre-vobject/lib/Sabre/VObject/Property/DateTime.php
new file mode 100644 (file)
index 0000000..556cd44
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use Sabre\VObject;
+
+/**
+ * DateTime property
+ *
+ * This element is used for iCalendar properties such as the DTSTART property.
+ * It basically provides a few helper functions that make it easier to deal
+ * with these. It supports both DATE-TIME and DATE values.
+ *
+ * In order to use this correctly, you must call setDateTime and getDateTime to
+ * retrieve and modify dates respectively.
+ *
+ * If you use the 'value' or properties directly, this object does not keep
+ * reference and results might appear incorrectly.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class DateTime extends VObject\Property {
+
+    /**
+     * Local 'floating' time
+     */
+    const LOCAL = 1;
+
+    /**
+     * UTC-based time
+     */
+    const UTC = 2;
+
+    /**
+     * Local time plus timezone
+     */
+    const LOCALTZ = 3;
+
+    /**
+     * Only a date, time is ignored
+     */
+    const DATE = 4;
+
+    /**
+     * DateTime representation
+     *
+     * @var \DateTime
+     */
+    protected $dateTime;
+
+    /**
+     * dateType
+     *
+     * @var int
+     */
+    protected $dateType;
+
+    /**
+     * Updates the Date and Time.
+     *
+     * @param \DateTime $dt
+     * @param int $dateType
+     * @return void
+     */
+    public function setDateTime(\DateTime $dt, $dateType = self::LOCALTZ) {
+
+        switch($dateType) {
+
+            case self::LOCAL :
+                $this->setValue($dt->format('Ymd\\THis'));
+                $this->offsetUnset('VALUE');
+                $this->offsetUnset('TZID');
+                $this->offsetSet('VALUE','DATE-TIME');
+                break;
+            case self::UTC :
+                $dt->setTimeZone(new \DateTimeZone('UTC'));
+                $this->setValue($dt->format('Ymd\\THis\\Z'));
+                $this->offsetUnset('VALUE');
+                $this->offsetUnset('TZID');
+                $this->offsetSet('VALUE','DATE-TIME');
+                break;
+            case self::LOCALTZ :
+                $this->setValue($dt->format('Ymd\\THis'));
+                $this->offsetUnset('VALUE');
+                $this->offsetUnset('TZID');
+                $this->offsetSet('VALUE','DATE-TIME');
+                $this->offsetSet('TZID', $dt->getTimeZone()->getName());
+                break;
+            case self::DATE :
+                $this->setValue($dt->format('Ymd'));
+                $this->offsetUnset('VALUE');
+                $this->offsetUnset('TZID');
+                $this->offsetSet('VALUE','DATE');
+                break;
+            default :
+                throw new \InvalidArgumentException('You must pass a valid dateType constant');
+
+        }
+        $this->dateTime = $dt;
+        $this->dateType = $dateType;
+
+    }
+
+    /**
+     * Returns the current DateTime value.
+     *
+     * If no value was set, this method returns null.
+     *
+     * @return \DateTime|null
+     */
+    public function getDateTime() {
+
+        if ($this->dateTime)
+            return $this->dateTime;
+
+        list(
+            $this->dateType,
+            $this->dateTime
+        ) = self::parseData($this->value, $this);
+        return $this->dateTime;
+
+    }
+
+    /**
+     * Returns the type of Date format.
+     *
+     * This method returns one of the format constants. If no date was set,
+     * this method will return null.
+     *
+     * @return int|null
+     */
+    public function getDateType() {
+
+        if ($this->dateType)
+            return $this->dateType;
+
+        list(
+            $this->dateType,
+            $this->dateTime,
+        ) = self::parseData($this->value, $this);
+        return $this->dateType;
+
+    }
+
+    /**
+     * Parses the internal data structure to figure out what the current date
+     * and time is.
+     *
+     * The returned array contains two elements:
+     *   1. A 'DateType' constant (as defined on this class), or null.
+     *   2. A DateTime object (or null)
+     *
+     * @param string|null $propertyValue The string to parse (yymmdd or
+     *                                   ymmddThhmmss, etc..)
+     * @param \Sabre\VObject\Property|null $property The instance of the
+     *                                              property we're parsing.
+     * @return array
+     */
+    static public function parseData($propertyValue, VObject\Property $property = null) {
+
+        if (is_null($propertyValue)) {
+            return array(null, null);
+        }
+
+        $date = '(?P<year>[1-2][0-9]{3})(?P<month>[0-1][0-9])(?P<date>[0-3][0-9])';
+        $time = '(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9])';
+        $regex = "/^$date(T$time(?P<isutc>Z)?)?$/";
+
+        if (!preg_match($regex, $propertyValue, $matches)) {
+            throw new \InvalidArgumentException($propertyValue . ' is not a valid \DateTime or Date string');
+        }
+
+        if (!isset($matches['hour'])) {
+            // Date-only
+            return array(
+                self::DATE,
+                new \DateTime($matches['year'] . '-' . $matches['month'] . '-' . $matches['date'] . ' 00:00:00', new \DateTimeZone('UTC')),
+            );
+        }
+
+        $dateStr =
+            $matches['year'] .'-' .
+            $matches['month'] . '-' .
+            $matches['date'] . ' ' .
+            $matches['hour'] . ':' .
+            $matches['minute'] . ':' .
+            $matches['second'];
+
+        if (isset($matches['isutc'])) {
+            $dt = new \DateTime($dateStr,new \DateTimeZone('UTC'));
+            $dt->setTimeZone(new \DateTimeZone('UTC'));
+            return array(
+                self::UTC,
+                $dt
+            );
+        }
+
+        // Finding the timezone.
+        $tzid = $property['TZID'];
+        if (!$tzid) {
+            // This was a floating time string. This implies we use the
+            // timezone from date_default_timezone_set / date.timezone ini
+            // setting.
+            return array(
+                self::LOCAL,
+                new \DateTime($dateStr)
+            );
+        }
+
+        // To look up the timezone, we must first find the VCALENDAR component.
+        $root = $property;
+        while($root->parent) {
+            $root = $root->parent;
+        }
+        if ($root->name === 'VCALENDAR') {
+            $tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid, $root);
+        } else {
+            $tz = VObject\TimeZoneUtil::getTimeZone((string)$tzid);
+        }
+
+        $dt = new \DateTime($dateStr, $tz);
+        $dt->setTimeZone($tz);
+
+        return array(
+            self::LOCALTZ,
+            $dt
+        );
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Property/MultiDateTime.php b/dav/sabre-vobject/lib/Sabre/VObject/Property/MultiDateTime.php
new file mode 100644 (file)
index 0000000..629ef4a
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+use Sabre\VObject;
+
+/**
+ * Multi-DateTime property
+ *
+ * This element is used for iCalendar properties such as the EXDATE property.
+ * It basically provides a few helper functions that make it easier to deal
+ * with these. It supports both DATE-TIME and DATE values.
+ *
+ * In order to use this correctly, you must call setDateTimes and getDateTimes
+ * to retrieve and modify dates respectively.
+ *
+ * If you use the 'value' or properties directly, this object does not keep
+ * reference and results might appear incorrectly.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class MultiDateTime extends VObject\Property {
+
+    /**
+     * DateTime representation
+     *
+     * @var DateTime[]
+     */
+    protected $dateTimes;
+
+    /**
+     * dateType
+     *
+     * This is one of the Sabre\VObject\Property\DateTime constants.
+     *
+     * @var int
+     */
+    protected $dateType;
+
+    /**
+     * Updates the value
+     *
+     * @param array $dt Must be an array of DateTime objects.
+     * @param int $dateType
+     * @return void
+     */
+    public function setDateTimes(array $dt, $dateType = VObject\Property\DateTime::LOCALTZ) {
+
+        foreach($dt as $i)
+            if (!$i instanceof \DateTime)
+                throw new \InvalidArgumentException('You must pass an array of DateTime objects');
+
+        $this->offsetUnset('VALUE');
+        $this->offsetUnset('TZID');
+        switch($dateType) {
+
+            case DateTime::LOCAL :
+                $val = array();
+                foreach($dt as $i) {
+                    $val[] = $i->format('Ymd\\THis');
+                }
+                $this->setValue(implode(',',$val));
+                $this->offsetSet('VALUE','DATE-TIME');
+                break;
+            case DateTime::UTC :
+                $val = array();
+                foreach($dt as $i) {
+                    $i->setTimeZone(new \DateTimeZone('UTC'));
+                    $val[] = $i->format('Ymd\\THis\\Z');
+                }
+                $this->setValue(implode(',',$val));
+                $this->offsetSet('VALUE','DATE-TIME');
+                break;
+            case DateTime::LOCALTZ :
+                $val = array();
+                foreach($dt as $i) {
+                    $val[] = $i->format('Ymd\\THis');
+                }
+                $this->setValue(implode(',',$val));
+                $this->offsetSet('VALUE','DATE-TIME');
+                $this->offsetSet('TZID', $dt[0]->getTimeZone()->getName());
+                break;
+            case DateTime::DATE :
+                $val = array();
+                foreach($dt as $i) {
+                    $val[] = $i->format('Ymd');
+                }
+                $this->setValue(implode(',',$val));
+                $this->offsetSet('VALUE','DATE');
+                break;
+            default :
+                throw new \InvalidArgumentException('You must pass a valid dateType constant');
+
+        }
+        $this->dateTimes = $dt;
+        $this->dateType = $dateType;
+
+    }
+
+    /**
+     * Returns the current DateTime value.
+     *
+     * If no value was set, this method returns null.
+     *
+     * @return array|null
+     */
+    public function getDateTimes() {
+
+        if ($this->dateTimes)
+            return $this->dateTimes;
+
+        $dts = array();
+
+        if (!$this->value) {
+            $this->dateTimes = null;
+            $this->dateType = null;
+            return null;
+        }
+
+        foreach(explode(',',$this->value) as $val) {
+            list(
+                $type,
+                $dt
+            ) = DateTime::parseData($val, $this);
+            $dts[] = $dt;
+            $this->dateType = $type;
+        }
+        $this->dateTimes = $dts;
+        return $this->dateTimes;
+
+    }
+
+    /**
+     * Returns the type of Date format.
+     *
+     * This method returns one of the format constants. If no date was set,
+     * this method will return null.
+     *
+     * @return int|null
+     */
+    public function getDateType() {
+
+        if ($this->dateType)
+            return $this->dateType;
+
+        if (!$this->value) {
+            $this->dateTimes = null;
+            $this->dateType = null;
+            return null;
+        }
+
+        $dts = array();
+        foreach(explode(',',$this->value) as $val) {
+            list(
+                $type,
+                $dt
+            ) = DateTime::parseData($val, $this);
+            $dts[] = $dt;
+            $this->dateType = $type;
+        }
+        $this->dateTimes = $dts;
+        return $this->dateType;
+
+    }
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Reader.php b/dav/sabre-vobject/lib/Sabre/VObject/Reader.php
new file mode 100644 (file)
index 0000000..8fed7e2
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * VCALENDAR/VCARD reader
+ *
+ * This class reads the vobject file, and returns a full element tree.
+ *
+ * TODO: this class currently completely works 'statically'. This is pointless,
+ * and defeats OOP principals. Needs refactoring in a future version.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Reader {
+
+    /**
+     * Parses the file and returns the top component
+     *
+     * @param string $data
+     * @return Element
+     */
+    static function read($data) {
+
+        // Normalizing newlines
+        $data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
+
+        $lines = explode("\n", $data);
+
+        // Unfolding lines
+        $lines2 = array();
+        foreach($lines as $line) {
+
+            // Skipping empty lines
+            if (!$line) continue;
+
+            if ($line[0]===" " || $line[0]==="\t") {
+                $lines2[count($lines2)-1].=substr($line,1);
+            } else {
+                $lines2[] = $line;
+            }
+
+        }
+
+        unset($lines);
+
+        reset($lines2);
+
+        return self::readLine($lines2);
+
+    }
+
+    /**
+     * Reads and parses a single line.
+     *
+     * This method receives the full array of lines. The array pointer is used
+     * to traverse.
+     *
+     * @param array $lines
+     * @return Element
+     */
+    static private function readLine(&$lines) {
+
+        $line = current($lines);
+        $lineNr = key($lines);
+        next($lines);
+
+        // Components
+        if (stripos($line,"BEGIN:")===0) {
+
+            $componentName = strtoupper(substr($line,6));
+            $obj = Component::create($componentName);
+
+            $nextLine = current($lines);
+
+            while(stripos($nextLine,"END:")!==0) {
+
+                $obj->add(self::readLine($lines));
+
+                $nextLine = current($lines);
+
+                if ($nextLine===false)
+                    throw new ParseException('Invalid VObject. Document ended prematurely.');
+
+            }
+
+            // Checking component name of the 'END:' line.
+            if (substr($nextLine,4)!==$obj->name) {
+                throw new ParseException('Invalid VObject, expected: "END:' . $obj->name . '" got: "' . $nextLine . '"');
+            }
+            next($lines);
+
+            return $obj;
+
+        }
+
+        // Properties
+        //$result = preg_match('/(?P<name>[A-Z0-9-]+)(?:;(?P<parameters>^(?<!:):))(.*)$/',$line,$matches);
+
+
+        $token = '[A-Z0-9-\.]+';
+        $parameters = "(?:;(?P<parameters>([^:^\"]|\"([^\"]*)\")*))?";
+        $regex = "/^(?P<name>$token)$parameters:(?P<value>.*)$/i";
+
+        $result = preg_match($regex,$line,$matches);
+
+        if (!$result) {
+            throw new ParseException('Invalid VObject, line ' . ($lineNr+1) . ' did not follow the icalendar/vcard format');
+        }
+
+        $propertyName = strtoupper($matches['name']);
+        $propertyValue = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
+            if ($matches[2]==='n' || $matches[2]==='N') {
+                return "\n";
+            } else {
+                return $matches[2];
+            }
+        }, $matches['value']);
+
+        $obj = Property::create($propertyName, $propertyValue);
+
+        if ($matches['parameters']) {
+
+            foreach(self::readParameters($matches['parameters']) as $param) {
+                $obj->add($param);
+            }
+
+        }
+
+        return $obj;
+
+
+    }
+
+    /**
+     * Reads a parameter list from a property
+     *
+     * This method returns an array of Parameter
+     *
+     * @param string $parameters
+     * @return array
+     */
+    static private function readParameters($parameters) {
+
+        $token = '[A-Z0-9-]+';
+
+        $paramValue = '(?P<paramValue>[^\"^;]*|"[^"]*")';
+
+        $regex = "/(?<=^|;)(?P<paramName>$token)(=$paramValue(?=$|;))?/i";
+        preg_match_all($regex, $parameters, $matches,  PREG_SET_ORDER);
+
+        $params = array();
+        foreach($matches as $match) {
+
+            $value = isset($match['paramValue'])?$match['paramValue']:null;
+
+            if (isset($value[0])) {
+                // Stripping quotes, if needed
+                if ($value[0] === '"') $value = substr($value,1,strlen($value)-2);
+            } else {
+                $value = '';
+            }
+
+            $value = preg_replace_callback('#(\\\\(\\\\|N|n|;|,))#',function($matches) {
+                if ($matches[2]==='n' || $matches[2]==='N') {
+                    return "\n";
+                } else {
+                    return $matches[2];
+                }
+            }, $value);
+
+            $params[] = new Parameter($match['paramName'], $value);
+
+        }
+
+        return $params;
+
+    }
+
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/RecurrenceIterator.php b/dav/sabre-vobject/lib/Sabre/VObject/RecurrenceIterator.php
new file mode 100644 (file)
index 0000000..374b16e
--- /dev/null
@@ -0,0 +1,1058 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * This class is used to determine new for a recurring event, when the next
+ * events occur.
+ *
+ * This iterator may loop infinitely in the future, therefore it is important
+ * that if you use this class, you set hard limits for the amount of iterations
+ * you want to handle.
+ *
+ * Note that currently there is not full support for the entire iCalendar
+ * specification, as it's very complex and contains a lot of permutations
+ * that's not yet used very often in software.
+ *
+ * For the focus has been on features as they actually appear in Calendaring
+ * software, but this may well get expanded as needed / on demand
+ *
+ * The following RRULE properties are supported
+ *   * UNTIL
+ *   * INTERVAL
+ *   * COUNT
+ *   * FREQ=DAILY
+ *     * BYDAY
+ *   * FREQ=WEEKLY
+ *     * BYDAY
+ *     * WKST
+ *   * FREQ=MONTHLY
+ *     * BYMONTHDAY
+ *     * BYDAY
+ *     * BYSETPOS
+ *   * FREQ=YEARLY
+ *     * BYMONTH
+ *     * BYMONTHDAY (only if BYMONTH is also set)
+ *     * BYDAY (only if BYMONTH is also set)
+ *
+ * Anything beyond this is 'undefined', which means that it may get ignored, or
+ * you may get unexpected results. The effect is that in some applications the
+ * specified recurrence may look incorrect, or is missing.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class RecurrenceIterator implements \Iterator {
+
+    /**
+     * The initial event date
+     *
+     * @var DateTime
+     */
+    public $startDate;
+
+    /**
+     * The end-date of the initial event
+     *
+     * @var DateTime
+     */
+    public $endDate;
+
+    /**
+     * The 'current' recurrence.
+     *
+     * This will be increased for every iteration.
+     *
+     * @var DateTime
+     */
+    public $currentDate;
+
+
+    /**
+     * List of dates that are excluded from the rules.
+     *
+     * This list contains the items that have been overriden by the EXDATE
+     * property.
+     *
+     * @var array
+     */
+    public $exceptionDates = array();
+
+    /**
+     * Base event
+     *
+     * @var Component\VEvent
+     */
+    public $baseEvent;
+
+    /**
+     * List of dates that are overridden by other events.
+     * Similar to $overriddenEvents, but this just contains the original dates.
+     *
+     * @var array
+     */
+    public $overriddenDates = array();
+
+    /**
+     * list of events that are 'overridden'.
+     *
+     * This is an array of Component\VEvent objects.
+     *
+     * @var array
+     */
+    public $overriddenEvents = array();
+
+
+    /**
+     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
+     * yearly.
+     *
+     * @var string
+     */
+    public $frequency;
+
+    /**
+     * The last instance of this recurrence, inclusively
+     *
+     * @var DateTime|null
+     */
+    public $until;
+
+    /**
+     * The number of recurrences, or 'null' if infinitely recurring.
+     *
+     * @var int
+     */
+    public $count;
+
+    /**
+     * The interval.
+     *
+     * If for example frequency is set to daily, interval = 2 would mean every
+     * 2 days.
+     *
+     * @var int
+     */
+    public $interval = 1;
+
+    /**
+     * Which seconds to recur.
+     *
+     * This is an array of integers (between 0 and 60)
+     *
+     * @var array
+     */
+    public $bySecond;
+
+    /**
+     * Which minutes to recur
+     *
+     * This is an array of integers (between 0 and 59)
+     *
+     * @var array
+     */
+    public $byMinute;
+
+    /**
+     * Which hours to recur
+     *
+     * This is an array of integers (between 0 and 23)
+     *
+     * @var array
+     */
+    public $byHour;
+
+    /**
+     * Which weekdays to recur.
+     *
+     * This is an array of weekdays
+     *
+     * This may also be preceeded by a positive or negative integer. If present,
+     * this indicates the nth occurrence of a specific day within the monthly or
+     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
+     * the month, or year.
+     *
+     * @var array
+     */
+    public $byDay;
+
+    /**
+     * Which days of the month to recur
+     *
+     * This is an array of days of the months (1-31). The value can also be
+     * negative. -5 for instance means the 5th last day of the month.
+     *
+     * @var array
+     */
+    public $byMonthDay;
+
+    /**
+     * Which days of the year to recur.
+     *
+     * This is an array with days of the year (1 to 366). The values can also
+     * be negative. For instance, -1 will always represent the last day of the
+     * year. (December 31st).
+     *
+     * @var array
+     */
+    public $byYearDay;
+
+    /**
+     * Which week numbers to recur.
+     *
+     * This is an array of integers from 1 to 53. The values can also be
+     * negative. -1 will always refer to the last week of the year.
+     *
+     * @var array
+     */
+    public $byWeekNo;
+
+    /**
+     * Which months to recur
+     *
+     * This is an array of integers from 1 to 12.
+     *
+     * @var array
+     */
+    public $byMonth;
+
+    /**
+     * Which items in an existing st to recur.
+     *
+     * These numbers work together with an existing by* rule. It specifies
+     * exactly which items of the existing by-rule to filter.
+     *
+     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
+     * used to recur the last workday of the month.
+     *
+     * This would be done by setting frequency to 'monthly', byDay to
+     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
+     *
+     * @var array
+     */
+    public $bySetPos;
+
+    /**
+     * When a week starts
+     *
+     * @var string
+     */
+    public $weekStart = 'MO';
+
+    /**
+     * The current item in the list
+     *
+     * @var int
+     */
+    public $counter = 0;
+
+    /**
+     * Simple mapping from iCalendar day names to day numbers
+     *
+     * @var array
+     */
+    private $dayMap = array(
+        'SU' => 0,
+        'MO' => 1,
+        'TU' => 2,
+        'WE' => 3,
+        'TH' => 4,
+        'FR' => 5,
+        'SA' => 6,
+    );
+
+    /**
+     * Mappings between the day number and english day name.
+     *
+     * @var array
+     */
+    private $dayNames = array(
+        0 => 'Sunday',
+        1 => 'Monday',
+        2 => 'Tuesday',
+        3 => 'Wednesday',
+        4 => 'Thursday',
+        5 => 'Friday',
+        6 => 'Saturday',
+    );
+
+    /**
+     * If the current iteration of the event is an overriden event, this
+     * property will hold the VObject
+     *
+     * @var Component
+     */
+    private $currentOverriddenEvent;
+
+    /**
+     * This property may contain the date of the next not-overridden event.
+     * This date is calculated sometimes a bit early, before overridden events
+     * are evaluated.
+     *
+     * @var DateTime
+     */
+    private $nextDate;
+
+    /**
+     * Creates the iterator
+     *
+     * You should pass a VCALENDAR component, as well as the UID of the event
+     * we're going to traverse.
+     *
+     * @param Component $vcal
+     * @param string|null $uid
+     */
+    public function __construct(Component $vcal, $uid=null) {
+
+        if (is_null($uid)) {
+            if ($vcal->name === 'VCALENDAR') {
+                throw new \InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well');
+            }
+            $components = array($vcal);
+            $uid = (string)$vcal->uid;
+        } else {
+            $components = $vcal->select('VEVENT');
+        }
+        foreach($components as $component) {
+            if ((string)$component->uid == $uid) {
+                if (isset($component->{'RECURRENCE-ID'})) {
+                    $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component;
+                    $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime();
+                } else {
+                    $this->baseEvent = $component;
+                }
+            }
+        }
+        if (!$this->baseEvent) {
+            throw new \InvalidArgumentException('Could not find a base event with uid: ' . $uid);
+        }
+
+        $this->startDate = clone $this->baseEvent->DTSTART->getDateTime();
+
+        $this->endDate = null;
+        if (isset($this->baseEvent->DTEND)) {
+            $this->endDate = clone $this->baseEvent->DTEND->getDateTime();
+        } else {
+            $this->endDate = clone $this->startDate;
+            if (isset($this->baseEvent->DURATION)) {
+                $this->endDate->add(DateTimeParser::parse($this->baseEvent->DURATION->value));
+            } elseif ($this->baseEvent->DTSTART->getDateType()===Property\DateTime::DATE) {
+                $this->endDate->modify('+1 day');
+            }
+        }
+        $this->currentDate = clone $this->startDate;
+
+        $rrule = (string)$this->baseEvent->RRULE;
+
+        $parts = explode(';', $rrule);
+
+        // If no rrule was specified, we create a default setting
+        if (!$rrule) {
+            $this->frequency = 'daily';
+            $this->count = 1;
+        } else foreach($parts as $part) {
+
+            list($key, $value) = explode('=', $part, 2);
+
+            switch(strtoupper($key)) {
+
+                case 'FREQ' :
+                    if (!in_array(
+                        strtolower($value),
+                        array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
+                    )) {
+                        throw new \InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
+
+                    }
+                    $this->frequency = strtolower($value);
+                    break;
+
+                case 'UNTIL' :
+                    $this->until = DateTimeParser::parse($value);
+                    break;
+
+                case 'COUNT' :
+                    $this->count = (int)$value;
+                    break;
+
+                case 'INTERVAL' :
+                    $this->interval = (int)$value;
+                    break;
+
+                case 'BYSECOND' :
+                    $this->bySecond = explode(',', $value);
+                    break;
+
+                case 'BYMINUTE' :
+                    $this->byMinute = explode(',', $value);
+                    break;
+
+                case 'BYHOUR' :
+                    $this->byHour = explode(',', $value);
+                    break;
+
+                case 'BYDAY' :
+                    $this->byDay = explode(',', strtoupper($value));
+                    break;
+
+                case 'BYMONTHDAY' :
+                    $this->byMonthDay = explode(',', $value);
+                    break;
+
+                case 'BYYEARDAY' :
+                    $this->byYearDay = explode(',', $value);
+                    break;
+
+                case 'BYWEEKNO' :
+                    $this->byWeekNo = explode(',', $value);
+                    break;
+
+                case 'BYMONTH' :
+                    $this->byMonth = explode(',', $value);
+                    break;
+
+                case 'BYSETPOS' :
+                    $this->bySetPos = explode(',', $value);
+                    break;
+
+                case 'WKST' :
+                    $this->weekStart = strtoupper($value);
+                    break;
+
+            }
+
+        }
+
+        // Parsing exception dates
+        if (isset($this->baseEvent->EXDATE)) {
+            foreach($this->baseEvent->EXDATE as $exDate) {
+
+                foreach(explode(',', (string)$exDate) as $exceptionDate) {
+
+                    $this->exceptionDates[] =
+                        DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone());
+
+                }
+
+            }
+
+        }
+
+    }
+
+    /**
+     * Returns the current item in the list
+     *
+     * @return DateTime
+     */
+    public function current() {
+
+        if (!$this->valid()) return null;
+        return clone $this->currentDate;
+
+    }
+
+    /**
+     * This method returns the startdate for the current iteration of the
+     * event.
+     *
+     * @return DateTime
+     */
+    public function getDtStart() {
+
+        if (!$this->valid()) return null;
+        return clone $this->currentDate;
+
+    }
+
+    /**
+     * This method returns the enddate for the current iteration of the
+     * event.
+     *
+     * @return DateTime
+     */
+    public function getDtEnd() {
+
+        if (!$this->valid()) return null;
+        $dtEnd = clone $this->currentDate;
+        $dtEnd->add( $this->startDate->diff( $this->endDate ) );
+        return clone $dtEnd;
+
+    }
+
+    /**
+     * Returns a VEVENT object with the updated start and end date.
+     *
+     * Any recurrence information is removed, and this function may return an
+     * 'overridden' event instead.
+     *
+     * This method always returns a cloned instance.
+     *
+     * @return Component\VEvent
+     */
+    public function getEventObject() {
+
+        if ($this->currentOverriddenEvent) {
+            return clone $this->currentOverriddenEvent;
+        }
+        $event = clone $this->baseEvent;
+        unset($event->RRULE);
+        unset($event->EXDATE);
+        unset($event->RDATE);
+        unset($event->EXRULE);
+
+        $event->DTSTART->setDateTime($this->getDTStart(), $event->DTSTART->getDateType());
+        if (isset($event->DTEND)) {
+            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTSTART->getDateType());
+        }
+        if ($this->counter > 0) {
+            $event->{'RECURRENCE-ID'} = (string)$event->DTSTART;
+        }
+
+        return $event;
+
+    }
+
+    /**
+     * Returns the current item number
+     *
+     * @return int
+     */
+    public function key() {
+
+        return $this->counter;
+
+    }
+
+    /**
+     * Whether or not there is a 'next item'
+     *
+     * @return bool
+     */
+    public function valid() {
+
+        if (!is_null($this->count)) {
+            return $this->counter < $this->count;
+        }
+        if (!is_null($this->until)) {
+            return $this->currentDate <= $this->until;
+        }
+        return true;
+
+    }
+
+    /**
+     * Resets the iterator
+     *
+     * @return void
+     */
+    public function rewind() {
+
+        $this->currentDate = clone $this->startDate;
+        $this->counter = 0;
+
+    }
+
+    /**
+     * This method allows you to quickly go to the next occurrence after the
+     * specified date.
+     *
+     * Note that this checks the current 'endDate', not the 'stardDate'. This
+     * means that if you forward to January 1st, the iterator will stop at the
+     * first event that ends *after* January 1st.
+     *
+     * @param DateTime $dt
+     * @return void
+     */
+    public function fastForward(\DateTime $dt) {
+
+        while($this->valid() && $this->getDTEnd() <= $dt) {
+            $this->next();
+        }
+
+    }
+
+    /**
+     * Returns true if this recurring event never ends.
+     *
+     * @return bool
+     */
+    public function isInfinite() {
+
+        return !$this->count && !$this->until;
+
+    }
+
+    /**
+     * Goes on to the next iteration
+     *
+     * @return void
+     */
+    public function next() {
+
+        /*
+        if (!is_null($this->count) && $this->counter >= $this->count) {
+            $this->currentDate = null;
+        }*/
+
+
+        $previousStamp = $this->currentDate->getTimeStamp();
+
+        while(true) {
+
+            $this->currentOverriddenEvent = null;
+
+            // If we have a next date 'stored', we use that
+            if ($this->nextDate) {
+                $this->currentDate = $this->nextDate;
+                $currentStamp = $this->currentDate->getTimeStamp();
+                $this->nextDate = null;
+            } else {
+
+                // Otherwise, we calculate it
+                switch($this->frequency) {
+
+                    case 'daily' :
+                        $this->nextDaily();
+                        break;
+
+                    case 'weekly' :
+                        $this->nextWeekly();
+                        break;
+
+                    case 'monthly' :
+                        $this->nextMonthly();
+                        break;
+
+                    case 'yearly' :
+                        $this->nextYearly();
+                        break;
+
+                }
+                $currentStamp = $this->currentDate->getTimeStamp();
+
+                // Checking exception dates
+                foreach($this->exceptionDates as $exceptionDate) {
+                    if ($this->currentDate == $exceptionDate) {
+                        $this->counter++;
+                        continue 2;
+                    }
+                }
+                foreach($this->overriddenDates as $overriddenDate) {
+                    if ($this->currentDate == $overriddenDate) {
+                        continue 2;
+                    }
+                }
+
+            }
+
+            // Checking overridden events
+            foreach($this->overriddenEvents as $index=>$event) {
+                if ($index > $previousStamp && $index <= $currentStamp) {
+
+                    // We're moving the 'next date' aside, for later use.
+                    $this->nextDate = clone $this->currentDate;
+
+                    $this->currentDate = $event->DTSTART->getDateTime();
+                    $this->currentOverriddenEvent = $event;
+
+                    break;
+                }
+            }
+
+            break;
+
+        }
+
+        /*
+        if (!is_null($this->until)) {
+            if($this->currentDate > $this->until) {
+                $this->currentDate = null;
+            }
+        }*/
+
+        $this->counter++;
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for daily frequency.
+     *
+     * @return void
+     */
+    protected function nextDaily() {
+
+        if (!$this->byDay) {
+            $this->currentDate->modify('+' . $this->interval . ' days');
+            return;
+        }
+
+        $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)];
+
+        }
+
+        do {
+
+            $this->currentDate->modify('+' . $this->interval . ' days');
+
+            // Current day of the week
+            $currentDay = $this->currentDate->format('w');
+
+        } while (!in_array($currentDay, $recurrenceDays));
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for weekly frequency.
+     *
+     * @return void
+     */
+    protected function nextWeekly() {
+
+        if (!$this->byDay) {
+            $this->currentDate->modify('+' . $this->interval . ' weeks');
+            return;
+        }
+
+        $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)];
+
+        }
+
+        // Current day of the week
+        $currentDay = $this->currentDate->format('w');
+
+        // First day of the week:
+        $firstDay = $this->dayMap[$this->weekStart];
+
+        $time = array(
+            $this->currentDate->format('H'),
+            $this->currentDate->format('i'),
+            $this->currentDate->format('s')
+        );
+
+        // Increasing the 'current day' until we find our next
+        // occurrence.
+        while(true) {
+
+            $currentDay++;
+
+            if ($currentDay>6) {
+                $currentDay = 0;
+            }
+
+            // We need to roll over to the next week
+            if ($currentDay === $firstDay) {
+                $this->currentDate->modify('+' . $this->interval . ' 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]]);
+                    $this->currentDate->setTime($time[0],$time[1],$time[2]);
+                }
+            }
+
+            // We have a match
+            if (in_array($currentDay ,$recurrenceDays)) {
+                $this->currentDate->modify($this->dayNames[$currentDay]);
+                $this->currentDate->setTime($time[0],$time[1],$time[2]);
+                break;
+            }
+
+        }
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for monthly frequency.
+     *
+     * @return void
+     */
+    protected function nextMonthly() {
+
+        $currentDayOfMonth = $this->currentDate->format('j');
+        if (!$this->byMonthDay && !$this->byDay) {
+
+            // If the current day is higher than the 28th, rollover can
+            // occur to the next month. We Must skip these invalid
+            // entries.
+            if ($currentDayOfMonth < 29) {
+                $this->currentDate->modify('+' . $this->interval . ' months');
+            } else {
+                $increase = 0;
+                do {
+                    $increase++;
+                    $tempDate = clone $this->currentDate;
+                    $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
+                } while ($tempDate->format('j') != $currentDayOfMonth);
+                $this->currentDate = $tempDate;
+            }
+            return;
+        }
+
+        while(true) {
+
+            $occurrences = $this->getMonthlyOccurrences();
+
+            foreach($occurrences as $occurrence) {
+
+                // The first occurrence thats higher than the current
+                // day of the month wins.
+                if ($occurrence > $currentDayOfMonth) {
+                    break 2;
+                }
+
+            }
+
+            // If we made it all the way here, it means there were no
+            // valid occurrences, and we need to advance to the next
+            // month.
+            $this->currentDate->modify('first day of this month');
+            $this->currentDate->modify('+ ' . $this->interval . ' months');
+
+            // This goes to 0 because we need to start counting at hte
+            // beginning.
+            $currentDayOfMonth = 0;
+
+        }
+
+        $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
+
+    }
+
+    /**
+     * Does the processing for advancing the iterator for yearly frequency.
+     *
+     * @return void
+     */
+    protected function nextYearly() {
+
+        $currentMonth = $this->currentDate->format('n');
+        $currentYear = $this->currentDate->format('Y');
+        $currentDayOfMonth = $this->currentDate->format('j');
+
+        // No sub-rules, so we just advance by year
+        if (!$this->byMonth) {
+
+            // Unless it was a leap day!
+            if ($currentMonth==2 && $currentDayOfMonth==29) {
+
+                $counter = 0;
+                do {
+                    $counter++;
+                    // Here we increase the year count by the interval, until
+                    // we hit a date that's also in a leap year.
+                    //
+                    // We could just find the next interval that's dividable by
+                    // 4, but that would ignore the rule that there's no leap
+                    // year every year that's dividable by a 100, but not by
+                    // 400. (1800, 1900, 2100). So we just rely on the datetime
+                    // functions instead.
+                    $nextDate = clone $this->currentDate;
+                    $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
+                } while ($nextDate->format('n')!=2);
+                $this->currentDate = $nextDate;
+
+                return;
+
+            }
+
+            // The easiest form
+            $this->currentDate->modify('+' . $this->interval . ' years');
+            return;
+
+        }
+
+        $currentMonth = $this->currentDate->format('n');
+        $currentYear = $this->currentDate->format('Y');
+        $currentDayOfMonth = $this->currentDate->format('j');
+
+        $advancedToNewMonth = false;
+
+        // If we got a byDay or getMonthDay filter, we must first expand
+        // further.
+        if ($this->byDay || $this->byMonthDay) {
+
+            while(true) {
+
+                $occurrences = $this->getMonthlyOccurrences();
+
+                foreach($occurrences as $occurrence) {
+
+                    // The first occurrence that's higher than the current
+                    // day of the month wins.
+                    // If we advanced to the next month or year, the first
+                    // occurrence is always correct.
+                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
+                        break 2;
+                    }
+
+                }
+
+                // If we made it here, it means we need to advance to
+                // the next month or year.
+                $currentDayOfMonth = 1;
+                $advancedToNewMonth = true;
+                do {
+
+                    $currentMonth++;
+                    if ($currentMonth>12) {
+                        $currentYear+=$this->interval;
+                        $currentMonth = 1;
+                    }
+                } while (!in_array($currentMonth, $this->byMonth));
+
+                $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+            }
+
+            // If we made it here, it means we got a valid occurrence
+            $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
+            return;
+
+        } else {
+
+            // These are the 'byMonth' rules, if there are no byDay or
+            // byMonthDay sub-rules.
+            do {
+
+                $currentMonth++;
+                if ($currentMonth>12) {
+                    $currentYear+=$this->interval;
+                    $currentMonth = 1;
+                }
+            } while (!in_array($currentMonth, $this->byMonth));
+            $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+            return;
+
+        }
+
+    }
+
+    /**
+     * Returns all the occurrences for a monthly frequency with a 'byDay' or
+     * 'byMonthDay' expansion for the current month.
+     *
+     * The returned list is an array of integers with the day of month (1-31).
+     *
+     * @return array
+     */
+    protected function getMonthlyOccurrences() {
+
+        $startDate = clone $this->currentDate;
+
+        $byDayResults = array();
+
+        // Our strategy is to simply go through the byDays, advance the date to
+        // that point and add it to the results.
+        if ($this->byDay) foreach($this->byDay as $day) {
+
+            $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
+
+            // Dayname will be something like 'wednesday'. Now we need to find
+            // all wednesdays in this month.
+            $dayHits = array();
+
+            $checkDate = clone $startDate;
+            $checkDate->modify('first day of this month');
+            $checkDate->modify($dayName);
+
+            do {
+                $dayHits[] = $checkDate->format('j');
+                $checkDate->modify('next ' . $dayName);
+            } while ($checkDate->format('n') === $startDate->format('n'));
+
+            // So now we have 'all wednesdays' for month. It is however
+            // possible that the user only really wanted the 1st, 2nd or last
+            // wednesday.
+            if (strlen($day)>2) {
+                $offset = (int)substr($day,0,-2);
+
+                if ($offset>0) {
+                    // It is possible that the day does not exist, such as a
+                    // 5th or 6th wednesday of the month.
+                    if (isset($dayHits[$offset-1])) {
+                        $byDayResults[] = $dayHits[$offset-1];
+                    }
+                } else {
+
+                    // if it was negative we count from the end of the array
+                    $byDayResults[] = $dayHits[count($dayHits) + $offset];
+                }
+            } else {
+                // There was no counter (first, second, last wednesdays), so we
+                // just need to add the all to the list).
+                $byDayResults = array_merge($byDayResults, $dayHits);
+
+            }
+
+        }
+
+        $byMonthDayResults = array();
+        if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
+
+            // Removing values that are out of range for this month
+            if ($monthDay > $startDate->format('t') ||
+                $monthDay < 0-$startDate->format('t')) {
+                    continue;
+            }
+            if ($monthDay>0) {
+                $byMonthDayResults[] = $monthDay;
+            } else {
+                // Negative values
+                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
+            }
+        }
+
+        // If there was just byDay or just byMonthDay, they just specify our
+        // (almost) final list. If both were provided, then byDay limits the
+        // list.
+        if ($this->byMonthDay && $this->byDay) {
+            $result = array_intersect($byMonthDayResults, $byDayResults);
+        } elseif ($this->byMonthDay) {
+            $result = $byMonthDayResults;
+        } else {
+            $result = $byDayResults;
+        }
+        $result = array_unique($result);
+        sort($result, SORT_NUMERIC);
+
+        // The last thing that needs checking is the BYSETPOS. If it's set, it
+        // means only certain items in the set survive the filter.
+        if (!$this->bySetPos) {
+            return $result;
+        }
+
+        $filteredResult = array();
+        foreach($this->bySetPos as $setPos) {
+
+            if ($setPos<0) {
+                $setPos = count($result)-($setPos+1);
+            }
+            if (isset($result[$setPos-1])) {
+                $filteredResult[] = $result[$setPos-1];
+            }
+        }
+
+        sort($filteredResult, SORT_NUMERIC);
+        return $filteredResult;
+
+    }
+
+
+}
+
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/TimeZoneUtil.php b/dav/sabre-vobject/lib/Sabre/VObject/TimeZoneUtil.php
new file mode 100644 (file)
index 0000000..8e01210
--- /dev/null
@@ -0,0 +1,351 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * Time zone name translation
+ *
+ * This file translates well-known time zone names into "Olson database" time zone names.
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class TimeZoneUtil {
+
+    public static $map = array(
+
+        // from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
+        // snapshot taken on 2012/01/16
+
+        // windows
+        'AUS Central Standard Time'=>'Australia/Darwin',
+        'AUS Eastern Standard Time'=>'Australia/Sydney',
+        'Afghanistan Standard Time'=>'Asia/Kabul',
+        'Alaskan Standard Time'=>'America/Anchorage',
+        'Arab Standard Time'=>'Asia/Riyadh',
+        'Arabian Standard Time'=>'Asia/Dubai',
+        'Arabic Standard Time'=>'Asia/Baghdad',
+        'Argentina Standard Time'=>'America/Buenos_Aires',
+        'Armenian Standard Time'=>'Asia/Yerevan',
+        'Atlantic Standard Time'=>'America/Halifax',
+        'Azerbaijan Standard Time'=>'Asia/Baku',
+        'Azores Standard Time'=>'Atlantic/Azores',
+        'Bangladesh Standard Time'=>'Asia/Dhaka',
+        'Canada Central Standard Time'=>'America/Regina',
+        'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
+        'Caucasus Standard Time'=>'Asia/Yerevan',
+        'Cen. Australia Standard Time'=>'Australia/Adelaide',
+        'Central America Standard Time'=>'America/Guatemala',
+        'Central Asia Standard Time'=>'Asia/Almaty',
+        'Central Brazilian Standard Time'=>'America/Cuiaba',
+        'Central Europe Standard Time'=>'Europe/Budapest',
+        'Central European Standard Time'=>'Europe/Warsaw',
+        'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
+        'Central Standard Time'=>'America/Chicago',
+        'Central Standard Time (Mexico)'=>'America/Mexico_City',
+        'China Standard Time'=>'Asia/Shanghai',
+        'Dateline Standard Time'=>'Etc/GMT+12',
+        'E. Africa Standard Time'=>'Africa/Nairobi',
+        'E. Australia Standard Time'=>'Australia/Brisbane',
+        'E. Europe Standard Time'=>'Europe/Minsk',
+        'E. South America Standard Time'=>'America/Sao_Paulo',
+        'Eastern Standard Time'=>'America/New_York',
+        'Egypt Standard Time'=>'Africa/Cairo',
+        'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
+        'FLE Standard Time'=>'Europe/Kiev',
+        'Fiji Standard Time'=>'Pacific/Fiji',
+        'GMT Standard Time'=>'Europe/London',
+        'GTB Standard Time'=>'Europe/Istanbul',
+        'Georgian Standard Time'=>'Asia/Tbilisi',
+        'Greenland Standard Time'=>'America/Godthab',
+        'Greenwich Standard Time'=>'Atlantic/Reykjavik',
+        'Hawaiian Standard Time'=>'Pacific/Honolulu',
+        'India Standard Time'=>'Asia/Calcutta',
+        'Iran Standard Time'=>'Asia/Tehran',
+        'Israel Standard Time'=>'Asia/Jerusalem',
+        'Jordan Standard Time'=>'Asia/Amman',
+        'Kamchatka Standard Time'=>'Asia/Kamchatka',
+        'Korea Standard Time'=>'Asia/Seoul',
+        'Magadan Standard Time'=>'Asia/Magadan',
+        'Mauritius Standard Time'=>'Indian/Mauritius',
+        'Mexico Standard Time'=>'America/Mexico_City',
+        'Mexico Standard Time 2'=>'America/Chihuahua',
+        'Mid-Atlantic Standard Time'=>'Etc/GMT+2',
+        'Middle East Standard Time'=>'Asia/Beirut',
+        'Montevideo Standard Time'=>'America/Montevideo',
+        'Morocco Standard Time'=>'Africa/Casablanca',
+        'Mountain Standard Time'=>'America/Denver',
+        'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
+        'Myanmar Standard Time'=>'Asia/Rangoon',
+        'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
+        'Namibia Standard Time'=>'Africa/Windhoek',
+        'Nepal Standard Time'=>'Asia/Katmandu',
+        'New Zealand Standard Time'=>'Pacific/Auckland',
+        'Newfoundland Standard Time'=>'America/St_Johns',
+        'North Asia East Standard Time'=>'Asia/Irkutsk',
+        'North Asia Standard Time'=>'Asia/Krasnoyarsk',
+        'Pacific SA Standard Time'=>'America/Santiago',
+        'Pacific Standard Time'=>'America/Los_Angeles',
+        'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
+        'Pakistan Standard Time'=>'Asia/Karachi',
+        'Paraguay Standard Time'=>'America/Asuncion',
+        'Romance Standard Time'=>'Europe/Paris',
+        'Russian Standard Time'=>'Europe/Moscow',
+        'SA Eastern Standard Time'=>'America/Cayenne',
+        'SA Pacific Standard Time'=>'America/Bogota',
+        'SA Western Standard Time'=>'America/La_Paz',
+        'SE Asia Standard Time'=>'Asia/Bangkok',
+        'Samoa Standard Time'=>'Pacific/Apia',
+        'Singapore Standard Time'=>'Asia/Singapore',
+        'South Africa Standard Time'=>'Africa/Johannesburg',
+        'Sri Lanka Standard Time'=>'Asia/Colombo',
+        'Syria Standard Time'=>'Asia/Damascus',
+        'Taipei Standard Time'=>'Asia/Taipei',
+        'Tasmania Standard Time'=>'Australia/Hobart',
+        'Tokyo Standard Time'=>'Asia/Tokyo',
+        'Tonga Standard Time'=>'Pacific/Tongatapu',
+        'US Eastern Standard Time'=>'America/Indianapolis',
+        'US Mountain Standard Time'=>'America/Phoenix',
+        'UTC'=>'Etc/GMT',
+        'UTC+12'=>'Etc/GMT-12',
+        'UTC-02'=>'Etc/GMT+2',
+        'UTC-11'=>'Etc/GMT+11',
+        'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
+        'Venezuela Standard Time'=>'America/Caracas',
+        'Vladivostok Standard Time'=>'Asia/Vladivostok',
+        'W. Australia Standard Time'=>'Australia/Perth',
+        'W. Central Africa Standard Time'=>'Africa/Lagos',
+        'W. Europe Standard Time'=>'Europe/Berlin',
+        'West Asia Standard Time'=>'Asia/Tashkent',
+        'West Pacific Standard Time'=>'Pacific/Port_Moresby',
+        'Yakutsk Standard Time'=>'Asia/Yakutsk',
+
+        // Microsoft exchange timezones
+        // Source:
+        // http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
+        //
+        // Correct timezones deduced with help from:
+        // http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+        'Universal Coordinated Time' => 'UTC',
+        'Casablanca, Monrovia' => 'Africa/Casablanca',
+        'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
+        'Greenwich Mean Time; Dublin, Edinburgh, London' =>  'Europe/London',
+        'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
+        'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
+        'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
+        'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
+        'Prague, Central Europe' => 'Europe/Prague',
+        'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
+        'West Central Africa' => 'Africa/Luanda', // This was a best guess
+        'Athens, Istanbul, Minsk' => 'Europe/Athens',
+        'Bucharest' => 'Europe/Bucharest',
+        'Cairo' => 'Africa/Cairo',
+        'Harare, Pretoria' => 'Africa/Harare',
+        'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
+        'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
+        'Baghdad' => 'Asia/Baghdad',
+        'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
+        'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
+        'East Africa, Nairobi' => 'Africa/Nairobi',
+        'Tehran' => 'Asia/Tehran',
+        'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
+        'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
+        'Kabul' => 'Asia/Kabul',
+        'Ekaterinburg' => 'Asia/Yekaterinburg',
+        'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
+        'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
+        'Kathmandu, Nepal' => 'Asia/Kathmandu',
+        'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
+        'Astana, Dhaka' => 'Asia/Dhaka',
+        'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
+        'Rangoon' => 'Asia/Rangoon',
+        'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
+        'Krasnoyarsk' => 'Asia/Krasnoyarsk',
+        'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
+        'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
+        'Kuala Lumpur, Singapore' => 'Asia/Singapore',
+        'Perth, Western Australia' => 'Australia/Perth',
+        'Taipei' => 'Asia/Taipei',
+        'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
+        'Seoul, Korea Standard time' => 'Asia/Seoul',
+        'Yakutsk' => 'Asia/Yakutsk',
+        'Adelaide, Central Australia' => 'Australia/Adelaide',
+        'Darwin' => 'Australia/Darwin',
+        'Brisbane, East Australia' => 'Australia/Brisbane',
+        'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
+        'Guam, Port Moresby' => 'Pacific/Guam',
+        'Hobart, Tasmania' => 'Australia/Hobart',
+        'Vladivostok' => 'Asia/Vladivostok',
+        'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
+        'Auckland, Wellington' => 'Pacific/Auckland',
+        'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
+        'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
+        'Azores' => 'Atlantic/Azores',
+        'Cape Verde Is.' => 'Atlantic/Cape_Verde',
+        'Mid-Atlantic' => 'America/Noronha',
+        'Brasilia' => 'America/Sao_Paulo', // Best guess
+        'Buenos Aires' => 'America/Argentina/Buenos_Aires',
+        'Greenland' => 'America/Godthab',
+        'Newfoundland' => 'America/St_Johns',
+        'Atlantic Time (Canada)' => 'America/Halifax',
+        'Caracas, La Paz' => 'America/Caracas',
+        'Santiago' => 'America/Santiago',
+        'Bogota, Lima, Quito' => 'America/Bogota',
+        'Eastern Time (US & Canada)' => 'America/New_York',
+        'Indiana (East)' => 'America/Indiana/Indianapolis',
+        'Central America' => 'America/Guatemala',
+        'Central Time (US & Canada)' => 'America/Chicago',
+        'Mexico City, Tegucigalpa' => 'America/Mexico_City',
+        'Saskatchewan' => 'America/Edmonton',
+        'Arizona' => 'America/Phoenix',
+        'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
+        'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
+        'Alaska' => 'America/Anchorage',
+        'Hawaii' => 'Pacific/Honolulu',
+        'Midway Island, Samoa' => 'Pacific/Midway',
+        'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
+
+    );
+
+    public static $microsoftExchangeMap = array(
+        0  => 'UTC',
+        31 => 'Africa/Casablanca',
+        2  => 'Europe/Lisbon',
+        1  => 'Europe/London',
+        4  => 'Europe/Berlin',
+        6  => 'Europe/Prague',
+        3  => 'Europe/Paris',
+        69 => 'Africa/Luanda', // This was a best guess
+        7  => 'Europe/Athens',
+        5  => 'Europe/Bucharest',
+        49 => 'Africa/Cairo',
+        50 => 'Africa/Harare',
+        59 => 'Europe/Helsinki',
+        27 => 'Asia/Jerusalem',
+        26 => 'Asia/Baghdad',
+        74 => 'Asia/Kuwait',
+        51 => 'Europe/Moscow',
+        56 => 'Africa/Nairobi',
+        25 => 'Asia/Tehran',
+        24 => 'Asia/Muscat', // Best guess
+        54 => 'Asia/Baku',
+        48 => 'Asia/Kabul',
+        58 => 'Asia/Yekaterinburg',
+        47 => 'Asia/Karachi',
+        23 => 'Asia/Calcutta',
+        62 => 'Asia/Kathmandu',
+        46 => 'Asia/Almaty',
+        71 => 'Asia/Dhaka',
+        66 => 'Asia/Colombo',
+        61 => 'Asia/Rangoon',
+        22 => 'Asia/Bangkok',
+        64 => 'Asia/Krasnoyarsk',
+        45 => 'Asia/Shanghai',
+        63 => 'Asia/Irkutsk',
+        21 => 'Asia/Singapore',
+        73 => 'Australia/Perth',
+        75 => 'Asia/Taipei',
+        20 => 'Asia/Tokyo',
+        72 => 'Asia/Seoul',
+        70 => 'Asia/Yakutsk',
+        19 => 'Australia/Adelaide',
+        44 => 'Australia/Darwin',
+        18 => 'Australia/Brisbane',
+        76 => 'Australia/Sydney',
+        43 => 'Pacific/Guam',
+        42 => 'Australia/Hobart',
+        68 => 'Asia/Vladivostok',
+        41 => 'Asia/Magadan',
+        17 => 'Pacific/Auckland',
+        40 => 'Pacific/Fiji',
+        67 => 'Pacific/Tongatapu',
+        29 => 'Atlantic/Azores',
+        53 => 'Atlantic/Cape_Verde',
+        30 => 'America/Noronha',
+         8 => 'America/Sao_Paulo', // Best guess
+        32 => 'America/Argentina/Buenos_Aires',
+        69 => 'America/Godthab',
+        28 => 'America/St_Johns',
+         9 => 'America/Halifax',
+        33 => 'America/Caracas',
+        65 => 'America/Santiago',
+        35 => 'America/Bogota',
+        10 => 'America/New_York',
+        34 => 'America/Indiana/Indianapolis',
+        55 => 'America/Guatemala',
+        11 => 'America/Chicago',
+        37 => 'America/Mexico_City',
+        36 => 'America/Edmonton',
+        38 => 'America/Phoenix',
+        12 => 'America/Denver', // Best guess
+        13 => 'America/Los_Angeles', // Best guess
+        14 => 'America/Anchorage',
+        15 => 'Pacific/Honolulu',
+        16 => 'Pacific/Midway',
+        39 => 'Pacific/Kwajalein',
+    );
+
+    /**
+     * This method will try to find out the correct timezone for an iCalendar
+     * date-time value.
+     *
+     * You must pass the contents of the TZID parameter, as well as the full
+     * calendar.
+     *
+     * If the lookup fails, this method will return UTC.
+     *
+     * @param string $tzid
+     * @param Sabre\VObject\Component $vcalendar
+     * @return DateTimeZone
+     */
+    static public function getTimeZone($tzid, Component $vcalendar = null) {
+
+        // First we will just see if the tzid is a support timezone identifier.
+        try {
+            return new \DateTimeZone($tzid);
+        } catch (\Exception $e) {
+        }
+
+        // Next, we check if the tzid is somewhere in our tzid map.
+        if (isset(self::$map[$tzid])) {
+            return new \DateTimeZone(self::$map[$tzid]);
+        }
+
+        if ($vcalendar) {
+
+            // If that didn't work, we will scan VTIMEZONE objects
+            foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
+
+                if ((string)$vtimezone->TZID === $tzid) {
+
+                    // Some clients add 'X-LIC-LOCATION' with the olson name.
+                    if (isset($vtimezone->{'X-LIC-LOCATION'})) {
+                        try {
+                            return new \DateTimeZone($vtimezone->{'X-LIC-LOCATION'});
+                        } catch (\Exception $e) {
+                        }
+
+                    }
+                    // Microsoft may add a magic number, which we also have an
+                    // answer for.
+                    if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
+                        if (isset(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value])) {
+                            return new \DateTimeZone(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value]);
+                        }
+                    }
+                }
+
+            }
+
+        }
+
+        // If we got all the way here, we default to UTC.
+        return new \DateTimeZone(date_default_timezone_get());
+
+
+    }
+
+
+}
diff --git a/dav/sabre-vobject/lib/Sabre/VObject/Version.php b/dav/sabre-vobject/lib/Sabre/VObject/Version.php
new file mode 100644 (file)
index 0000000..a35c852
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+namespace Sabre\VObject;
+
+/**
+ * This class contains the version number for the VObject package
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Version {
+
+    /**
+     * Full version number
+     */
+    const VERSION = '2.0';
+
+    /**
+     * Stability : alpha, beta, stable
+     */
+    const STABILITY = 'stable';
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VAlarmTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VAlarmTest.php
new file mode 100644 (file)
index 0000000..f2cc340
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject\Component;
+use DateTime;
+
+class VAlarmTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider timeRangeTestData
+     */
+    public function testInTimeRange(VAlarm $valarm,$start,$end,$outcome) {
+
+        $this->assertEquals($outcome, $valarm->isInTimeRange($start, $end));
+
+    }
+
+    public function timeRangeTestData() {
+
+        $tests = array();
+
+        // Hard date and time        
+        $valarm1 = Component::create('VALARM');
+        $valarm1->TRIGGER = '20120312T130000Z';
+        $valarm1->TRIGGER['VALUE'] = 'DATE-TIME';
+
+        $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+        $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+        // Relation to start time of event
+        $valarm2 = Component::create('VALARM');
+        $valarm2->TRIGGER = '-P1D';
+        $valarm2->TRIGGER['VALUE'] = 'DURATION';
+
+        $vevent2 = Component::create('VEVENT');
+        $vevent2->DTSTART = '20120313T130000Z';
+        $vevent2->add($valarm2);
+
+        $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+        $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+        // Relation to end time of event
+        $valarm3 = Component::create('VALARM');
+        $valarm3->TRIGGER = '-P1D';
+        $valarm3->TRIGGER['VALUE'] = 'DURATION';
+        $valarm3->TRIGGER['RELATED']= 'END';
+
+        $vevent3 = Component::create('VEVENT');
+        $vevent3->DTSTART = '20120301T130000Z';
+        $vevent3->DTEND = '20120401T130000Z';
+        $vevent3->add($valarm3);
+
+        $tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+        $tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+        // Relation to end time of todo 
+        $valarm4 = Component::create('VALARM');
+        $valarm4->TRIGGER = '-P1D';
+        $valarm4->TRIGGER['VALUE'] = 'DURATION';
+        $valarm4->TRIGGER['RELATED']= 'END';
+
+        $vtodo4 = Component::create('VTODO');
+        $vtodo4->DTSTART = '20120301T130000Z';
+        $vtodo4->DUE = '20120401T130000Z';
+        $vtodo4->add($valarm4);
+
+        $tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+        $tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+        // Relation to start time of event + repeat
+        $valarm5 = Component::create('VALARM');
+        $valarm5->TRIGGER = '-P1D';
+        $valarm5->TRIGGER['VALUE'] = 'DURATION';
+        $valarm5->REPEAT = 10;
+        $valarm5->DURATION = 'P1D';
+
+        $vevent5 = Component::create('VEVENT');
+        $vevent5->DTSTART = '20120301T130000Z';
+        $vevent5->add($valarm5);
+
+        $tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true);
+
+        // Relation to start time of event + duration, but no repeat
+        $valarm6 = Component::create('VALARM');
+        $valarm6->TRIGGER = '-P1D';
+        $valarm6->TRIGGER['VALUE'] = 'DURATION';
+        $valarm6->DURATION = 'P1D';
+
+        $vevent6 = Component::create('VEVENT');
+        $vevent6->DTSTART = '20120313T130000Z';
+        $vevent6->add($valarm6);
+
+        $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+        $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+
+        // Relation to end time of event (DURATION instead of DTEND)
+        $valarm7 = Component::create('VALARM');
+        $valarm7->TRIGGER = '-P1D';
+        $valarm7->TRIGGER['VALUE'] = 'DURATION';
+        $valarm7->TRIGGER['RELATED']= 'END';
+
+        $vevent7 = Component::create('VEVENT');
+        $vevent7->DTSTART = '20120301T130000Z';
+        $vevent7->DURATION = 'P30D';
+        $vevent7->add($valarm7);
+
+        $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+        $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+        // Relation to end time of event (No DTEND or DURATION)
+        $valarm7 = Component::create('VALARM');
+        $valarm7->TRIGGER = '-P1D';
+        $valarm7->TRIGGER['VALUE'] = 'DURATION';
+        $valarm7->TRIGGER['RELATED']= 'END';
+
+        $vevent7 = Component::create('VEVENT');
+        $vevent7->DTSTART = '20120301T130000Z';
+        $vevent7->add($valarm7);
+
+        $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true);
+        $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false);
+
+
+        return $tests;
+    }
+
+    /**
+     * @expectedException LogicException
+     */
+    public function testInTimeRangeInvalidComponent() {
+
+        $valarm = Component::create('VALARM');
+        $valarm->TRIGGER = '-P1D';
+        $valarm->TRIGGER['RELATED'] = 'END';
+
+        $vjournal = Component::create('VJOURNAL');
+        $vjournal->add($valarm);
+
+        $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VCalendarTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VCalendarTest.php
new file mode 100644 (file)
index 0000000..1d7e0c6
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+class VCalendarTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider expandData
+     */
+    public function testExpand($input, $output) {
+
+        $vcal = VObject\Reader::read($input);
+        $vcal->expand(
+            new \DateTime('2011-12-01'),
+            new \DateTime('2011-12-31')
+        );
+
+        // This will normalize the output
+        $output = VObject\Reader::read($output)->serialize();
+
+        $this->assertEquals($output, $vcal->serialize());
+
+    }
+
+    public function expandData() {
+
+        $tests = array();
+
+        // No data
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+END:VCALENDAR
+';
+
+        $output = $input;
+        $tests[] = array($input,$output);
+
+
+        // Simple events
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla
+SUMMARY:InExpand
+DTSTART;VALUE=DATE:20111202
+END:VEVENT
+BEGIN:VEVENT
+UID:bla2
+SUMMARY:NotInExpand
+DTSTART;VALUE=DATE:20120101
+END:VEVENT
+END:VCALENDAR
+';
+
+        $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla
+SUMMARY:InExpand
+DTSTART;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+        $tests[] = array($input, $output);
+
+        // Removing timezone info
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Europe/Paris
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:bla4
+SUMMARY:RemoveTZ info
+DTSTART;TZID=Europe/Paris:20111203T130102
+END:VEVENT
+END:VCALENDAR
+';
+
+        $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla4
+SUMMARY:RemoveTZ info
+DTSTART;VALUE=DATE-TIME:20111203T120102Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $tests[] = array($input, $output);
+
+        // Recurrence rule
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111125T120000Z
+DTEND:20111125T130000Z
+RRULE:FREQ=WEEKLY
+END:VEVENT
+END:VCALENDAR
+';
+
+        $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART;VALUE=DATE-TIME:20111202T120000Z
+DTEND;VALUE=DATE-TIME:20111202T130000Z
+RECURRENCE-ID:20111202T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART;VALUE=DATE-TIME:20111209T120000Z
+DTEND;VALUE=DATE-TIME:20111209T130000Z
+RECURRENCE-ID:20111209T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART;VALUE=DATE-TIME:20111216T120000Z
+DTEND;VALUE=DATE-TIME:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART;VALUE=DATE-TIME:20111223T120000Z
+DTEND;VALUE=DATE-TIME:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART;VALUE=DATE-TIME:20111230T120000Z
+DTEND;VALUE=DATE-TIME:20111230T130000Z
+RECURRENCE-ID:20111230T120000Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        // Recurrence rule + override
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111125T120000Z
+DTEND:20111125T130000Z
+RRULE:FREQ=WEEKLY
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+RECURRENCE-ID:20111209T120000Z
+DTSTART:20111209T140000Z
+DTEND:20111209T150000Z
+SUMMARY:Override!
+END:VEVENT
+END:VCALENDAR
+';
+
+        $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART;VALUE=DATE-TIME:20111202T120000Z
+DTEND;VALUE=DATE-TIME:20111202T130000Z
+RECURRENCE-ID:20111202T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+RECURRENCE-ID:20111209T120000Z
+DTSTART:20111209T140000Z
+DTEND:20111209T150000Z
+SUMMARY:Override!
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART;VALUE=DATE-TIME:20111216T120000Z
+DTEND;VALUE=DATE-TIME:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART;VALUE=DATE-TIME:20111223T120000Z
+DTEND;VALUE=DATE-TIME:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART;VALUE=DATE-TIME:20111230T120000Z
+DTEND;VALUE=DATE-TIME:20111230T130000Z
+RECURRENCE-ID:20111230T120000Z
+END:VEVENT
+END:VCALENDAR
+';
+
+        $tests[] = array($input, $output);
+        return $tests;
+
+    }
+
+    /**
+     * @expectedException LogicException
+     */
+    public function testBrokenEventExpand() {
+
+        $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+RRULE:FREQ=WEEKLY
+DTSTART;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+        $vcal = VObject\Reader::read($input);
+        $vcal->expand(
+            new \DateTime('2011-12-01'),
+            new \DateTime('2011-12-31')
+        );
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VCardTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VCardTest.php
new file mode 100644 (file)
index 0000000..584a007
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+class VCardTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider validateData
+     */
+    function testValidate($input, $expectedWarnings, $expectedRepairedOutput) {
+
+        $vcard = VObject\Reader::read($input);
+
+        $warnings = $vcard->validate();
+
+        $warnMsg = array();
+        foreach($warnings as $warning) {
+            $warnMsg[] = $warning['message'];
+        }
+
+        $this->assertEquals($expectedWarnings, $warnMsg);
+
+        $vcard->validate(VObject\Component::REPAIR);
+
+        $this->assertEquals(
+            $expectedRepairedOutput,
+            $vcard->serialize()
+        );
+
+    }
+
+    public function validateData() {
+
+        $tests = array();
+
+        // Correct
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
+            array(),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
+        );
+
+        // No VERSION
+        $tests[] = array(
+            "BEGIN:VCARD\r\nFN:John Doe\r\nEND:VCARD\r\n",
+            array(
+                'The VERSION property must appear in the VCARD component exactly 1 time',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
+        );
+
+        // Unknown version
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nEND:VCARD\r\n",
+            array(
+                'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nEND:VCARD\r\n",
+        );
+
+        // No FN
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD\r\n",
+            array(
+                'The FN property must appear in the VCARD component exactly 1 time',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD\r\n",
+        );
+        // No FN, N fallback
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n",
+            array(
+                'The FN property must appear in the VCARD component exactly 1 time',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n",
+        );
+        // No FN, N fallback, no first name
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n",
+            array(
+                'The FN property must appear in the VCARD component exactly 1 time',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n",
+        );
+
+        // No FN, ORG fallback
+        $tests[] = array(
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nORG:Acme Co.\r\nEND:VCARD\r\n",
+            array(
+                'The FN property must appear in the VCARD component exactly 1 time',
+            ),
+            "BEGIN:VCARD\r\nVERSION:4.0\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n",
+        );
+        return $tests;
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VEventTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VEventTest.php
new file mode 100644 (file)
index 0000000..616da4a
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject;
+
+class VEventTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider timeRangeTestData
+     */
+    public function testInTimeRange(VEvent $vevent,$start,$end,$outcome) {
+
+        $this->assertEquals($outcome, $vevent->isInTimeRange($start, $end));
+
+    }
+
+    public function timeRangeTestData() {
+
+        $tests = array();
+
+        $vevent = new VEvent('VEVENT');
+        $vevent->DTSTART = '20111223T120000Z';
+        $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vevent2 = clone $vevent;
+        $vevent2->DTEND = '20111225T120000Z';
+        $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vevent3 = clone $vevent;
+        $vevent3->DURATION = 'P1D';
+        $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vevent4 = clone $vevent;
+        $vevent4->DTSTART = '20111225';
+        $vevent4->DTSTART['VALUE'] = 'DATE';
+        $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+        // Event with no end date should be treated as lasting the entire day.
+        $tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true);
+
+
+        $vevent5 = clone $vevent;
+        $vevent5->DURATION = 'P1D';
+        $vevent5->RRULE = 'FREQ=YEARLY';
+        $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+        $tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true);
+
+        $vevent6 = clone $vevent;
+        $vevent6->DTSTART = '20111225';
+        $vevent6->DTSTART['VALUE'] = 'DATE';
+        $vevent6->DTEND   = '20111225';
+        $vevent6->DTEND['VALUE'] = 'DATE';
+
+        $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        // Added this test to ensure that recurrence rules with no DTEND also 
+        // get checked for the entire day.
+        $vevent7 = clone $vevent;
+        $vevent7->DTSTART = '20120101';
+        $vevent7->DTSTART['VALUE'] = 'DATE';
+        $vevent7->RRULE = 'FREQ=MONTHLY';
+        $tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true);
+        return $tests;
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VJournalTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VJournalTest.php
new file mode 100644 (file)
index 0000000..46ecb99
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject\Component;
+
+class VJournalTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider timeRangeTestData
+     */
+    public function testInTimeRange(VJournal $vtodo,$start,$end,$outcome) {
+
+        $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
+
+    }
+
+    public function timeRangeTestData() {
+
+        $tests = array();
+
+        $vjournal = Component::create('VJOURNAL');
+        $vjournal->DTSTART = '20111223T120000Z';
+        $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vjournal2 = Component::create('VJOURNAL');
+        $vjournal2->DTSTART = '20111223';
+        $vjournal2->DTSTART['VALUE'] = 'DATE';
+        $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vjournal3 = Component::create('VJOURNAL');
+        $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false);
+        $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        return $tests;
+    }
+
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Component/VTodoTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Component/VTodoTest.php
new file mode 100644 (file)
index 0000000..a84da5c
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use Sabre\VObject\Component;
+
+class VTodoTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider timeRangeTestData
+     */
+    public function testInTimeRange(VTodo $vtodo,$start,$end,$outcome) {
+
+        $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
+
+    }
+
+    public function timeRangeTestData() {
+
+        $tests = array();
+
+        $vtodo = Component::create('VTODO');
+        $vtodo->DTSTART = '20111223T120000Z';
+        $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo2 = clone $vtodo;
+        $vtodo2->DURATION = 'P1D';
+        $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo3 = clone $vtodo;
+        $vtodo3->DUE = '20111225';
+        $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo4 = Component::create('VTODO');
+        $vtodo4->DUE = '20111225';
+        $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo5 = Component::create('VTODO');
+        $vtodo5->COMPLETED = '20111225';
+        $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo6 = Component::create('VTODO');
+        $vtodo6->CREATED = '20111225';
+        $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo7 = Component::create('VTODO');
+        $vtodo7->CREATED = '20111225';
+        $vtodo7->COMPLETED = '20111226';
+        $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+        $vtodo7 = Component::create('VTODO');
+        $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+        $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true);
+
+        return $tests;
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/ComponentTest.php b/dav/sabre-vobject/tests/Sabre/VObject/ComponentTest.php
new file mode 100644 (file)
index 0000000..07000bd
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+
+namespace Sabre\VObject;
+
+class ComponentTest extends \PHPUnit_Framework_TestCase {
+
+    function testIterate() {
+
+        $comp = new Component('VCALENDAR');
+
+        $sub = new Component('VEVENT');
+        $comp->children[] = $sub;
+
+        $sub = new Component('VTODO');
+        $comp->children[] = $sub;
+
+        $count = 0;
+        foreach($comp->children() as $key=>$subcomponent) {
+
+           $count++;
+           $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
+
+        }
+        $this->assertEquals(2,$count);
+        $this->assertEquals(1,$key);
+
+    }
+
+    function testMagicGet() {
+
+        $comp = new Component('VCALENDAR');
+
+        $sub = new Component('VEVENT');
+        $comp->children[] = $sub;
+
+        $sub = new Component('VTODO');
+        $comp->children[] = $sub;
+
+        $event = $comp->vevent;
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $event);
+        $this->assertEquals('VEVENT', $event->name);
+
+        $this->assertInternalType('null', $comp->vjournal);
+
+    }
+
+    function testMagicGetGroups() {
+
+        $comp = new Component('VCARD');
+
+        $sub = new Property('GROUP1.EMAIL','1@1.com');
+        $comp->children[] = $sub;
+
+        $sub = new Property('GROUP2.EMAIL','2@2.com');
+        $comp->children[] = $sub;
+
+        $sub = new Property('EMAIL','3@3.com');
+        $comp->children[] = $sub;
+
+        $emails = $comp->email;
+        $this->assertEquals(3, count($emails));
+
+        $email1 = $comp->{"group1.email"};
+        $this->assertEquals('EMAIL', $email1[0]->name);
+        $this->assertEquals('GROUP1', $email1[0]->group);
+
+        $email3 = $comp->{".email"};
+        $this->assertEquals('EMAIL', $email3[0]->name);
+        $this->assertEquals(null, $email3[0]->group);
+
+    }
+
+    function testMagicIsset() {
+
+        $comp = new Component('VCALENDAR');
+
+        $sub = new Component('VEVENT');
+        $comp->children[] = $sub;
+
+        $sub = new Component('VTODO');
+        $comp->children[] = $sub;
+
+        $this->assertTrue(isset($comp->vevent));
+        $this->assertTrue(isset($comp->vtodo));
+        $this->assertFalse(isset($comp->vjournal));
+
+    }
+
+    function testMagicSetScalar() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->myProp = 'myValue';
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+        $this->assertEquals('myValue',$comp->MYPROP->value);
+
+
+    }
+
+    function testMagicSetScalarTwice() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->myProp = 'myValue';
+        $comp->myProp = 'myValue';
+
+        $this->assertEquals(1,count($comp->children));
+        $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+        $this->assertEquals('myValue',$comp->MYPROP->value);
+
+    }
+
+    function testMagicSetComponent() {
+
+        $comp = new Component('VCALENDAR');
+
+        // Note that 'myProp' is ignored here.
+        $comp->myProp = new Component('VEVENT');
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testMagicSetTwice() {
+
+        $comp = new Component('VCALENDAR');
+
+        $comp->VEVENT = new Component('VEVENT');
+        $comp->VEVENT = new Component('VEVENT');
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testArrayAccessGet() {
+
+        $comp = new Component('VCALENDAR');
+
+        $event = new Component('VEVENT');
+        $event->summary = 'Event 1';
+
+        $comp->add($event);
+
+        $event2 = clone $event;
+        $event2->summary = 'Event 2';
+
+        $comp->add($event2);
+
+        $this->assertEquals(2,count($comp->children()));
+        $this->assertTrue($comp->vevent[1] instanceof Component);
+        $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary);
+
+    }
+
+    function testArrayAccessExists() {
+
+        $comp = new Component('VCALENDAR');
+
+        $event = new Component('VEVENT');
+        $event->summary = 'Event 1';
+
+        $comp->add($event);
+
+        $event2 = clone $event;
+        $event2->summary = 'Event 2';
+
+        $comp->add($event2);
+
+        $this->assertTrue(isset($comp->vevent[0]));
+        $this->assertTrue(isset($comp->vevent[1]));
+
+    }
+
+    /**
+     * @expectedException LogicException
+     */
+    function testArrayAccessSet() {
+
+        $comp = new Component('VCALENDAR');
+        $comp['hey'] = 'hi there';
+
+    }
+    /**
+     * @expectedException LogicException
+     */
+    function testArrayAccessUnset() {
+
+        $comp = new Component('VCALENDAR');
+        unset($comp[0]);
+
+    }
+
+    function testAddScalar() {
+
+        $comp = new Component('VCALENDAR');
+
+        $comp->add('myprop','value');
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertTrue($comp->children[0] instanceof Property);
+        $this->assertEquals('MYPROP',$comp->children[0]->name);
+        $this->assertEquals('value',$comp->children[0]->value);
+
+    }
+
+    function testAddScalarParams() {
+
+        $comp = Component::create('VCALENDAR');
+
+        $comp->add('myprop','value',array('param1'=>'value1'));
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertTrue($comp->children[0] instanceof Property);
+        $this->assertEquals('MYPROP',$comp->children[0]->name);
+        $this->assertEquals('value',$comp->children[0]->value);
+
+        $this->assertEquals(1, count($comp->children[0]->parameters));
+
+        $this->assertTrue($comp->children[0]->parameters[0] instanceof Parameter);
+        $this->assertEquals('PARAM1',$comp->children[0]->parameters[0]->name);
+        $this->assertEquals('value1',$comp->children[0]->parameters[0]->value);
+
+    }
+
+
+    function testAddComponent() {
+
+        $comp = new Component('VCALENDAR');
+
+        $comp->add(new Component('VEVENT'));
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testAddComponentTwice() {
+
+        $comp = new Component('VCALENDAR');
+
+        $comp->add(new Component('VEVENT'));
+        $comp->add(new Component('VEVENT'));
+
+        $this->assertEquals(2, count($comp->children));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->add(new Component('VEVENT'),'hello');
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail2() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->add(array());
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail3() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->add('hello',array());
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testMagicSetInvalid() {
+
+        $comp = new Component('VCALENDAR');
+
+        // Note that 'myProp' is ignored here.
+        $comp->myProp = new \StdClass();
+
+        $this->assertEquals(1, count($comp->children));
+
+        $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+    }
+
+    function testMagicUnset() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->add(new Component('VEVENT'));
+
+        unset($comp->vevent);
+
+        $this->assertEquals(array(), $comp->children);
+
+    }
+
+
+    function testCount() {
+
+        $comp = new Component('VCALENDAR');
+        $this->assertEquals(1,$comp->count());
+
+    }
+
+    function testChildren() {
+
+        $comp = new Component('VCALENDAR');
+
+        // Note that 'myProp' is ignored here.
+        $comp->children = array(
+            new Component('VEVENT'),
+            new Component('VTODO')
+        );
+
+        $r = $comp->children();
+        $this->assertTrue($r instanceof ElementList);
+        $this->assertEquals(2,count($r));
+    }
+
+    function testGetComponents() {
+
+        $comp = new Component('VCALENDAR');
+
+        // Note that 'myProp' is ignored here.
+        $comp->children = array(
+            new Property('FOO','BAR'),
+            new Component('VTODO')
+        );
+
+        $r = $comp->getComponents();
+        $this->assertInternalType('array', $r);
+        $this->assertEquals(1, count($r));
+        $this->assertEquals('VTODO', $r[0]->name);
+    }
+
+    function testSerialize() {
+
+        $comp = new Component('VCALENDAR');
+        $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
+
+    }
+
+    function testSerializeChildren() {
+
+        $comp = new Component('VCALENDAR');
+        $comp->children = array(
+            new Component('VEVENT'),
+            new Component('VTODO')
+        );
+
+        $str = $comp->serialize();
+
+        $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str);
+
+    }
+
+    function testSerializeOrderCompAndProp() {
+        
+        $comp = new Component('VCALENDAR');
+        $comp->add(new Component('VEVENT'));
+        $comp->add('PROP1','BLABLA');
+        $comp->add('VERSION','2.0');
+        $comp->add(new Component('VTIMEZONE'));
+
+        $str = $comp->serialize();
+
+        $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str);
+
+    }
+
+    function testAnotherSerializeOrderProp() {
+
+        $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
+
+        $comp = new Component('VCARD');
+        $comp->__set('SOMEPROP','FOO');
+        $comp->__set('ANOTHERPROP','FOO');
+        $comp->__set('THIRDPROP','FOO');
+        foreach ($prop4s as $prop4) {
+            $comp->add('PROP4', 'FOO '.$prop4);
+        }
+        $comp->__set('PROPNUMBERFIVE', 'FOO');
+        $comp->__set('PROPNUMBERSIX', 'FOO');
+        $comp->__set('PROPNUMBERSEVEN', 'FOO');
+        $comp->__set('PROPNUMBEREIGHT', 'FOO');
+        $comp->__set('PROPNUMBERNINE', 'FOO');
+        $comp->__set('PROPNUMBERTEN', 'FOO');
+        $comp->__set('VERSION','2.0');
+        $comp->__set('UID', 'FOO');
+
+        $str = $comp->serialize();
+
+        $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/DateTimeParserTest.php b/dav/sabre-vobject/tests/Sabre/VObject/DateTimeParserTest.php
new file mode 100644 (file)
index 0000000..463e7bc
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+namespace Sabre\VObject;
+
+use DateTime;
+use DateTimeZone;
+use DateInterval;
+
+class DateTimeParserTest extends \PHPUnit_Framework_TestCase {
+
+    function testParseICalendarDuration() {
+
+        $this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true));
+        $this->assertEquals('+5 days',  DateTimeParser::parseDuration('P5D',true));
+        $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true));
+        $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true));
+        $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true));
+        $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S'));
+
+    }
+
+    function testParseICalendarDurationDateInterval() {
+
+        $expected = new DateInterval('P7D');
+        $this->assertEquals($expected, DateTimeParser::parseDuration('P1W'));
+        $this->assertEquals($expected, DateTimeParser::parse('P1W'));
+
+        $expected = new DateInterval('PT3M');
+        $expected->invert = true;
+        $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M'));
+
+    }
+
+    /**
+     * @expectedException LogicException
+     */
+    function testParseICalendarDurationFail() {
+
+        DateTimeParser::parseDuration('P1X',true);
+
+    }
+
+    function testParseICalendarDateTime() {
+
+        $dateTime = DateTimeParser::parseDateTime('20100316T141405');
+
+        $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
+
+        $this->assertEquals($compare, $dateTime);
+
+    }
+
+    /**
+     * @depends testParseICalendarDateTime
+     * @expectedException LogicException
+     */
+    function testParseICalendarDateTimeBadFormat() {
+
+        $dateTime = DateTimeParser::parseDateTime('20100316T141405 ');
+
+    }
+
+    /**
+     * @depends testParseICalendarDateTime
+     */
+    function testParseICalendarDateTimeUTC() {
+
+        $dateTime = DateTimeParser::parseDateTime('20100316T141405Z');
+
+        $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
+        $this->assertEquals($compare, $dateTime);
+
+    }
+
+    /**
+     * @depends testParseICalendarDateTime
+     */
+    function testParseICalendarDateTimeUTC2() {
+
+        $dateTime = DateTimeParser::parseDateTime('20101211T160000Z');
+
+        $compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC'));
+        $this->assertEquals($compare, $dateTime);
+
+    }
+
+    /**
+     * @depends testParseICalendarDateTime
+     */
+    function testParseICalendarDateTimeCustomTimeZone() {
+
+        $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
+
+        $compare = new DateTime('2010-03-16 13:14:05',new DateTimeZone('UTC'));
+        $this->assertEquals($compare, $dateTime);
+
+    }
+
+    function testParseICalendarDate() {
+
+        $dateTime = DateTimeParser::parseDate('20100316');
+
+        $expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC'));
+
+        $this->assertEquals($expected, $dateTime);
+
+        $dateTime = DateTimeParser::parse('20100316');
+        $this->assertEquals($expected, $dateTime);
+
+    }
+
+    /**
+     * @depends testParseICalendarDate
+     * @expectedException LogicException
+     */
+    function testParseICalendarDateBadFormat() {
+
+        $dateTime = DateTimeParser::parseDate('20100316T141405');
+
+    }
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/ElementListTest.php b/dav/sabre-vobject/tests/Sabre/VObject/ElementListTest.php
new file mode 100644 (file)
index 0000000..84e1bcb
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace Sabre\VObject;
+
+class ElementListTest extends \PHPUnit_Framework_TestCase {
+
+    function testIterate() {
+
+        $sub = new Component('VEVENT');
+
+        $elems = array(
+            $sub,
+            clone $sub,
+            clone $sub
+        );
+
+        $elemList = new ElementList($elems);
+
+        $count = 0;
+        foreach($elemList as $key=>$subcomponent) {
+
+           $count++;
+           $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
+
+        }
+        $this->assertEquals(3,$count);
+        $this->assertEquals(2,$key);
+
+    }
+
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/EmClientTest.php b/dav/sabre-vobject/tests/Sabre/VObject/EmClientTest.php
new file mode 100644 (file)
index 0000000..69d410f
--- /dev/null
@@ -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);
+
+    }
+
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/FreeBusyGeneratorTest.php b/dav/sabre-vobject/tests/Sabre/VObject/FreeBusyGeneratorTest.php
new file mode 100644 (file)
index 0000000..1f79e0a
--- /dev/null
@@ -0,0 +1,246 @@
+<?php
+
+namespace Sabre\VObject;
+
+class FreeBusyGeneratorTest extends \PHPUnit_Framework_TestCase {
+
+    function getInput() {
+
+        // shows up
+$blob1 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T130000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // opaque, shows up
+$blob2 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+TRANSP:OPAQUE
+DTSTART:20110101T130000Z
+DTEND:20110101T140000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // transparent, hidden
+$blob3 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+TRANSP:TRANSPARENT
+DTSTART:20110101T140000Z
+DTEND:20110101T150000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // cancelled, hidden
+$blob4 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+STATUS:CANCELLED
+DTSTART:20110101T160000Z
+DTEND:20110101T170000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // tentative, shows up
+$blob5 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+STATUS:TENTATIVE
+DTSTART:20110101T180000Z
+DTEND:20110101T190000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // outside of time-range, hidden
+$blob6 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110101T090000Z
+DTEND:20110101T100000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // outside of time-range, hidden
+$blob7 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110104T090000Z
+DTEND:20110104T100000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // using duration, shows up
+$blob8 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110101T190000Z
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+    // Day-long event, shows up
+$blob9 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;TYPE=DATE:20110102
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+// No duration, does not show up
+$blob10 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110101T200000Z
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+// encoded as object, shows up
+$blob11 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20110101T210000Z
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+// Freebusy. Some parts show up
+$blob12 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VFREEBUSY
+FREEBUSY:20110103T010000Z/20110103T020000Z
+FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z
+FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z
+FREEBUSY:20120101T000000Z/20120101T010000Z
+FREEBUSY:20110103T050000Z/PT1H
+END:VFREEBUSY
+END:VCALENDAR
+ICS;
+
+// Yearly recurrence rule, shows up
+$blob13 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20100101T220000Z
+DTEND:20100101T230000Z
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+// Yearly recurrence rule + duration, shows up
+$blob14 = <<<ICS
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART:20100101T230000Z
+DURATION:PT1H
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+
+        return array(
+            $blob1,
+            $blob2,
+            $blob3,
+            $blob4,
+            $blob5,
+            $blob6,
+            $blob7,
+            $blob8,
+            $blob9,
+            $blob10,
+            Reader::read($blob11),
+            $blob12,
+            $blob13,
+            $blob14,
+        );
+
+    }
+
+    function testGenerator() {
+
+        $gen = new FreeBusyGenerator(
+            new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')),
+            new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')),
+            $this->getInput()
+        );
+
+        $result = $gen->getResult();
+
+        $expected = array(
+            '20110101T120000Z/20110101T130000Z',
+            '20110101T130000Z/20110101T140000Z',
+            '20110101T180000Z/20110101T190000Z',
+            '20110101T190000Z/20110101T200000Z',
+            '20110102T000000Z/20110103T000000Z',
+            '20110101T210000Z/20110101T220000Z',
+
+            '20110103T010000Z/20110103T020000Z',
+            '20110103T030000Z/20110103T040000Z',
+            '20110103T040000Z/20110103T050000Z',
+            '20110103T050000Z/20110103T060000Z',
+
+            '20110101T220000Z/20110101T230000Z',
+            '20110101T230000Z/20110102T000000Z',
+        );
+
+        foreach($result->VFREEBUSY->FREEBUSY as $fb) {
+
+            $this->assertContains((string)$fb, $expected);
+
+            $k = array_search((string)$fb, $expected);
+            unset($expected[$k]);
+
+        }
+        if (count($expected)>0) {
+            $this->fail('There were elements in the expected array that were not found in the output: ' . "\n"  . print_r($expected,true) . "\n" . $result->serialize());
+
+        }
+
+    }
+
+    function testGeneratorBaseObject() {
+
+        $obj = new Component('VCALENDAR');
+        $obj->METHOD = 'PUBLISH';
+
+        $gen = new FreeBusyGenerator();
+        $gen->setObjects(array());
+        $gen->setBaseObject($obj);
+
+        $result = $gen->getResult();
+
+        $this->assertEquals('PUBLISH', $result->METHOD->value);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testInvalidArg() {
+
+        $gen = new FreeBusyGenerator(
+            new \DateTime('2012-01-01'),
+            new \DateTime('2012-12-31'),
+            new \StdClass()
+        );
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Issue153Test.php b/dav/sabre-vobject/tests/Sabre/VObject/Issue153Test.php
new file mode 100644 (file)
index 0000000..1cc14c1
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Sabre\VObject;
+
+class Issue153Test extends \PHPUnit_Framework_TestCase {
+
+    function testRead() {
+
+        $obj = Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf'));
+        $this->assertEquals('Test Benutzer', (string)$obj->fn);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Issue154Test.php b/dav/sabre-vobject/tests/Sabre/VObject/Issue154Test.php
new file mode 100644 (file)
index 0000000..ed9c7c3
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Sabre\VObject;
+
+class Issue154Test extends \PHPUnit_Framework_TestCase {
+
+    function testStuff() {
+
+        $vcard = new Component('VCARD');
+        $vcard->VERSION = '3.0';
+        $vcard->PHOTO = base64_encode('random_stuff');
+        $vcard->PHOTO->add('BASE64',null);
+        $vcard->UID = 'foo-bar';
+
+        $result = $vcard->serialize();
+        $expected = array(
+            "BEGIN:VCARD",
+            "VERSION:3.0",
+            "PHOTO;BASE64:" . base64_encode('random_stuff'),
+            "UID:foo-bar",
+            "END:VCARD",
+            "",
+        );
+
+        $this->assertEquals(implode("\r\n", $expected), $result);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/ParameterTest.php b/dav/sabre-vobject/tests/Sabre/VObject/ParameterTest.php
new file mode 100644 (file)
index 0000000..1508304
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace Sabre\VObject;
+
+class ParameterTest extends \PHPUnit_Framework_TestCase {
+
+    function testSetup() {
+
+        $param = new Parameter('name','value');
+        $this->assertEquals('NAME',$param->name);
+        $this->assertEquals('value',$param->value);
+
+    }
+
+    function testCastToString() {
+
+        $param = new Parameter('name','value');
+        $this->assertEquals('value',$param->__toString());
+        $this->assertEquals('value',(string)$param);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Property/DateTimeTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Property/DateTimeTest.php
new file mode 100644 (file)
index 0000000..9dc7e86
--- /dev/null
@@ -0,0 +1,234 @@
+<?php
+
+namespace Sabre\VObject\Property;
+use Sabre\VObject\Component;
+
+class DateTimeTest extends \PHPUnit_Framework_TestCase {
+
+    function testSetDateTime() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt);
+
+        $this->assertEquals('19850704T013000', $elem->value);
+        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeLOCAL() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt, DateTime::LOCAL);
+
+        $this->assertEquals('19850704T013000', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeUTC() {
+
+        $tz = new \DateTimeZone('GMT');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt, DateTime::UTC);
+
+        $this->assertEquals('19850704T013000Z', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeLOCALTZ() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt, DateTime::LOCALTZ);
+
+        $this->assertEquals('19850704T013000', $elem->value);
+        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeDATE() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt, DateTime::DATE);
+
+        $this->assertEquals('19850704', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE', (string)$elem['VALUE']);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testSetDateTimeInvalid() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt, 7);
+
+    }
+
+    function testGetDateTimeCached() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new DateTime('DTSTART');
+        $elem->setDateTime($dt);
+
+        $this->assertEquals($elem->getDateTime(), $dt);
+
+    }
+
+    function testGetDateTimeDateNULL() {
+
+        $elem = new DateTime('DTSTART');
+        $dt = $elem->getDateTime();
+
+        $this->assertNull($dt);
+        $this->assertNull($elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateDATE() {
+
+        $elem = new DateTime('DTSTART','19850704');
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals(DateTime::DATE, $elem->getDateType());
+
+    }
+
+
+    function testGetDateTimeDateLOCAL() {
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals(DateTime::LOCAL, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateUTC() {
+
+        $elem = new DateTime('DTSTART','19850704T013000Z');
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('UTC', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::UTC, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateLOCALTZ() {
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $elem['TZID'] = 'Europe/Amsterdam';
+
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testGetDateTimeDateInvalid() {
+
+        $elem = new DateTime('DTSTART','bla');
+        $dt = $elem->getDateTime();
+
+    }
+
+    function testGetDateTimeWeirdTZ() {
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
+
+
+        $event = new Component('VEVENT');
+        $event->add($elem);
+
+        $timezone = new Component('VTIMEZONE');
+        $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
+        $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam';
+
+        $calendar = new Component('VCALENDAR');
+        $calendar->add($event);
+        $calendar->add($timezone);
+
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeBadTimeZone() {
+
+        $default = date_default_timezone_get();
+        date_default_timezone_set('Canada/Eastern');
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $elem['TZID'] = 'Moon';
+
+
+        $event = new Component('VEVENT');
+        $event->add($elem);
+
+        $timezone = new Component('VTIMEZONE');
+        $timezone->TZID = 'Moon';
+        $timezone->{'X-LIC-LOCATION'} = 'Moon';
+
+        $calendar = new Component('VCALENDAR');
+        $calendar->add($event);
+        $calendar->add($timezone);
+
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
+        date_default_timezone_set($default);
+
+    }
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/Property/MultiDateTimeTest.php b/dav/sabre-vobject/tests/Sabre/VObject/Property/MultiDateTimeTest.php
new file mode 100644 (file)
index 0000000..eae3e2b
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+
+namespace Sabre\VObject\Property;
+
+class MultiDateTimeTest extends \PHPUnit_Framework_TestCase {
+
+    function testSetDateTime() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->setTimeZone($tz);
+        $dt2->setTimeZone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2));
+
+        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
+        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeLOCAL() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->setTimeZone($tz);
+        $dt2->setTimeZone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2), DateTime::LOCAL);
+
+        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeUTC() {
+
+        $tz = new \DateTimeZone('GMT');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->setTimeZone($tz);
+        $dt2->setTimeZone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2), DateTime::UTC);
+
+        $this->assertEquals('19850704T013000Z,19860704T013000Z', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeLOCALTZ() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->setTimeZone($tz);
+        $dt2->setTimeZone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2), DateTime::LOCALTZ);
+
+        $this->assertEquals('19850704T013000,19860704T013000', $elem->value);
+        $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+        $this->assertEquals('DATE-TIME', (string)$elem['VALUE']);
+
+    }
+
+    function testSetDateTimeDATE() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->settimezone($tz);
+        $dt2->settimezone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2), DateTime::DATE);
+
+        $this->assertEquals('19850704,19860704', $elem->value);
+        $this->assertNull($elem['TZID']);
+        $this->assertEquals('DATE', (string)$elem['VALUE']);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testSetDateTimeInvalid() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt->setTimeZone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt), 7);
+
+    }
+
+    function testGetDateTimeCached() {
+
+        $tz = new \DateTimeZone('Europe/Amsterdam');
+        $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+        $dt2 = new \DateTime('1986-07-04 01:30:00', $tz);
+        $dt1->settimezone($tz);
+        $dt2->settimezone($tz);
+
+        $elem = new MultiDateTime('DTSTART');
+        $elem->setDateTimes(array($dt1,$dt2));
+
+        $this->assertEquals($elem->getDateTimes(), array($dt1,$dt2));
+
+    }
+
+    function testGetDateTimeDateNULL() {
+
+        $elem = new MultiDateTime('DTSTART');
+        $dt = $elem->getDateTimes();
+
+        $this->assertNull($dt);
+        $this->assertNull($elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateDATE() {
+
+        $elem = new MultiDateTime('DTSTART','19850704,19860704');
+        $dt = $elem->getDateTimes();
+
+        $this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
+        $this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
+        $this->assertEquals(DateTime::DATE, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateDATEReverse() {
+
+        $elem = new MultiDateTime('DTSTART','19850704,19860704');
+
+        $this->assertEquals(DateTime::DATE, $elem->getDateType());
+
+        $dt = $elem->getDateTimes();
+        $this->assertEquals('1985-07-04 00:00:00', $dt[0]->format('Y-m-d H:i:s'));
+        $this->assertEquals('1986-07-04 00:00:00', $dt[1]->format('Y-m-d H:i:s'));
+
+    }
+
+
+    function testGetDateTimeDateLOCAL() {
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals(DateTime::LOCAL, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateUTC() {
+
+        $elem = new DateTime('DTSTART','19850704T013000Z');
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('UTC', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::UTC, $elem->getDateType());
+
+    }
+
+    function testGetDateTimeDateLOCALTZ() {
+
+        $elem = new DateTime('DTSTART','19850704T013000');
+        $elem['TZID'] = 'Europe/Amsterdam';
+
+        $dt = $elem->getDateTime();
+
+        $this->assertInstanceOf('DateTime', $dt);
+        $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+        $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
+        $this->assertEquals(DateTime::LOCALTZ, $elem->getDateType());
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testGetDateTimeDateInvalid() {
+
+        $elem = new DateTime('DTSTART','bla');
+        $dt = $elem->getDateTime();
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/PropertyTest.php b/dav/sabre-vobject/tests/Sabre/VObject/PropertyTest.php
new file mode 100644 (file)
index 0000000..201646b
--- /dev/null
@@ -0,0 +1,293 @@
+<?php
+
+namespace Sabre\VObject;
+
+class PropertyTest extends \PHPUnit_Framework_TestCase {
+
+    public function testToString() {
+
+        $property = new Property('propname','propvalue');
+        $this->assertEquals('PROPNAME', $property->name);
+        $this->assertEquals('propvalue', $property->value);
+        $this->assertEquals('propvalue', $property->__toString());
+        $this->assertEquals('propvalue', (string)$property);
+
+    }
+
+    public function testParameterExists() {
+
+        $property = new Property('propname','propvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+
+        $this->assertTrue(isset($property['PARAMNAME']));
+        $this->assertTrue(isset($property['paramname']));
+        $this->assertFalse(isset($property['foo']));
+
+    }
+
+    public function testParameterGet() {
+
+        $property = new Property('propname','propvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+
+        $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
+
+    }
+
+    public function testParameterNotExists() {
+
+        $property = new Property('propname','propvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+
+        $this->assertInternalType('null',$property['foo']);
+
+    }
+
+    public function testParameterMultiple() {
+
+        $property = new Property('propname','propvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+
+        $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
+        $this->assertEquals(2,count($property['paramname']));
+
+    }
+
+    public function testSetParameterAsString() {
+
+        $property = new Property('propname','propvalue');
+        $property['paramname'] = 'paramvalue';
+
+        $this->assertEquals(1,count($property->parameters));
+        $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters[0]);
+        $this->assertEquals('PARAMNAME',$property->parameters[0]->name);
+        $this->assertEquals('paramvalue',$property->parameters[0]->value);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testSetParameterAsStringNoKey() {
+
+        $property = new Property('propname','propvalue');
+        $property[] = 'paramvalue';
+
+    }
+
+    public function testSetParameterObject() {
+
+        $property = new Property('propname','propvalue');
+        $param = new Parameter('paramname','paramvalue');
+
+        $property[] = $param;
+
+        $this->assertEquals(1,count($property->parameters));
+        $this->assertEquals($param, $property->parameters[0]);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testSetParameterObjectWithKey() {
+
+        $property = new Property('propname','propvalue');
+        $param = new Parameter('paramname','paramvalue');
+
+        $property['key'] = $param;
+
+    }
+
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testSetParameterObjectRandomObject() {
+
+        $property = new Property('propname','propvalue');
+        $property[] = new \StdClass();
+
+    }
+
+    public function testUnsetParameter() {
+
+        $property = new Property('propname','propvalue');
+        $param = new Parameter('paramname','paramvalue');
+        $property->parameters[] = $param;
+
+        unset($property['PARAMNAME']);
+        $this->assertEquals(0,count($property->parameters));
+
+    }
+
+    public function testParamCount() {
+
+        $property = new Property('propname','propvalue');
+        $param = new Parameter('paramname','paramvalue');
+        $property->parameters[] = $param;
+        $property->parameters[] = clone $param;
+
+        $this->assertEquals(2,count($property->parameters));
+
+    }
+
+    public function testSerialize() {
+
+        $property = new Property('propname','propvalue');
+
+        $this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize());
+
+    }
+
+    public function testSerializeParam() {
+
+        $property = new Property('propname','propvalue');
+        $property->parameters[] = new Parameter('paramname','paramvalue');
+        $property->parameters[] = new Parameter('paramname2','paramvalue2');
+
+        $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize());
+
+    }
+
+    public function testSerializeNewLine() {
+
+        $property = new Property('propname',"line1\nline2");
+
+        $this->assertEquals("PROPNAME:line1\\nline2\r\n",$property->serialize());
+
+    }
+
+    public function testSerializeLongLine() {
+
+        $value = str_repeat('!',200);
+        $property = new Property('propname',$value);
+
+        $expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n";
+
+        $this->assertEquals($expected,$property->serialize());
+
+    }
+
+    public function testSerializeUTF8LineFold() {
+
+        $value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a
+        $property = new Property('propname', $value);
+        $expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n";
+        $this->assertEquals($expected, $property->serialize());
+
+    }
+
+    public function testGetIterator() {
+
+        $it = new ElementList(array());
+        $property = new Property('propname','propvalue');
+        $property->setIterator($it);
+        $this->assertEquals($it,$property->getIterator());
+
+    }
+
+
+    public function testGetIteratorDefault() {
+
+        $property = new Property('propname','propvalue');
+        $it = $property->getIterator();
+        $this->assertTrue($it instanceof ElementList);
+        $this->assertEquals(1,count($it));
+
+    }
+
+    function testAddScalar() {
+
+        $property = new Property('EMAIL');
+
+        $property->add('myparam','value');
+
+        $this->assertEquals(1, count($property->parameters));
+
+        $this->assertTrue($property->parameters[0] instanceof Parameter);
+        $this->assertEquals('MYPARAM',$property->parameters[0]->name);
+        $this->assertEquals('value',$property->parameters[0]->value);
+
+    }
+
+    function testAddParameter() {
+
+        $prop = new Property('EMAIL');
+
+        $prop->add(new Parameter('MYPARAM','value'));
+
+        $this->assertEquals(1, count($prop->parameters));
+        $this->assertEquals('MYPARAM',$prop['myparam']->name);
+
+    }
+
+    function testAddParameterTwice() {
+
+        $prop = new Property('EMAIL');
+
+        $prop->add(new Parameter('MYPARAM', 'value1'));
+        $prop->add(new Parameter('MYPARAM', 'value2'));
+
+        $this->assertEquals(2, count($prop->parameters));
+
+        $this->assertEquals('MYPARAM',$prop['MYPARAM']->name);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail() {
+
+        $prop = new Property('EMAIL');
+        $prop->add(new Parameter('MPARAM'),'hello');
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail2() {
+
+        $property = new Property('EMAIL','value');
+        $property->add(array());
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testAddArgFail3() {
+
+        $property = new Property('EMAIL','value');
+        $property->add('hello',array());
+
+    }
+
+    function testClone() {
+
+        $property = new Property('EMAIL','value');
+        $property['FOO'] = 'BAR';
+
+        $property2 = clone $property;
+        
+        $property['FOO'] = 'BAZ';
+        $this->assertEquals('BAR', (string)$property2['FOO']);
+
+    }
+
+    function testCreateParams() {
+
+        $property = Property::create('X-PROP', 'value', array(
+            'param1' => 'value1',
+            'param2' => array('value2', 'value3')
+        ));
+
+        $this->assertEquals(1, count($property['PARAM1']));
+        $this->assertEquals(2, count($property['PARAM2']));
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/ReaderTest.php b/dav/sabre-vobject/tests/Sabre/VObject/ReaderTest.php
new file mode 100644 (file)
index 0000000..998ff62
--- /dev/null
@@ -0,0 +1,265 @@
+<?php
+
+namespace Sabre\VObject;
+
+class ReaderTest extends \PHPUnit_Framework_TestCase {
+
+    function testReadComponent() {
+
+        $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
+
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(0, count($result->children));
+
+    }
+
+    function testReadComponentUnixNewLine() {
+
+        $data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
+
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(0, count($result->children));
+
+    }
+
+    function testReadComponentMacNewLine() {
+
+        $data = "BEGIN:VCALENDAR\rEND:VCALENDAR";
+
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(0, count($result->children));
+
+    }
+
+    function testReadComponentLineFold() {
+
+        $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR";
+
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(0, count($result->children));
+
+    }
+
+    /**
+     * @expectedException Sabre\VObject\ParseException
+     */
+    function testReadCorruptComponent() {
+
+        $data = "BEGIN:VCALENDAR\r\nEND:FOO";
+
+        $result = Reader::read($data);
+
+    }
+
+    function testReadProperty() {
+
+        $data = "PROPNAME:propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+
+    }
+
+    function testReadPropertyWithNewLine() {
+
+        $data = 'PROPNAME:Line1\\nLine2\\NLine3\\\\Not the 4th line!';
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->value);
+
+    }
+
+    function testReadMappedProperty() {
+
+        $data = "DTSTART:20110529";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property\\DateTime', $result);
+        $this->assertEquals('DTSTART', $result->name);
+        $this->assertEquals('20110529', $result->value);
+
+    }
+
+    function testReadMappedPropertyGrouped() {
+
+        $data = "foo.DTSTART:20110529";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property\\DateTime', $result);
+        $this->assertEquals('DTSTART', $result->name);
+        $this->assertEquals('20110529', $result->value);
+
+    }
+
+
+    /**
+     * @expectedException Sabre\VObject\ParseException
+     */
+    function testReadBrokenLine() {
+
+        $data = "PROPNAME;propValue";
+        $result = Reader::read($data);
+
+    }
+
+    function testReadPropertyInComponent() {
+
+        $data = array(
+            "BEGIN:VCALENDAR",
+            "PROPNAME:propValue",
+            "END:VCALENDAR"
+        );
+
+        $result = Reader::read(implode("\r\n",$data));
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(1, count($result->children));
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]);
+        $this->assertEquals('PROPNAME', $result->children[0]->name);
+        $this->assertEquals('propValue', $result->children[0]->value);
+
+
+    }
+    function testReadNestedComponent() {
+
+        $data = array(
+            "BEGIN:VCALENDAR",
+            "BEGIN:VTIMEZONE",
+            "BEGIN:DAYLIGHT",
+            "END:DAYLIGHT",
+            "END:VTIMEZONE",
+            "END:VCALENDAR"
+        );
+
+        $result = Reader::read(implode("\r\n",$data));
+
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+        $this->assertEquals('VCALENDAR', $result->name);
+        $this->assertEquals(1, count($result->children));
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]);
+        $this->assertEquals('VTIMEZONE', $result->children[0]->name);
+        $this->assertEquals(1, count($result->children[0]->children));
+        $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]);
+        $this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name);
+
+
+    }
+
+    function testReadPropertyParameter() {
+
+        $data = "PROPNAME;PARAMNAME=paramvalue:propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('paramvalue', $result->parameters[0]->value);
+
+    }
+
+    function testReadPropertyNoValue() {
+
+        $data = "PROPNAME;PARAMNAME:propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('', $result->parameters[0]->value);
+
+    }
+
+    function testReadPropertyParameterExtraColon() {
+
+        $data = "PROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue:anotherrandomstring', $result->value);
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('paramvalue', $result->parameters[0]->value);
+
+    }
+
+    function testReadProperty2Parameters() {
+
+        $data = "PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+        $this->assertEquals(2, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('paramvalue', $result->parameters[0]->value);
+        $this->assertEquals('PARAMNAME2', $result->parameters[1]->name);
+        $this->assertEquals('paramvalue2', $result->parameters[1]->value);
+
+    }
+
+    function testReadPropertyParameterQuoted() {
+
+        $data = "PROPNAME;PARAMNAME=\"paramvalue\":propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('paramvalue', $result->parameters[0]->value);
+
+    }
+    function testReadPropertyParameterNewLines() {
+
+        $data = "PROPNAME;PARAMNAME=paramvalue1\\nvalue2\\\\nvalue3:propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals("paramvalue1\nvalue2\\nvalue3", $result->parameters[0]->value);
+
+    }
+
+    function testReadPropertyParameterQuotedColon() {
+
+        $data = "PROPNAME;PARAMNAME=\"param:value\":propValue";
+        $result = Reader::read($data);
+
+        $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+        $this->assertEquals('PROPNAME', $result->name);
+        $this->assertEquals('propValue', $result->value);
+        $this->assertEquals(1, count($result->parameters));
+        $this->assertEquals('PARAMNAME', $result->parameters[0]->name);
+        $this->assertEquals('param:value', $result->parameters[0]->value);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php b/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorFifthTuesdayProblemTest.php
new file mode 100644 (file)
index 0000000..ffcb6ee
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace Sabre\VObject;
+
+class RecurrenceIteratorFifthTuesdayProblemTest extends \PHPUnit_Framework_TestCase {
+
+    function testGetDTEnd() {
+
+        $ics = <<<ICS
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+TRANSP:OPAQUE
+DTEND;TZID=America/New_York:20070925T170000
+UID:uuid
+DTSTAMP:19700101T000000Z
+LOCATION:
+DESCRIPTION:
+STATUS:CONFIRMED
+SEQUENCE:18
+SUMMARY:Stuff
+DTSTART;TZID=America/New_York:20070925T160000
+CREATED:20071004T144642Z
+RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU
+END:VEVENT
+END:VCALENDAR
+ICS;
+
+        $vObject = Reader::read($ics);
+        $it = new RecurrenceIterator($vObject, (string)$vObject->VEVENT->UID);
+
+        while($it->valid()) {
+            $it->next();
+        }
+
+        // If we got here, it means we were successful. The bug that was in teh 
+        // system before would fail on the 5th tuesday of the month, if the 5th 
+        // tuesday did not exist.
+       
+    }
+
+}
+
+?>
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php b/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorInfiniteLoopProblemTest.php
new file mode 100644 (file)
index 0000000..3976388
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+namespace Sabre\VObject;
+
+use DateTime;
+use DateTimeZone;
+
+class RecurrenceIteratorInfiniteLoopProblemTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * This bug came from a Fruux customer. This would result in a never-ending
+     * request.
+     */
+    function testFastForwardTooFar() {
+
+        $ev = Component::create('VEVENT');
+        $ev->DTSTART = '20090420T180000Z';
+        $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1';
+
+        $this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00')));
+
+    }
+
+    /**
+     * Different bug, also likely an infinite loop.
+     */
+    function testYearlyByMonthLoop() {
+
+        $ev = Component::create('VEVENT');
+        $ev->UID = 'uuid';
+        $ev->DTSTART = '20120101T154500';
+        $ev->DTSTART['TZID'] = 'Europe/Berlin';
+        $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
+        $ev->DTEND = '20120101T164500';
+        $ev->DTEND['TZID'] = 'Europe/Berlin';
+
+        // This recurrence rule by itself is a yearly rule that should happen
+        // every february.
+        //
+        // The BYDAY part expands this to every day of the month, but the
+        // BYSETPOS limits this to only the 1st day of the month. Very crazy
+        // way to specify this, and could have certainly been a lot easier.
+        $cal = Component::create('VCALENDAR');
+        $cal->add($ev);
+
+        $it = new RecurrenceIterator($cal,'uuid');
+        $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
+
+        $collect = array();
+
+        while($it->valid()) {
+            $collect[] = $it->getDTSTART();
+            if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
+                break;
+            }
+            $it->next();
+
+        }
+
+        $this->assertEquals(
+            array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))),
+            $collect
+        );
+
+    }
+
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorTest.php b/dav/sabre-vobject/tests/Sabre/VObject/RecurrenceIteratorTest.php
new file mode 100644 (file)
index 0000000..c79ac68
--- /dev/null
@@ -0,0 +1,1137 @@
+<?php
+
+namespace Sabre\VObject;
+
+use DateTime;
+use DateTimeZone;
+
+class RecurrenceIteratorTest extends \PHPUnit_Framework_TestCase {
+
+    function testValues() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07'),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals(array(10), $it->byHour);
+        $this->assertEquals(array(5), $it->byMinute);
+        $this->assertEquals(array(16), $it->bySecond);
+        $this->assertEquals(array(32), $it->byWeekNo);
+        $this->assertEquals(array(100,200), $it->byYearDay);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     * @depends testValues
+     */
+    function testInvalidFreq() {
+
+        $ev = new Component('VEVENT');
+        $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07'),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testVCalendarNoUID() {
+
+        $vcal = new Component('VCALENDAR');
+        $it = new RecurrenceIterator($vcal);
+
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    function testVCalendarInvalidUID() {
+
+        $vcal = new Component('VCALENDAR');
+        $it = new RecurrenceIterator($vcal,'foo');
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testDaily() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,$ev->uid);
+
+        $this->assertEquals('daily', $it->frequency);
+        $this->assertEquals(3, $it->interval);
+        $this->assertEquals(new DateTime('2011-10-25', new DateTimeZone('UTC')), $it->until);
+
+        // Max is to prevent overflow
+        $max = 12;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-10-07', $tz),
+                new DateTime('2011-10-10', $tz),
+                new DateTime('2011-10-13', $tz),
+                new DateTime('2011-10-16', $tz),
+                new DateTime('2011-10-19', $tz),
+                new DateTime('2011-10-22', $tz),
+                new DateTime('2011-10-25', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testNoRRULE() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,$ev->uid);
+
+        $this->assertEquals('daily', $it->frequency);
+        $this->assertEquals(1, $it->interval);
+
+        // Max is to prevent overflow
+        $max = 12;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-10-07', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testDailyByDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('daily', $it->frequency);
+        $this->assertEquals(2, $it->interval);
+        $this->assertEquals(array('TU','WE','FR'), $it->byDay);
+
+        // Grabbing the next 12 items
+        $max = 12;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-10-07', $tz),
+                new DateTime('2011-10-11', $tz),
+                new DateTime('2011-10-19', $tz),
+                new DateTime('2011-10-21', $tz),
+                new DateTime('2011-10-25', $tz),
+                new DateTime('2011-11-02', $tz),
+                new DateTime('2011-11-04', $tz),
+                new DateTime('2011-11-08', $tz),
+                new DateTime('2011-11-16', $tz),
+                new DateTime('2011-11-18', $tz),
+                new DateTime('2011-11-22', $tz),
+                new DateTime('2011-11-30', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testWeekly() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('weekly', $it->frequency);
+        $this->assertEquals(2, $it->interval);
+        $this->assertEquals(10, $it->count);
+
+        // Max is to prevent overflow
+        $max = 12;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-10-07', $tz),
+                new DateTime('2011-10-21', $tz),
+                new DateTime('2011-11-04', $tz),
+                new DateTime('2011-11-18', $tz),
+                new DateTime('2011-12-02', $tz),
+                new DateTime('2011-12-16', $tz),
+                new DateTime('2011-12-30', $tz),
+                new DateTime('2012-01-13', $tz),
+                new DateTime('2012-01-27', $tz),
+                new DateTime('2012-02-10', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testWeeklyByDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('weekly', $it->frequency);
+        $this->assertEquals(2, $it->interval);
+        $this->assertEquals(array('TU','WE','FR'), $it->byDay);
+        $this->assertEquals('SU', $it->weekStart);
+
+        // Grabbing the next 12 items
+        $max = 12;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-10-07', $tz),
+                new DateTime('2011-10-18', $tz),
+                new DateTime('2011-10-19', $tz),
+                new DateTime('2011-10-21', $tz),
+                new DateTime('2011-11-01', $tz),
+                new DateTime('2011-11-02', $tz),
+                new DateTime('2011-11-04', $tz),
+                new DateTime('2011-11-15', $tz),
+                new DateTime('2011-11-16', $tz),
+                new DateTime('2011-11-18', $tz),
+                new DateTime('2011-11-29', $tz),
+                new DateTime('2011-11-30', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthly() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-12-05', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(3, $it->interval);
+        $this->assertEquals(5, $it->count);
+
+        $max = 14;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-12-05', $tz),
+                new DateTime('2012-03-05', $tz),
+                new DateTime('2012-06-05', $tz),
+                new DateTime('2012-09-05', $tz),
+                new DateTime('2012-12-05', $tz),
+            ),
+            $result
+        );
+
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthlyEndOfMonth() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-12-31', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(2, $it->interval);
+        $this->assertEquals(12, $it->count);
+
+        $max = 14;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-12-31', $tz),
+                new DateTime('2012-08-31', $tz),
+                new DateTime('2012-10-31', $tz),
+                new DateTime('2012-12-31', $tz),
+                new DateTime('2013-08-31', $tz),
+                new DateTime('2013-10-31', $tz),
+                new DateTime('2013-12-31', $tz),
+                new DateTime('2014-08-31', $tz),
+                new DateTime('2014-10-31', $tz),
+                new DateTime('2014-12-31', $tz),
+                new DateTime('2015-08-31', $tz),
+                new DateTime('2015-10-31', $tz),
+            ),
+            $result
+        );
+
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthlyByMonthDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(5, $it->interval);
+        $this->assertEquals(9, $it->count);
+        $this->assertEquals(array(1, 31, -7), $it->byMonthDay);
+
+        $max = 14;
+        $result = array();
+        foreach($it as $item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-01-01', $tz),
+                new DateTime('2011-01-25', $tz),
+                new DateTime('2011-01-31', $tz),
+                new DateTime('2011-06-01', $tz),
+                new DateTime('2011-06-24', $tz),
+                new DateTime('2011-11-01', $tz),
+                new DateTime('2011-11-24', $tz),
+                new DateTime('2012-04-01', $tz),
+                new DateTime('2012-04-24', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthlyByDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(2, $it->interval);
+        $this->assertEquals(16, $it->count);
+        $this->assertEquals(array('MO','-2TU','+1WE','3TH'), $it->byDay);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-01-03', $tz),
+                new DateTime('2011-01-05', $tz),
+                new DateTime('2011-01-10', $tz),
+                new DateTime('2011-01-17', $tz),
+                new DateTime('2011-01-18', $tz),
+                new DateTime('2011-01-20', $tz),
+                new DateTime('2011-01-24', $tz),
+                new DateTime('2011-01-31', $tz),
+                new DateTime('2011-03-02', $tz),
+                new DateTime('2011-03-07', $tz),
+                new DateTime('2011-03-14', $tz),
+                new DateTime('2011-03-17', $tz),
+                new DateTime('2011-03-21', $tz),
+                new DateTime('2011-03-22', $tz),
+                new DateTime('2011-03-28', $tz),
+                new DateTime('2011-05-02', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthlyByDayByMonthDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-08-01', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(1, $it->interval);
+        $this->assertEquals(10, $it->count);
+        $this->assertEquals(array('MO'), $it->byDay);
+        $this->assertEquals(array(1), $it->byMonthDay);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-08-01', $tz),
+                new DateTime('2012-10-01', $tz),
+                new DateTime('2013-04-01', $tz),
+                new DateTime('2013-07-01', $tz),
+                new DateTime('2014-09-01', $tz),
+                new DateTime('2014-12-01', $tz),
+                new DateTime('2015-06-01', $tz),
+                new DateTime('2016-02-01', $tz),
+                new DateTime('2016-08-01', $tz),
+                new DateTime('2017-05-01', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testMonthlyByDayBySetPos() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('monthly', $it->frequency);
+        $this->assertEquals(1, $it->interval);
+        $this->assertEquals(10, $it->count);
+        $this->assertEquals(array('MO','TU','WE','TH','FR'), $it->byDay);
+        $this->assertEquals(array(1,-1), $it->bySetPos);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-01-03', $tz),
+                new DateTime('2011-01-31', $tz),
+                new DateTime('2011-02-01', $tz),
+                new DateTime('2011-02-28', $tz),
+                new DateTime('2011-03-01', $tz),
+                new DateTime('2011-03-31', $tz),
+                new DateTime('2011-04-01', $tz),
+                new DateTime('2011-04-29', $tz),
+                new DateTime('2011-05-02', $tz),
+                new DateTime('2011-05-31', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testYearly() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('yearly', $it->frequency);
+        $this->assertEquals(3, $it->interval);
+        $this->assertEquals(10, $it->count);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-01-01', $tz),
+                new DateTime('2014-01-01', $tz),
+                new DateTime('2017-01-01', $tz),
+                new DateTime('2020-01-01', $tz),
+                new DateTime('2023-01-01', $tz),
+                new DateTime('2026-01-01', $tz),
+                new DateTime('2029-01-01', $tz),
+                new DateTime('2032-01-01', $tz),
+                new DateTime('2035-01-01', $tz),
+                new DateTime('2038-01-01', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testYearlyLeapYear() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=3';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2012-02-29', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('yearly', $it->frequency);
+        $this->assertEquals(3, $it->count);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2012-02-29', $tz),
+                new DateTime('2016-02-29', $tz),
+                new DateTime('2020-02-29', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testYearlyByMonth() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-04-07', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('yearly', $it->frequency);
+        $this->assertEquals(4, $it->interval);
+        $this->assertEquals(8, $it->count);
+        $this->assertEquals(array(4,10), $it->byMonth);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-04-07', $tz),
+                new DateTime('2011-10-07', $tz),
+                new DateTime('2015-04-07', $tz),
+                new DateTime('2015-10-07', $tz),
+                new DateTime('2019-04-07', $tz),
+                new DateTime('2019-10-07', $tz),
+                new DateTime('2023-04-07', $tz),
+                new DateTime('2023-10-07', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testYearlyByMonthByDay() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('yearly', $it->frequency);
+        $this->assertEquals(5, $it->interval);
+        $this->assertEquals(8, $it->count);
+        $this->assertEquals(array(4,10), $it->byMonth);
+        $this->assertEquals(array('1MO','-1SU'), $it->byDay);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-04-04', $tz),
+                new DateTime('2011-04-24', $tz),
+                new DateTime('2011-10-03', $tz),
+                new DateTime('2011-10-30', $tz),
+                new DateTime('2016-04-04', $tz),
+                new DateTime('2016-04-24', $tz),
+                new DateTime('2016-10-03', $tz),
+                new DateTime('2016-10-30', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testFastForward() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
+        $dtStart = new Property\DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC')),Property\DateTime::UTC);
+
+        $ev->add($dtStart);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        // The idea is that we're fast-forwarding too far in the future, so
+        // there will be no results left.
+        $it->fastForward(new DateTime('2020-05-05', new DateTimeZone('UTC')));
+
+        $max = 20;
+        $result = array();
+        while($item = $it->current()) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+            $it->next();
+
+        }
+
+        $tz = new DateTimeZone('UTC');
+        $this->assertEquals(array(), $result);
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testComplexExclusions() {
+
+        $ev = new Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=10';
+        $dtStart = new Property\DateTime('DTSTART');
+
+        $tz = new DateTimeZone('Canada/Eastern');
+        $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz),Property\DateTime::LOCALTZ);
+
+        $exDate1 = new Property\MultiDateTime('EXDATE');
+        $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz)), Property\DateTime::LOCALTZ);
+        $exDate2 = new Property\MultiDateTime('EXDATE');
+        $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz)), Property\DateTime::LOCALTZ);
+
+        $ev->add($dtStart);
+        $ev->add($exDate1);
+        $ev->add($exDate2);
+
+        $vcal = Component::create('VCALENDAR');
+        $vcal->add($ev);
+
+        $it = new RecurrenceIterator($vcal,(string)$ev->uid);
+
+        $this->assertEquals('yearly', $it->frequency);
+        $this->assertEquals(1, $it->interval);
+        $this->assertEquals(10, $it->count);
+
+        $max = 20;
+        $result = array();
+        foreach($it as $k=>$item) {
+
+            $result[] = $item;
+            $max--;
+
+            if (!$max) break;
+
+        }
+
+        $this->assertEquals(
+            array(
+                new DateTime('2011-01-01 13:50:20', $tz),
+                new DateTime('2013-01-01 13:50:20', $tz),
+                new DateTime('2015-01-01 13:50:20', $tz),
+                new DateTime('2017-01-01 13:50:20', $tz),
+                new DateTime('2018-01-01 13:50:20', $tz),
+                new DateTime('2019-01-01 13:50:20', $tz),
+                new DateTime('2020-01-01 13:50:20', $tz),
+            ),
+            $result
+        );
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testOverridenEvent() {
+
+        $vcal = Component::create('VCALENDAR');
+
+        $ev1 = Component::create('VEVENT');
+        $ev1->UID = 'overridden';
+        $ev1->RRULE = 'FREQ=DAILY;COUNT=10';
+        $ev1->DTSTART = '20120107T120000Z';
+        $ev1->SUMMARY = 'baseEvent';
+
+        $vcal->add($ev1);
+
+        // ev2 overrides an event, and puts it on 2pm instead.
+        $ev2 = Component::create('VEVENT');
+        $ev2->UID = 'overridden';
+        $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
+        $ev2->DTSTART = '20120110T140000Z';
+        $ev2->SUMMARY = 'Event 2';
+
+        $vcal->add($ev2);
+
+        // ev3 overrides an event, and puts it 2 days and 2 hours later 
+        $ev3 = Component::create('VEVENT');
+        $ev3->UID = 'overridden';
+        $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
+        $ev3->DTSTART = '20120115T140000Z';
+        $ev3->SUMMARY = 'Event 3';
+
+        $vcal->add($ev3);
+
+        $it = new RecurrenceIterator($vcal,'overridden');
+
+        $dates = array();
+        $summaries = array();
+        while($it->valid()) {
+
+            $dates[] = $it->getDTStart();
+            $summaries[] = (string)$it->getEventObject()->SUMMARY;
+            $it->next();
+
+        }
+
+        $tz = new DateTimeZone('GMT');
+        $this->assertEquals(array(
+            new DateTime('2012-01-07 12:00:00',$tz),
+            new DateTime('2012-01-08 12:00:00',$tz),
+            new DateTime('2012-01-09 12:00:00',$tz),
+            new DateTime('2012-01-10 14:00:00',$tz),
+            new DateTime('2012-01-11 12:00:00',$tz),
+            new DateTime('2012-01-12 12:00:00',$tz),
+            new DateTime('2012-01-14 12:00:00',$tz),
+            new DateTime('2012-01-15 12:00:00',$tz),
+            new DateTime('2012-01-15 14:00:00',$tz),
+            new DateTime('2012-01-16 12:00:00',$tz),
+        ), $dates);
+
+        $this->assertEquals(array(
+            'baseEvent',
+            'baseEvent',
+            'baseEvent',
+            'Event 2',
+            'baseEvent',
+            'baseEvent',
+            'baseEvent',
+            'baseEvent',
+            'Event 3',
+            'baseEvent',
+        ), $summaries);
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testOverridenEvent2() {
+
+        $vcal = Component::create('VCALENDAR');
+
+        $ev1 = Component::create('VEVENT');
+        $ev1->UID = 'overridden';
+        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
+        $ev1->DTSTART = '20120112T120000Z';
+        $ev1->SUMMARY = 'baseEvent';
+
+        $vcal->add($ev1);
+
+        // ev2 overrides an event, and puts it 6 days earlier instead.
+        $ev2 = Component::create('VEVENT');
+        $ev2->UID = 'overridden';
+        $ev2->{'RECURRENCE-ID'} = '20120119T120000Z';
+        $ev2->DTSTART = '20120113T120000Z';
+        $ev2->SUMMARY = 'Override!';
+
+        $vcal->add($ev2);
+
+        $it = new RecurrenceIterator($vcal,'overridden');
+
+        $dates = array();
+        $summaries = array();
+        while($it->valid()) {
+
+            $dates[] = $it->getDTStart();
+            $summaries[] = (string)$it->getEventObject()->SUMMARY;
+            $it->next();
+
+        }
+
+        $tz = new DateTimeZone('GMT');
+        $this->assertEquals(array(
+            new DateTime('2012-01-12 12:00:00',$tz),
+            new DateTime('2012-01-13 12:00:00',$tz),
+            new DateTime('2012-01-26 12:00:00',$tz),
+
+        ), $dates);
+
+        $this->assertEquals(array(
+            'baseEvent',
+            'Override!',
+            'baseEvent',
+        ), $summaries);
+
+    }
+
+    /**
+     * @depends testValues
+     */
+    function testOverridenEventNoValuesExpected() {
+
+        $vcal = Component::create('VCALENDAR');
+
+        $ev1 = Component::create('VEVENT');
+        $ev1->UID = 'overridden';
+        $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
+        $ev1->DTSTART = '20120124T120000Z';
+        $ev1->SUMMARY = 'baseEvent';
+
+        $vcal->add($ev1);
+
+        // ev2 overrides an event, and puts it 6 days earlier instead.
+        $ev2 = Component::create('VEVENT');
+        $ev2->UID = 'overridden';
+        $ev2->{'RECURRENCE-ID'} = '20120131T120000Z';
+        $ev2->DTSTART = '20120125T120000Z';
+        $ev2->SUMMARY = 'Override!';
+
+        $vcal->add($ev2);
+
+        $it = new RecurrenceIterator($vcal,'overridden');
+
+        $dates = array();
+        $summaries = array();
+
+        // The reported problem was specifically related to the VCALENDAR 
+        // expansion. In this parcitular case, we had to forward to the 28th of 
+        // january.
+        $it->fastForward(new DateTime('2012-01-28 23:00:00'));
+
+        // We stop the loop when it hits the 6th of februari. Normally this 
+        // iterator would hit 24, 25 (overriden from 31) and 7 feb but because 
+        // we 'filter' from the 28th till the 6th, we should get 0 results.
+        while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) {
+
+            $dates[] = $it->getDTStart();
+            $summaries[] = (string)$it->getEventObject()->SUMMARY;
+            $it->next();
+
+        }
+
+        $this->assertEquals(array(), $dates);
+        $this->assertEquals(array(), $summaries);
+
+    }
+}
+
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/TimeZoneUtilTest.php b/dav/sabre-vobject/tests/Sabre/VObject/TimeZoneUtilTest.php
new file mode 100644 (file)
index 0000000..1f13ccb
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+
+namespace Sabre\VObject;
+
+class TimezoneUtilTest extends \PHPUnit_Framework_TestCase {
+
+    /**
+     * @dataProvider getMapping
+     */
+    function testCorrectTZ($timezoneName) {
+
+        $tz = new \DateTimeZone($timezoneName);
+
+    }
+
+    function getMapping() {
+
+        // PHPUNit requires an array of arrays
+        return array_map(
+            function($value) {
+                return array($value);
+            },
+            TimeZoneUtil::$map
+        );
+
+    }
+
+    function testExchangeMap() {
+
+        $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+X-MICROSOFT-CDO-TZID:2
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+
+        $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
+
+        $this->assertEquals(new \DateTimeZone('Europe/Sarajevo'), $tz);
+
+    }
+
+    function testUnknownExchangeId() {
+
+        $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+X-MICROSOFT-CDO-TZID:2000
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+
+        $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
+
+        $this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $tz);
+
+    }
+
+    function testWindowsTimeZone() {
+
+        $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time');
+        $this->assertEquals(new \DateTimeZone('America/New_York'), $tz);
+
+    }
+
+    function testFallBack() {
+
+        $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+        $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj));
+
+        $this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $tz);
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/VersionTest.php b/dav/sabre-vobject/tests/Sabre/VObject/VersionTest.php
new file mode 100644 (file)
index 0000000..ae6855e
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+namespace Sabre\VObject;
+
+class VersionTest extends \PHPUnit_Framework_TestCase {
+
+    function testString() {
+
+        $v = Version::VERSION;
+        $this->assertEquals(-1, version_compare('0.9.0',$v));
+
+        $s = Version::STABILITY;
+        $this->assertTrue($s == 'alpha' || $s == 'beta' || $s =='stable');
+
+    }
+
+}
diff --git a/dav/sabre-vobject/tests/Sabre/VObject/issue153.vcf b/dav/sabre-vobject/tests/Sabre/VObject/issue153.vcf
new file mode 100644 (file)
index 0000000..5fb0fa2
--- /dev/null
@@ -0,0 +1,352 @@
+BEGIN:VCARD\r
+VERSION:3.0\r
+N:Benutzer;Test;;;\r
+FN:Test Benutzer\r
+PHOTO;BASE64:\r
+  /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
+  AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
+  AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
+  Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
+  CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
+  AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
+  kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
+  aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
+  1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+  CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
+  YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
+  goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
+  5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
+  F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
+  7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
+  BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
+  t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau
+  m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H
+  a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii
+  KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ
+  BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW
+  u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn
+  bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4
+  g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci
+  QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh
+  UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9
+  CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc
+  u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku
+  Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP
+  j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP
+  OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro
+  /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU
+  LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy
+  9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl
+  G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW
+  QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb
+  94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD
+  5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+
+  dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV
+  4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0
+  sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW
+  rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K
+  rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk
+  HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD
+  xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC
+  yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY
+  itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN
+  AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh
+  dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V
+  DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A
+  RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun
+  8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg
+  QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt
+  pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS
+  nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu
+  lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V
+  5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF
+  tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3
+  Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs
+  uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+
+  1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx
+  sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r
+  VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP
+  X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY
+  2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm
+  P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi
+  yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N
+  t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk
+  OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4
+  V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish
+  yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46
+  ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW
+  KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX
+  e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO
+  lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY
+  MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21
+  MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy
+  WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d
+  6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ
+  HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs
+  HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw
+  ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa
+  KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9
+  iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8
+  Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5
+  z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33
+  yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4
+  NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/
+  BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3
+  evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP
+  4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8
+  nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+
+  RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi
+  JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0
+  xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA
+  GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS
+  P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw
+  WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+
+  6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6
+  1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf
+  rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c
+  VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z
+  nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m
+  PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3
+  En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4
+  wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7
+  3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP
+  7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3
+  wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G
+  00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE
+  rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg
+  B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA
+  6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw
+  cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb
+  juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r
+  PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t
+  7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr
+  nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD
+  aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq
+  /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg
+  C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA
+  iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F
+  h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb
+  d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC
+  UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk
+  XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR
+  79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF
+  jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA
+  MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA
+  Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA
+  +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W
+  qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE
+  DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM
+  jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR
+  jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI
+  do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze
+  MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S
+  KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn
+  cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ
+  JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz
+  R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR
+  kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd
+  0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb
+  zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/
+  Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf
+  Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa
+  AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht
+  X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp
+  UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO
+  3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK
+  QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH
+  HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/
+  McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka
+  6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi
+  Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy
+  MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u
+  1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up
+  YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH
+  0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB
+  159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA
+  7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG
+  0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm
+  gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS
+  24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l
+  GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd
+  g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34
+  x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9
+  8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I
+  NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ
+  GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe
+  DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey
+  jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN
+  VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP
+  uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU
+  6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9
+  jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt
+  XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0
+  /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr
+  qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM
+  4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM
+  XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw
+  NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx
+  2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X
+  2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU
+  65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn
+  h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+
+  OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd
+  xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh
+  aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw
+  o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH
+  1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP
+  O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb
+  lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ
+  dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy
+  7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi
+  anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2
+  Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y
+  ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ
+  LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8
+  g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld
+  x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar
+  u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV
+  RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe
+  3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz
+  xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg
+  eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ
+  fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6
+  XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2
+  ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF
+  c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K
+  iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU
+  CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c
+  54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc
+  ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c
+  OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4
+  AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8
+  zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn
+  Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4
+  eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9
+  cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW
+  KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21
+  1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi
+  qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ
+  q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N
+  ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG
+  CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e
+  lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt
+  MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6
+  qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh
+  h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv
+  S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL
+  KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w
+  dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z
+  mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb
+  AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww
+  eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC
+  L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm
+  xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C
+  KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG
+  OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY
+  gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7
+  qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP
+  mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA
+  zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR
+  mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg
+  pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF
+  +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu
+  mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND
+  bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V
+  2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE
+  9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9
+  QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4
+  QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki
+  RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP
+  xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW
+  ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA
+  bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml
+  jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk
+  1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub
+  c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr
+  co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI
+  gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI
+  iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG
+  WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw
+  tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG
+  7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC
+  SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1
+  R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b
+  AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG
+  31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx
+  obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy
+  Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA
+  GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr
+  csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg
+  0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx
+  bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1
+  oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71
+  LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j
+  TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP
+  HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX
+  bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x
+  0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl
+  PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC
+  s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT
+  LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc
+  FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09
+  9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW
+  56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw
+  2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH
+  wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj
+  pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I
+  /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW
+  UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5
+  vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ
+  bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm
+  AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5
+  7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW
+  DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX
+  TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p
+  wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws
+  HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6
+  VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt
+  6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH
+  X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ
+  7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8
+  QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P
+  BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG
+  R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6
+  zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe
+  poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD
+  4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D
+  N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG
+  XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t
+  yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK
+  yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb
+  qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44
+  5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX
+  +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA
+  5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC
+  CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye
+  3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w
+  EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg
+  CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68
+  d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE
+  bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC
+  UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH
+  qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF
+  pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H
+  G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX
+  cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/
+  AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw
+  aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG
+  W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa
+  fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw
+  vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p
+  V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma
+  IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw
+  EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G
+  9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2
+  Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6
+  ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+
+  U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH
+  14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr
+  bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt
+  0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw
+  zbVbk4/OrNpefLsnyyg5UUAf/9k=\r
+END:VCARD\r
diff --git a/dav/sabre-vobject/tests/bootstrap.php b/dav/sabre-vobject/tests/bootstrap.php
new file mode 100644 (file)
index 0000000..ee071ea
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+// Composer autoloader
+include __DIR__ . '/../vendor/autoload.php';
diff --git a/dav/sabre-vobject/tests/phpunit.xml b/dav/sabre-vobject/tests/phpunit.xml
new file mode 100644 (file)
index 0000000..8aeb65a
--- /dev/null
@@ -0,0 +1,17 @@
+<phpunit
+  colors="true"
+  bootstrap="bootstrap.php"
+  convertErrorsToExceptions="true"
+  convertNoticesToExceptions="true"
+  convertWarningsToExceptions="true"
+  >
+  <testsuite name="Sabre_VObject">
+    <directory>Sabre/</directory>
+  </testsuite>
+
+  <filter>
+    <whitelist addUncoveredFilesFromWhitelist="true">
+       <directory suffix=".php">../lib/</directory>
+    </whitelist>
+  </filter>
+</phpunit>
index 4f7d6f4dacc3e120761b66393cad201d527136fe..e6babf7dcf041712c91724aff7ca32cc65798cd9 100644 (file)
@@ -24,7 +24,7 @@
   }
   </style>
   
-  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
+  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
   <script type="text/javascript" src="jquery.timePicker.js"></script>
   <script type="text/javascript">
   jQuery(function() {
     
   });
   </script>
-  <script type="text/javascript">
-  // Analytics.
-  var _gaq=_gaq||[];_gaq.push(["_setAccount","UA-123444-3"]),_gaq.push(["_trackPageview"]),function(){var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=("https:"==document.location.protocol?"https://ssl":"http://www")+".google-analytics.com/ga.js";var b=document.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b)}()
-  </script>
 </head>
 
 <body>
diff --git a/dav/virtual_cal_source_friendica.inc.php b/dav/virtual_cal_source_friendica.inc.php
deleted file mode 100644 (file)
index 7434fbb..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-
-class FriendicaVirtualCalSourceBackend extends VirtualCalSourceBackend
-{
-
-       /**
-        * @static
-        * @return int
-        */
-       static public function getNamespace()
-       {
-               return CALDAV_NAMESPACE_FRIENDICA_NATIVE;
-       }
-
-       /**
-        * @static
-        * @param int $uid
-        * @param int $namespace_id
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return void
-        */
-       static function createCache($uid = 0, $namespace_id = 0)
-       {
-       }
-
-
-       static private function row2array($row, $timezone, $hostname, $uid, $namespace_id) {
-               $v = new vcalendar();
-               $v->setConfig('unique_id', $hostname);
-
-               $v->setProperty('method', 'PUBLISH');
-               $v->setProperty("x-wr-calname", "AnimexxCal");
-               $v->setProperty("X-WR-CALDESC", "Animexx Calendar");
-               $v->setProperty("X-WR-TIMEZONE", $timezone);
-
-               if ($row["adjust"]) {
-                       $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]);
-                       $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]);
-               } else {
-                       $start = $row["start"];
-                       $finish = $row["finish"];
-               }
-               $allday      = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false);
-
-               /*
-
-               if ($allday) {
-                       $dat = Datetime::createFromFormat("Y-m-d H:i:s", $finish_tmp);
-                       $dat->sub(new DateInterval("P1D"));
-                       $finish = datetime_convert("UTC", date_default_timezone_get(), $dat->format("Y-m-d H:i:s"));
-                       var_dump($finish);
-               }
-               */
-
-               // 2012-06-29 - change to Friendica new event behaviour where summary is present and required,
-               // but use desc for older events where summary wasn't present or required (but desc was)
-
-               $subject     = (($row["summary"]) ? $row["summary"] : substr(preg_replace("/\[[^\]]*\]/", "", $row["desc"]), 0, 100));
-               $description = (($row["desc"]) ? preg_replace("/\[[^\]]*\]/", "", $row["desc"]) : $row["summary"]);
-
-               $vevent = dav_create_vevent(wdcal_mySql2icalTime($row["start"]), wdcal_mySql2icalTime($row["finish"]), false);
-               $vevent->setLocation(icalendar_sanitize_string($row["location"]));
-               $vevent->setSummary(icalendar_sanitize_string($subject));
-               $vevent->setDescription(icalendar_sanitize_string($description));
-
-               $v->setComponent($vevent);
-               $ical  = $v->createCalendar();
-               return array(
-                       "uid"              => $uid,
-                       "namespace"        => CALDAV_NAMESPACE_FRIENDICA_NATIVE,
-                       "namespace_id"     => $namespace_id,
-                       "date"             => $row["edited"],
-                       "data_uri"         => "friendica-" . $namespace_id . "-" . $row["id"] . "@" . $hostname,
-                       "data_subject"     => $subject,
-                       "data_location"    => $row["location"],
-                       "data_description" => $description,
-                       "data_start"       => $start,
-                       "data_end"         => $finish,
-                       "data_allday"      => $allday,
-                       "data_type"        => $row["type"],
-                       "ical"             => $ical,
-                       "ical_size"        => strlen($ical),
-                       "ical_etag"        => md5($ical),
-               );
-
-       }
-
-       /**
-        * @static
-        * @param int $uid
-        * @param int $namespace_id
-        * @param string|int $date_from
-        * @param string|int $date_to
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return array
-        */
-       static public function getItemsByTime($uid = 0, $namespace_id = 0, $date_from = "", $date_to = "")
-       {
-               $uid          = IntVal($uid);
-               $namespace_id = IntVal($namespace_id);
-
-               switch ($namespace_id) {
-                       case CALDAV_FRIENDICA_MINE:
-                               $sql_where = " AND cid = 0";
-                               break;
-                       case CALDAV_FRIENDICA_CONTACTS:
-                               $sql_where = " AND cid > 0";
-                               break;
-                       default:
-                               throw new Sabre_DAV_Exception_NotFound();
-               }
-
-               if ($date_from != "") {
-                       if (is_numeric($date_from)) $sql_where .= " AND `finish` >= '" . date("Y-m-d H:i:s", $date_from) . "'";
-                       else $sql_where .= " AND `finish` >= '" . dbesc($date_from) . "'";
-               }
-               if ($date_to != "") {
-                       if (is_numeric($date_to)) $sql_where .= " AND `start` <= '" . date("Y-m-d H:i:s", $date_to) . "'";
-                       else $sql_where .= " AND `start` <= '" . dbesc($date_to) . "'";
-               }
-
-               $ret  = array();
-               $a    = get_app();
-               $host = $a->get_hostname();
-
-
-               $r    = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", $uid);
-               foreach ($r as $row) $ret[] =self::row2array($row, $a->timezone, $host, $uid, $namespace_id);
-
-               return $ret;
-       }
-
-
-       /**
-        * @static
-        * @param int $uid
-        * @param string $uri
-        * @throws Sabre_DAV_Exception_NotFound
-        * @return array
-        */
-       static public function getItemsByUri($uid = 0, $uri)
-       {
-               $x = explode("-", $uri);
-               if ($x[0] != "friendica") throw new Sabre_DAV_Exception_NotFound();
-
-               $namespace_id = IntVal($x[1]);
-               switch ($namespace_id) {
-                       case CALDAV_FRIENDICA_MINE:
-                               $sql_where = " AND cid = 0";
-                               break;
-                       case CALDAV_FRIENDICA_CONTACTS:
-                               $sql_where = " AND cid > 0";
-                               break;
-                       default:
-                               throw new Sabre_DAV_Exception_NotFound();
-               }
-
-               $a    = get_app();
-               $host = $a->get_hostname();
-
-               $r    = q("SELECT * FROM `event` WHERE `uid` = %d AND id = %d " . $sql_where, $uid, IntVal($x[2]));
-               if (count($r) != 1) throw new Sabre_DAV_Exception_NotFound();
-               $ret =self::row2array($r[0], $a->timezone, $host, $uid, $namespace_id);
-
-               return $ret;
-       }
-
-
-}
\ No newline at end of file
diff --git a/dav/wdcal.css b/dav/wdcal.css
deleted file mode 100644 (file)
index c64f0cf..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-
-div.colorPicker-picker { display: inline-block; }
-.colorPicker-palette { font-size: 12px; }
-.animexxcalendar label, .colorPicker-palette label { background: none; border: none; padding: 0; margin: 0; box-shadow: none; display: inline; font-size: 14px; }
-.animexxcalendar input, .colorPicker-palette input { font-size: 14px; }
-
-
-.ui-datepicker { width: 200px; }
-.ui-datepicker * { font-size: 12px; line-height: 12px; }
-.ui-datepicker th { padding: 10px 2px; }
-.ui-datepicker select.ui-datepicker-year { min-width: 0; width: 50px !important; }
-#cal_start_time, #cal_end_time { width: 5em; margin-left: 1em; }
-#cal_start_date, #cal_end_date { width: 6em;}
\ No newline at end of file
diff --git a/dav/wdcal/Changelog.txt b/dav/wdcal/Changelog.txt
deleted file mode 100644 (file)
index 9cc23a4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-v0.1.1
-======
-[FEATURE] A "New Event" Button in the navigation bar of the calendar is added.
-[FEATURE] When creating an event by dragging in the calendar, the "Edit Details"-Link leads to a page where the details can be added before actually creating the event.
-[BUGFIX] When editing a event, the start time cannot be set befor the end time anymore.
-[BUGFIX] Fixed some problems with Magic Quotes
-
-v0.1
-======
-Initial Release
\ No newline at end of file
diff --git a/dav/wdcal_cal_source_friendicaevents.inc.php b/dav/wdcal_cal_source_friendicaevents.inc.php
deleted file mode 100644 (file)
index ed44bf9..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-<?php
-
-class FriendicaCalSourceEvents extends AnimexxCalSource
-{
-
-       /**
-        * @return int
-        */
-       public static function getNamespace()
-       {
-               return CALDAV_NAMESPACE_FRIENDICA_NATIVE;
-       }
-
-       /**
-        * @param int $user
-        * @return array
-        */
-       public function getPermissionsCalendar($user)
-       {
-               if ($user == $this->calendarDb->uid) return array("read"=> true, "write"=> false);
-               return array("read"=> false, "write"=> false);
-       }
-
-       /**
-        * @param int $user
-        * @param string $item_uri
-        * @param string $recurrence_uri
-        * @param null|array $item_arr
-        * @return array
-        */
-       public function getPermissionsItem($user, $item_uri, $recurrence_uri, $item_arr = null)
-       {
-               $cal_perm = $this->getPermissionsCalendar($user);
-               if (!$cal_perm["read"]) return array("read"=> false, "write"=> false);
-               return array("read"=> true, "write"=> false);
-       }
-
-
-       /**
-        * @param string $uri
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        * @throws Sabre_DAV_Exception_MethodNotAllowed
-        */
-       public function updateItem($uri, $start, $end, $subject = "", $allday = false, $description = "", $location = "", $color = null, $timezone = "", $notification = true, $notification_type = null, $notification_value = null)
-       {
-               throw new Sabre_DAV_Exception_MethodNotAllowed();
-       }
-
-       /**
-        * @param array $start
-        * @param array $end
-        * @param string $subject
-        * @param bool $allday
-        * @param string $description
-        * @param string $location
-        * @param null $color
-        * @param string $timezone
-        * @param bool $notification
-        * @param null $notification_type
-        * @param null $notification_value
-        * @throws Sabre_DAV_Exception_MethodNotAllowed
-        * @return array|string
-        */
-       public function addItem($start, $end, $subject, $allday = false, $description = "", $location = "", $color = null,
-                                                       $timezone = "", $notification = true, $notification_type = null, $notification_value = null)
-       {
-               throw new Sabre_DAV_Exception_MethodNotAllowed();
-       }
-
-       /**
-        * @param array $row
-        * @return array
-        */
-       private function virtualData2wdcal($row) {
-               $end = wdcal_mySql2PhpTime($row["data_end"]);
-               if ($row["data_allday"]) $end--;
-               $start = wdcal_mySql2PhpTime($row["data_start"]);
-               $a = get_app();
-               $arr             = array(
-                       "uri"               => $row["data_uri"],
-                       "subject"           => escape_tags($row["data_subject"]),
-                       "start"             => $start,
-                       "end"               => $end,
-                       "is_allday"         => ($row["data_allday"] == 1),
-                       "is_moredays"       => (date("Ymd", $start) != date("Ymd", $end)),
-                       "is_recurring"      => ($row["data_type"] == "birthday"),
-                       "color"             => "#ff0000",
-                       "is_editable"       => false,
-                       "is_editable_quick" => false,
-                       "location"          => $row["data_location"],
-                       "attendees"         => '',
-                       "has_notification"  => false,
-                       "url_detail"        => $a->get_baseurl() . "/dav/wdcal/" . $row["data_uri"] . "/",
-                       "url_edit"          => "",
-                       "special_type"      => ($row["data_type"] == "birthday" ? "birthday" : ""),
-               );
-               return $arr;
-       }
-
-       /**
-        * @param string $sd
-        * @param string $ed
-        * @param string $base_path
-        * @return array
-        */
-       public function listItemsByRange($sd, $ed, $base_path)
-       {
-               $usr_id = IntVal($this->calendarDb->uid);
-
-               $evs =  FriendicaVirtualCalSourceBackend::getItemsByTime($usr_id, $this->namespace_id, $sd, $ed);
-               $events = array();
-               foreach ($evs as $row) $events[] = $this->virtualData2wdcal($row);
-
-               return $events;
-       }
-
-       /**
-        * @param string $uri
-        * @throws Sabre_DAV_Exception_MethodNotAllowed
-        * @return void
-        */
-       public function removeItem($uri) {
-               throw new Sabre_DAV_Exception_MethodNotAllowed();
-       }
-
-       /**
-        * @param string $uri
-        * @return array
-        */
-       public function getItemByUri($uri)
-       {
-               $usr_id = IntVal($this->calendarDb->uid);
-               $row = FriendicaVirtualCalSourceBackend::getItemsByUri($usr_id, $uri);
-               return $this->virtualData2wdcal($row);
-       }
-
-       /**
-        * @param string $uri
-        * @return string
-        */
-       public function getItemDetailRedirect($uri) {
-               $x = explode("@", $uri);
-               $y = explode("-", $x[0]);
-               $a = get_app();
-               if (count($y) != 3) {
-                       goaway($a->get_baseurl() . "/dav/wdcal/");
-                       killme();
-               }
-               $a = get_app();
-               $item = q("SELECT `id` FROM `item` WHERE `event-id` = %d AND `uid` = %d AND deleted = 0", IntVal($y[2]), $a->user["uid"]);
-               if (count($item) == 0) return "/events/";
-               return "/display/" . $a->user["nickname"] . "/" . IntVal($item[0]["id"]);
-       }
-}
diff --git a/group_text/group_text.css b/group_text/group_text.css
new file mode 100755 (executable)
index 0000000..4122b67
--- /dev/null
@@ -0,0 +1,14 @@
+
+
+
+#group_text-enable-label {
+       float: left;
+       width: 200px;
+       margin-bottom: 25px;
+}
+
+#group_text-checkbox {
+       float: left;
+}
+
+
diff --git a/group_text/group_text.php b/group_text/group_text.php
new file mode 100755 (executable)
index 0000000..151ff0a
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Name: Group Text
+ * Description: Disable images in group edit menu
+ * Version: 1.0
+ * Author: Thomas Willingham <https://kakste.com/profile/beardyunixer>
+ * 
+ *
+ */
+
+
+function group_text_install() {
+
+       register_hook('plugin_settings', 'addon/group_text/group_text.php', 'group_text_settings');
+       register_hook('plugin_settings_post', 'addon/group_text/group_text.php', 'group_text_settings_post');
+
+       logger("installed group_text");
+}
+
+
+function group_text_uninstall() {
+
+       unregister_hook('plugin_settings', 'addon/group_text/group_text.php', 'group_text_settings');
+       unregister_hook('plugin_settings_post', 'addon/group_text/group_text.php', 'group_text_settings_post');
+
+
+       logger("removed group_text");
+}
+
+
+
+/**
+ *
+ * Callback from the settings post function.
+ * $post contains the $_POST array.
+ * We will make sure we've got a valid user account
+ * and if so set our configuration setting for this person.
+ *
+ */
+
+function group_text_settings_post($a,$post) {
+       if(! local_user() || (! x($_POST,'group_text-submit')))
+               return;
+       set_pconfig(local_user(),'system','groupedit_image_limit',intval($_POST['group_text']));
+
+       info( t('Editplain settings updated.') . EOL);
+}
+
+
+/**
+ *
+ * Called from the Plugin Setting form. 
+ * Add our own settings info to the page.
+ *
+ */
+
+
+
+function group_text_settings(&$a,&$s) {
+
+       if(! local_user())
+               return;
+
+       /* Add our stylesheet to the page so we can make our settings look nice */
+
+       $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/group_text/group_text.css' . '" media="all" />' . "\r\n";
+
+       /* Get the current state of our config variable */
+
+       $enabled = get_pconfig(local_user(),'system','groupedit_image_limit');
+       $checked = (($enabled) ? ' checked="checked" ' : '');
+
+       /* Add some HTML to the existing form */
+
+       $s .= '<div class="settings-block">';
+       $s .= '<h3>' . t('Group Text') . '</h3>';
+       $s .= '<div id="group_text-enable-wrapper">';
+       $s .= '<label id="group_text-enable-label" for="group_text-checkbox">' . t('Use a text only (non-image) group selector in the "group edit" menu') . '</label>';
+       $s .= '<input id="group_text-checkbox" type="checkbox" name="group_text" value="1" ' . $checked . '/>';
+       $s .= '</div><div class="clear"></div>';
+
+       /* provide a submit button */
+
+       $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="group_text-submit" class="settings-submit" value="' . t('Submit') . '" /></div></div>';
+
+}
index a4e7199c970c66b26237c39e713b9b9425a327c0..9d038178beb8eca2ccf65011481e48702c5b559e 100755 (executable)
@@ -29,6 +29,7 @@ function impressum_footer($a, &$b) {
     $text = bbcode(get_config('impressum','footer_text'), true);
     if (! $text == '') {
         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'.$a->get_baseurl().'/addon/impressum/impressum.css" media="all" />';
+        $b .= '<div class="clear"></div>';
         $b .= '<div id="impressum_footer">'.$text.'</div>';
     }
 }
index 8d9a2203fc1b0b29d951461b132ad266a6927e75..65c52987b9209e2a7942baf62725781da2b4fe22 100644 (file)
Binary files a/infiniteimprobabilitydrive.tgz and b/infiniteimprobabilitydrive.tgz differ
index dce36924580f9bc539ac96d58149f1fe6e9439ba..5e46b3b7b6837b5df906896dbc208738ba932097 100644 (file)
@@ -3,7 +3,7 @@
 * Name: Infinite Improbability Drive
 * Description: Infinitely Improbably Find A Random User
 * Version: 1.0
-* Author: Thomas Willingham
+* Author: Thomas Willingham <https://kakste.com/profile/beardyunixer>
 */
 
 function infiniteimprobabilitydrive_install() {
diff --git a/irc.tgz b/irc.tgz
index a43cde7ede0e810152adb8fc0a320525d9235499..af216a43d261e98ae59ee889e003d2ae8af3a257 100644 (file)
Binary files a/irc.tgz and b/irc.tgz differ
index 50eddb8cc939f45e62ce7d862f09c263731a9a21..bd7444a9eb9bd414488df7f0ff8ad664e8030a1f 100644 (file)
@@ -110,7 +110,7 @@ function irc_content(&$a) {
   $o .= <<< EOT
 <h2>IRC chat</h2>
 <p><a href="http://tldp.org/HOWTO/IRC/beginners.html" target="_blank">A beginner's guide to using IRC. [en]</a></p>
-<iframe src="http://webchat.freenode.net?channels=$channels" width="600" height="600"></iframe>
+<iframe src="//webchat.freenode.net?channels=$channels" width="600" height="600"></iframe>
 EOT;
 
 return $o;
index 5d6f8446ad4595c62f371960d820e8f9697921d0..69c58e94c8930dede7471d822a07d754cb2ff365 100644 (file)
@@ -3,7 +3,7 @@
 /**
 * Name: jappixmini
 * Description: Provides a Facebook-like chat using Jappix Mini
-* Version: 1.0
+* Version: 1.0.1
 * Author: leberwurscht <leberwurscht@hoegners.de>
 *
 */
@@ -221,6 +221,8 @@ function jappixmini_settings(&$a, &$s) {
 
     $activate = get_pconfig(local_user(),'jappixmini','activate');
     $activate = intval($activate) ? ' checked="checked"' : '';
+    $dontinsertchat = get_pconfig(local_user(),'jappixmini','dontinsertchat');
+    $insertchat = !(intval($dontinsertchat) ? ' checked="checked"' : '');
 
     $username = get_pconfig(local_user(),'jappixmini','username');
     $username = htmlentities($username);
@@ -261,46 +263,49 @@ function jappixmini_settings(&$a, &$s) {
 
     $s .= '<div class="settings-block">';
 
-    $s .= '<h3>Jappix Mini addon settings</h3>';
+    $s .= '<h3>'.t('Jappix Mini addon settings').'</h3>';
     $s .= '<div>';
-    $s .= '<label for="jappixmini-activate">Activate addon</label>';
+    $s .= '<label for="jappixmini-activate">'.t('Activate addon').'</label>';
     $s .= ' <input id="jappixmini-activate" type="checkbox" name="jappixmini-activate" value="1"'.$activate.' />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-username">Jabber username</label>';
+    $s .= '<label for"jappixmini-dont-insertchat">'.t('Do <em>not</em> insert the Jappixmini Chat-Widget into the webinterface').'</label>';
+    $s .= '<input id="jappixmini-dont-insertchat" type="checkbox" name="jappixmini-dont-insertchat" value="1"'.$insertchat.' />';
+    $s .= '<br />';
+    $s .= '<label for="jappixmini-username">'.t('Jabber username').'</label>';
     $s .= ' <input id="jappixmini-username" type="text" name="jappixmini-username" value="'.$username.'" />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-server">Jabber server</label>';
+    $s .= '<label for="jappixmini-server">'.t('Jabber server').'</label>';
     $s .= ' <input id="jappixmini-server" type="text" name="jappixmini-server" value="'.$server.'" />';
     $s .= '<br />';
 
-    $s .= '<label for="jappixmini-bosh">Jabber BOSH host</label>';
+    $s .= '<label for="jappixmini-bosh">'.t('Jabber BOSH host').'</label>';
     $s .= ' <input id="jappixmini-bosh" type="text" name="jappixmini-bosh" value="'.$bosh.'" />';
     $s .= '<br />';
 
-    $s .= '<label for="jappixmini-password">Jabber password</label>';
+    $s .= '<label for="jappixmini-password">'.t('Jabber password').'</label>';
     $s .= ' <input type="hidden" id="jappixmini-password" name="jappixmini-encrypted-password" value="'.$password.'" />';
     $s .= ' <input id="jappixmini-clear-password" type="password" value="" onchange="jappixmini_set_password();" />';
     $s .= '<br />';
     $onchange = "document.getElementById('jappixmini-friendica-password').disabled = !this.checked;jappixmini_set_password();";
-    $s .= '<label for="jappixmini-encrypt">Encrypt Jabber password with Friendica password (recommended)</label>';
+    $s .= '<label for="jappixmini-encrypt">'.t('Encrypt Jabber password with Friendica password (recommended)').'</label>';
     $s .= ' <input id="jappixmini-encrypt" type="checkbox" name="jappixmini-encrypt" onchange="'.$onchange.'" value="1"'.$encrypt_checked.' />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-friendica-password">Friendica password</label>';
+    $s .= '<label for="jappixmini-friendica-password">'.t('Friendica password').'</label>';
     $s .= ' <input id="jappixmini-friendica-password" name="jappixmini-friendica-password" type="password" onchange="jappixmini_set_password();" value=""'.$encrypt_disabled.' />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-autoapprove">Approve subscription requests from Friendica contacts automatically</label>';
+    $s .= '<label for="jappixmini-autoapprove">'.t('Approve subscription requests from Friendica contacts automatically').'</label>';
     $s .= ' <input id="jappixmini-autoapprove" type="checkbox" name="jappixmini-autoapprove" value="1"'.$autoapprove.' />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-autosubscribe">Subscribe to Friendica contacts automatically</label>';
+    $s .= '<label for="jappixmini-autosubscribe">'.t('Subscribe to Friendica contacts automatically').'</label>';
     $s .= ' <input id="jappixmini-autosubscribe" type="checkbox" name="jappixmini-autosubscribe" value="1"'.$autosubscribe.' />';
     $s .= '<br />';
-    $s .= '<label for="jappixmini-purge">Purge internal list of jabber addresses of contacts</label>';
+    $s .= '<label for="jappixmini-purge">'.t('Purge internal list of jabber addresses of contacts').'</label>';
     $s .= ' <input id="jappixmini-purge" type="checkbox" name="jappixmini-purge" value="1" />';
     $s .= '<br />';
     if ($info_text) $s .= '<br />Configuration help:<p style="margin-left:2em;">'.$info_text.'</p>';
     $s .= '<br />Status:<p style="margin-left:2em;">Addon knows '.$address_cnt.' Jabber addresses of '.$contact_cnt.' Friendica contacts (takes some time, usually 10 minutes, to update).</p>';
     $s .= '<input type="submit" name="jappixmini-submit" value="' . t('Submit') . '" />';
-    $s .= ' <input type="button" value="Add contact" onclick="jappixmini_addon_subscribe();" />';
+    $s .= ' <input type="button" value="'.t('Add contact').'" onclick="jappixmini_addon_subscribe();" />';
     $s .= '</div>';
 
     $s .= '</div>';
@@ -379,6 +384,7 @@ function jappixmini_settings_post(&$a,&$b) {
                set_pconfig($uid,'jappixmini','autosubscribe',intval($b['jappixmini-autosubscribe']));
                set_pconfig($uid,'jappixmini','autoapprove',intval($b['jappixmini-autoapprove']));
                set_pconfig($uid,'jappixmini','activate',intval($b['jappixmini-activate']));
+               set_pconfig($uid,'jappixmini','dontinsertchat',intval($b['jappixmini-dont-insertchat']));
                set_pconfig($uid,'jappixmini','encrypt',$encrypt);
                info( 'Jappix Mini settings saved.' );
 
@@ -395,7 +401,8 @@ function jappixmini_script(&$a,&$s) {
     if(! local_user()) return;
 
     $activate = get_pconfig(local_user(),'jappixmini','activate');
-    if (!$activate) return;
+    $dontinsertchat = get_pconfig(local_user(), 'jappixmini','dontinsertchat');
+    if (!$activate or $dontinsertchat) return;
 
     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;g=mini.xml"></script>'."\r\n";
     $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/jappixmini/jappix/php/get.php?t=js&amp;f=presence.js~caps.js~name.js~roster.js"></script>'."\r\n";
index a00b7dccce8abd03d3dd5d1ad77300a052e1ddc6..4065a2b78bf1763de3026dd9f2bd2616e1c16fe8 100644 (file)
Binary files a/morechoice.tgz and b/morechoice.tgz differ
index bf97b76bc834a64b99ee83170a4f18a9b6c3afc2..d22ec3aaf1dbfe3ab7e8e7c629e3d4303d996fb9 100644 (file)
@@ -14,8 +14,6 @@ function morechoice_install() {
        register_hook('gender_selector', 'addon/morechoice/morechoice.php', 'morechoice_gender_selector');
        register_hook('sexpref_selector', 'addon/morechoice/morechoice.php', 'morechoice_sexpref_selector');
        register_hook('marital_selector', 'addon/morechoice/morechoice.php', 'morechoice_marital_selector');
-       register_hook('poke_verbs', 'addon/morechoice/morechoice.php', 'morechoice_poke_verbs');
-
 }
 
 
@@ -24,6 +22,8 @@ function morechoice_uninstall() {
        unregister_hook('gender_selector', 'addon/morechoice/morechoice.php', 'morechoice_gender_selector');
        unregister_hook('sexpref_selector', 'addon/morechoice/morechoice.php', 'morechoice_sexpref_selector');
        unregister_hook('marital_selector', 'addon/morechoice/morechoice.php', 'morechoice_marital_selector');
+
+// We need to leave this here for a while, because we now have a situation where people can end up with an orphaned hook.
        unregister_hook('poke_verbs', 'addon/morechoice/morechoice.php', 'morechoice_poke_verbs');
 
 }
@@ -123,11 +123,4 @@ function morechoice_marital_selector($a,&$b) {
                $b[] = 'Hurt in the past';
                $b[] = 'Wallowing in self-pity';
        }
-}
-
-function morechoice_poke_verbs($a,&$b) {
-       $b['bitchslap'] = array('bitchslapped', t('bitchslap'), t('bitchslapped'));
-       $b['shag'] = array('shag', t('shag'), t('shagged'));
-
-
 }
\ No newline at end of file
diff --git a/morepokes/morepokes.php b/morepokes/morepokes.php
new file mode 100644 (file)
index 0000000..bdbd7dc
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Name: More Pokes
+ * Description: Additional poke options
+ * Version: 1.0
+ * Author: Thomas Willingham <https://kakste.com/profile/beardyunixer>
+ *
+ */
+
+function morepokes_install() {
+         register_hook('poke_verbs', 'addon/morepokes/morepokes.php', 'morepokes_poke_verbs');
+}
+
+function morepokes_uninstall() {
+         unregister_hook('poke_verbs', 'addon/morepokes/morepokes.php', 'morepokes_poke_verbs');
+}
+
+function morepokes_poke_verbs($a,&$b) {
+       $b['bitchslap'] = array('bitchslapped', t('bitchslap'), t('bitchslapped'));
+       $b['shag'] = array('shag', t('shag'), t('shagged'));
+       $b['somethingobscenelybiological'] = array('something obscenely biological', t('do something obscenely biological to'), t('did something obscenely biological to'));
+       $b['newpokefeature'] = array('pointed out the new poke feature to', t('point out the new poke feature to'), t('pointed out the new poke feature to'));
+       $b['declareundyinglove'] = array('declared undying love for', t('declare undying love for'), t('declared undying love for'));
+       $b['setfireto'] = array('set fire to', t('set fire to'), t('set fire to'));
+       $b['patent'] = array('patented', t('patent'), t('patented'));
+       $b['strokebeard'] = array('stroked their beard at', t('stroke beard'), t('stroked their beard at'));
+       $b['bemoan'] = array('bemoaned the declining standards of modern secondary and tertiary education to', t('bemoan the declining standards of modern secondary and tertiary education to'), t('bemoans the declining standards of modern secondary and tertiary education to'));
+       $b['hugs'] = array('hugged', t('hug'), t('hugged'));
+       $b['kiss'] = array('kissed', t('kiss'), t('kissed'));
+       $b['raiseeyebrows'] = array('raised their eyebrows at', t('raise eyebrows at'), t('raised their eyebrows at'));
+       $b['insult'] = array('insulted', t('insult'), t('insulted'));
+       $b['praise'] = array('praised', t('praise'), t('praised'));
+       $b['bedubiousof'] = array('was dubious of', t('be dubious of'), t('was dubious of'));
+       $b['eat'] = array('ate', t('eat'), t('ate'));
+       $b['giggleandfawn'] = array('giggled and fawned at', t('giggle and fawn at'), t('giggled and fawned at'));
+       $b['doubt'] = array('doubted', t('doubt'), t('doubted'));
+       $b['glare'] = array('glared at', t('glare'), t('glared at'));
+;}
\ No newline at end of file
index c484aa03f77b7275c44b3b9185c3fe05735b888c..73870dc3f95647424b8b69a1b7b52eeb276cfd0a 100755 (executable)
Binary files a/nsfw.tgz and b/nsfw.tgz differ
index 29fced0a2dfdc6d21e0199391b5daf2713970d6a..34f4bd6b15ddd900a4859dfd3e6d6587ba6ee03a 100644 (file)
Binary files a/openstreetmap.tgz and b/openstreetmap.tgz differ
index 415e448d7d2ed7893863ddee613b88fbbb730b4e..fda29905d411821fb145ce80aae1c01767731ced 100755 (executable)
@@ -1,7 +1,7 @@
 <?php
 /**
  * Name: OpenStreetMap
- * Description: Use OpenStreetMap for displaying locations.
+ * Description: Use OpenStreetMap for displaying locations.  After activation the post location just beneath your avatar in your posts will link to openstreetmap.
  * Version: 1.1
  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
  * Author: Klaus Weidenbach
index 7e95737fddf2a8b250f93402fb8f9c9112d346cd..d300a3e2db7b5004167b96a86077e4e3aea349e0 100644 (file)
Binary files a/page.tgz and b/page.tgz differ
index a1fc2ecd22ca28e2fbd9e0bcaaee44c4ab9e3e37..080dd9e32c6bfd1513b54dcd98fa04ace138b5ed 100755 (executable)
Binary files a/piwik.tgz and b/piwik.tgz differ
index 3e0d718ffaa7c0cdc5330e3d89530bd36efff1cf..9ba15db63f9e7dd35445f8cae10d3ef2daad8038 100755 (executable)
@@ -65,9 +65,9 @@ function piwik_analytics($a,&$b) {
         */
        if ($async) {
          $a->page['htmlhead'] .= "<!-- Piwik --> <script type=\"text/javascript\">\r\nvar _paq = _paq || [];\r\n(function(){ var u=((\"https:\" == document.location.protocol) ? \"https://".$baseurl."\" : \"http://".$baseurl."\");\r\n_paq.push(['setSiteId', ".$siteid."]);\r\n_paq.push(['setTrackerUrl', u+'piwik.php']);\r\n_paq.push(['trackPageView']);\r\n_paq.push(['enableLinkTracking']);\r\nvar d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';\r\ng.defer=true; g.async=true; g.src=u+'piwik.js';\r\ns.parentNode.insertBefore(g,s); })();\r\n </script>\r\n<!-- End Piwik Code -->\r\n";
-         $b .= "<div id='piwik-code-block'> <!-- Piwik -->\r\n<noscript><p><img src=\"http://".$baseurl."piwik.php?idsite=".$siteid."\" style=\"border:0\" alt=\"\" /></p></noscript>\r\n <!-- End Piwik Tracking Tag --> </div>";
+         $b .= "<div id='piwik-code-block'> <!-- Piwik -->\r\n<noscript><p><img src=\"//".$baseurl."piwik.php?idsite=".$siteid."\" style=\"border:0\" alt=\"\" /></p></noscript>\r\n <!-- End Piwik Tracking Tag --> </div>";
        } else {
-               $b .= "<div id='piwik-code-block'> <!-- Piwik -->\r\n <script type=\"text/javascript\">\r\n var pkBaseURL = ((\"https:\" == document.location.protocol) ? \"https://".$baseurl."\" : \"http://".$baseurl."\");\r\n document.write(unescape(\"%3Cscript src='\" + pkBaseURL + \"piwik.js' type='text/javascript'%3E%3C/script%3E\"));\r\n </script>\r\n<script type=\"text/javascript\">\r\n try {\r\n var piwikTracker = Piwik.getTracker(pkBaseURL + \"piwik.php\", ".$siteid.");\r\n piwikTracker.trackPageView();\r\n piwikTracker.enableLinkTracking();\r\n }\r\n catch( err ) {}\r\n </script>\r\n<noscript><p><img src=\"http://".$baseurl."piwik.php?idsite=".$siteid."\" style=\"border:0\" alt=\"\" /></p></noscript>\r\n <!-- End Piwik Tracking Tag --> </div>";
+               $b .= "<div id='piwik-code-block'> <!-- Piwik -->\r\n <script type=\"text/javascript\">\r\n var pkBaseURL = ((\"https:\" == document.location.protocol) ? \"https://".$baseurl."\" : \"http://".$baseurl."\");\r\n document.write(unescape(\"%3Cscript src='\" + pkBaseURL + \"piwik.js' type='text/javascript'%3E%3C/script%3E\"));\r\n </script>\r\n<script type=\"text/javascript\">\r\n try {\r\n var piwikTracker = Piwik.getTracker(pkBaseURL + \"piwik.php\", ".$siteid.");\r\n piwikTracker.trackPageView();\r\n piwikTracker.enableLinkTracking();\r\n }\r\n catch( err ) {}\r\n </script>\r\n<noscript><p><img src=\"//".$baseurl."piwik.php?idsite=".$siteid."\" style=\"border:0\" alt=\"\" /></p></noscript>\r\n <!-- End Piwik Tracking Tag --> </div>";
        }
 
        /*
index 9712a0677df36f61d419f37a8587024a2a9550ce..b4c100804d3eac95cc71393cd27162f5ba8361c0 100644 (file)
Binary files a/privacy_image_cache.tgz and b/privacy_image_cache.tgz differ
index f0d8e39ac55b24b2a5d4b31a1900d61911cc2bcc..f4ada61e1db4ca2684530fd5fd4269d74e73e17a 100644 (file)
@@ -34,6 +34,9 @@ function privacy_image_cache_module() {}
 function privacy_image_cache_init() {
        global $a;
 
+       if ($a->config["system"]["db_log"] != "")
+               $stamp1 = microtime(true);
+
        if(function_exists('header_remove')) {
                header_remove('Pragma');
                header_remove('pragma');
@@ -45,7 +48,7 @@ function privacy_image_cache_init() {
 
        $cache = get_config('system','itemcache');
        if (($cache != '') and is_dir($cache)) {
-               $cachefile = $cache."/".hash("md5", $urlhash);
+               $cachefile = $cache."/".hash("md5", $_REQUEST['url']);
                if (file_exists($cachefile)) {
                        $img_str = file_get_contents($cachefile);
 
@@ -57,18 +60,34 @@ function privacy_image_cache_init() {
 
                        echo $img_str;
 
+                       if ($a->config["system"]["db_log"] != "") {
+                               $stamp2 = microtime(true);
+                               $duration = round($stamp2-$stamp1, 3);
+                               if ($duration > $a->config["system"]["db_loglimit"])
+                                       @file_put_contents($a->config["system"]["db_log"], $duration."\t".strlen($img_str)."\t".$_REQUEST['url']."\n", FILE_APPEND);
+                       }
+
                        killme();
                }
        }
 
+       require_once("Photo.php");
+
        $r = q("SELECT * FROM `photo` WHERE `resource-id` in ('%s', '%s') LIMIT 1", $urlhash, $urlhash2);
        if (count($r)) {
                $img_str = $r[0]['data'];
                $mime = $r[0]["desc"];
                if ($mime == "") $mime = "image/jpeg";
-       } else {
-               require_once("Photo.php");
 
+               // Test
+               //if ($mime == "image/jpeg") {
+               //      $img = new Photo($img_str);
+               //      if($img->is_valid()) {
+               //              $img->scaleImage(1000);
+               //              $img_str = $img->imageString();
+               //      }
+               //}
+       } else {
                // It shouldn't happen but it does - spaces in URL
                $_REQUEST['url'] = str_replace(" ", "+", $_REQUEST['url']);
 
@@ -110,6 +129,7 @@ function privacy_image_cache_init() {
                        $img = new Photo($img_str);
                        if($img->is_valid()) {
                                $img->store(0, 0, $urlhash, $_REQUEST['url'], '', 100);
+                               //$img->scaleImage(1000); // Test
                                $img_str = $img->imageString();
                        }
                        $mime = "image/jpeg";
@@ -117,7 +137,7 @@ function privacy_image_cache_init() {
        }
 
        // Writing in cachefile
-       if (isset($cachefile) && $cachefile != '')
+       if (isset($cachefile) && ($cachefile != '') and (file_exists($cachefile)) and (exif_imagetype($cachefile) > 0))
                file_put_contents($cachefile, $img_str);
 
        header("Content-type: $mime");
@@ -126,6 +146,13 @@ function privacy_image_cache_init() {
 
        echo $img_str;
 
+       if ($a->config["system"]["db_log"] != "") {
+               $stamp2 = microtime(true);
+               $duration = round($stamp2-$stamp1, 3);
+               if ($duration > $a->config["system"]["db_loglimit"])
+                       @file_put_contents($a->config["system"]["db_log"], $duration."\t".strlen($img_str)."\t".$_REQUEST['url']."\n", FILE_APPEND);
+       }
+
        killme();
 }
 
index 394a99fda47e56a33721ca89057046d5b685d71a..3b6ac194f028355bfa342a2616b9749b418558c2 100644 (file)
Binary files a/showmore.tgz and b/showmore.tgz differ
index 096fd3f704b69e3f825daeffdc84048967e4beb0..1f40b027be3b7da947e3905e8a9a817ffd176c7a 100755 (executable)
@@ -69,6 +69,11 @@ function showmore_addon_settings_post(&$a,&$b) {
 function get_body_length($body) {
        $string = trim($body);
 
+       // DomDocument doesn't like empty strings
+       if(! strlen($string)) {
+               return 0;
+       }
+
        // We need to get rid of hidden tags (display: none)
 
        // Get rid of the warning. It would be better to have some valid html as input
index a9fe979675c740aee6ffd8113ff5bda8eaa9a505..c171f2c38aa190988fc5792d04fe3fb5b71df60e 100755 (executable)
Binary files a/statusnet.tgz and b/statusnet.tgz differ
index b433f57b2ae9ff6c3c7467662ca98ecd1b762859..46b3f03f520fa3fab23581da76839430bf54b10b 100755 (executable)
@@ -460,9 +460,9 @@ function statusnet_post_hook(&$a,&$b) {
                        // recycle 1
                        $recycle = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8');
                        $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
-                       // recycle 2
-                       //$recycle = html_entity_decode("&#x267B; ", ENT_QUOTES, 'UTF-8');
-                       //$tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', 'RT @$2:', $tmp);
+                       // recycle 2 (test)
+                       $recycle = html_entity_decode("&#x25CC; ", ENT_QUOTES, 'UTF-8');
+                       $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
                 }
                 // preserve links to webpages
                 $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/url\]/i', '$2 $1', $tmp);
index ec5d55d5c48870e632a5d030297475f271076902..beeea2bee46fb21069de6b6402035d4e3bf71511 100755 (executable)
Binary files a/twitter.tgz and b/twitter.tgz differ
index d55e3b73ca92cacb3456e59033df3a5011a080cc..5fd053fa7f95d7b1b09e66bd363fd04ad274dcfe 100755 (executable)
@@ -326,9 +326,9 @@ function twitter_post_hook(&$a,&$b) {
                        // recycle 1
                        $recycle = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8');
                        $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
-                       // recycle 2
-                       //$recycle = html_entity_decode("&#x267B; ", ENT_QUOTES, 'UTF-8');
-                       //$tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', 'RT @$2:', $tmp);
+                       // recycle 2 (Test)
+                       $recycle = html_entity_decode("&#x25CC; ", ENT_QUOTES, 'UTF-8');
+                       $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
                 }
                 $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/url\]/i', '$2 $1', $tmp);
                 $tmp = preg_replace( '/\[bookmark\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/bookmark\]/i', '$2 $1', $tmp);