]> git.mxchange.org Git - friendica-addons.git/commitdiff
Merge remote branch 'upstream/master'
authorMichael Vogel <icarus@dabo.de>
Wed, 1 Aug 2012 22:20:44 +0000 (00:20 +0200)
committerMichael Vogel <icarus@dabo.de>
Wed, 1 Aug 2012 22:20:44 +0000 (00:20 +0200)
149 files changed:
dav/Changelog.txt [new file with mode: 0644]
dav/README.md
dav/SabreDAV/ChangeLog
dav/SabreDAV/README.md [new file with mode: 0644]
dav/SabreDAV/bin/migrateto17.php
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/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/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
dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php
dav/SabreDAV/lib/Sabre/VObject/Node.php
dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php [changed mode: 0755->0644]
dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php
dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php [new file with mode: 0644]
dav/SabreDAV/lib/Sabre/VObject/Version.php
dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php [deleted file]
dav/SabreDAV/lib/Sabre/VObject/includes.php
dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php
dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php
dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php
dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.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/VEventTest.php
dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php
dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php
dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php
dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php [new file with mode: 0644]
dav/calendar.friendica.fnk.php
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_virtual.inc.php [new file with mode: 0644]
dav/common/dav_carddav_backend_std.inc.php
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
dav/dav.php
dav/dav_caldav_backend_friendica.inc.php [deleted file]
dav/dav_caldav_backend_virtual_friendica.inc.php [new file with mode: 0644]
dav/dav_carddav_backend_friendica_community.inc.php [deleted file]
dav/dav_carddav_backend_virtual_friendica.inc.php [new file with mode: 0644]
dav/dav_friendica_auth.inc.php
dav/dav_friendica_principal.inc.php
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
dav/main.php
dav/virtual_cal_source_friendica.inc.php [deleted file]
dav/wdcal.css
dav/wdcal/Changelog.txt [deleted file]
dav/wdcal_cal_source_friendicaevents.inc.php [deleted file]
facebook.tgz
group_text/group_text.css [new file with mode: 0755]
group_text/group_text.php [new file with mode: 0755]
infiniteimprobabilitydrive.tgz
infiniteimprobabilitydrive/infiniteimprobabilitydrive.php
libertree.tgz
morechoice.tgz
morechoice/morechoice.php
morepokes/morepokes.php [new file with mode: 0644]
nsfw.tgz
nsfw/nsfw.php
openstreetmap.tgz
openstreetmap/openstreetmap.php
page.tgz
privacy_image_cache.tgz
showmore.tgz
showmore/showmore.php
statusnet.tgz
tumblr.tgz
twitter.tgz
twitter/twitter.php

diff --git a/dav/Changelog.txt b/dav/Changelog.txt
new file mode 100644 (file)
index 0000000..90117b7
--- /dev/null
@@ -0,0 +1,18 @@
+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
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..1199ae766e9f8cf9db45c7397e21fa4a708ef5ce 100644 (file)
@@ -1,31 +1,63 @@
 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).
+       * 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: Workaround for 10.8 Mountain Lion vCards, as it needs \r line
+         endings to parse them correctly.
+       * 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.4-stable (2012-??-??)
+       * 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.
+
+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 +75,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 {
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..480e6329fe5377b87079e45011f003242cd37a0d 100644 (file)
@@ -3,45 +3,15 @@
 /**
  * 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 +55,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.
      *
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..105ebd338f3601f652157dd831de79946b0392f9 100644 (file)
@@ -16,8 +16,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
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..098edcccaca6451fcbef2269962b9b304612fa08 100644 (file)
@@ -68,7 +68,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:');
 
     }
 
index 4bcd32cdf885c55af54ee023acc232506bd79143..8f674840e8780e441fac72019cb36396627fc855 100644 (file)
@@ -304,28 +304,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
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..0b0978f14a7cbe11ef9dc73653bb2079429d2c85 100644 (file)
@@ -162,6 +162,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 +173,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 +198,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 +385,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 +442,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) {
@@ -648,7 +676,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 +696,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 +748,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)) {
@@ -704,6 +775,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 +791,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 +835,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 +844,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);
@@ -813,9 +892,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 +905,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
+     * @return array
      */
     protected function iMIPMessage($originator, array $recipients, Sabre_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 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..6335e2f274b9fe5278b5571ed5bb5a3cecc7aaf7 100644 (file)
@@ -154,8 +154,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 +269,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) {
@@ -358,6 +357,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.');
+        }
+
     }
 
 
@@ -440,6 +443,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
 
         $vcard = Sabre_VObject_Reader::read($vcardData);
 
+        if (!$filters) return true;
+
         foreach($filters as $filter) {
 
             $isDefined = isset($vcard->{$filter['name']});
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
index b78a26133fa19e1f9e9dc19c333b7744188da190..ced5938488fcfc5c871e23f58385618be772e58a 100644 (file)
@@ -30,7 +30,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element {
     public $children = array();
 
     /**
-     * If coponents are added to this map, they will be automatically mapped
+     * 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.
      *
@@ -94,40 +94,54 @@ class Sabre_VObject_Component extends Sabre_VObject_Element {
          *
          * This is solely used by the childrenSort method.
          *
-         * A higher score means the item will be higher in the list
+         * 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 Sabre_VObject_Node $n
+         * @param int $key
+         * @param Sabre_VObject $array
          * @return int
          */
-        $sortScore = function($n) {
+        $sortScore = function($key, $array) {
 
-            if ($n instanceof Sabre_VObject_Component) {
+            if ($array[$key] instanceof Sabre_VObject_Component) {
                 // We want to encode VTIMEZONE first, this is a personal
                 // preference.
-                if ($n->name === 'VTIMEZONE') {
-                    return 1;
+                if ($array[$key]->name === 'VTIMEZONE') {
+                    $score=300000000;
+                    return $score+$key;
                 } else {
-                    return 0;
+                    $score=400000000;
+                    return $score+$key;
                 }
             } else {
+                // Properties get encoded first
                 // VCARD version 4.0 wants the VERSION property to appear first
-                if ($n->name === 'VERSION') {
-                    return 3;
-                } else {
-                    return 2;
+                if ($array[$key] instanceof Sabre_VObject_Property) {
+                    if ($array[$key]->name === 'VERSION') {
+                        $score=100000000;
+                        return $score+$key;
+                    } else {
+                        // All other properties
+                        $score=200000000;
+                        return $score+$key;
+                    }
                 }
             }
+            next($children);
 
         };
 
-        usort($this->children, function($a, $b) use ($sortScore) {
+        $tmp = $this->children;
+        uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
 
-            $sA = $sortScore($a);
-            $sB = $sortScore($b);
+            $sA = $sortScore($a, $tmp);
+            $sB = $sortScore($b, $tmp);
 
             if ($sA === $sB) return 0;
 
-            return ($sA > $sB) ? -1 : 1;
+            return ($sA < $sB) ? -1 : 1;
 
         });
 
@@ -250,6 +264,27 @@ class Sabre_VObject_Component extends Sabre_VObject_Element {
 
     }
 
+    /**
+     * 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() {
+
+        $result = array();
+        foreach($this->children as $child) {
+            $result = array_merge($result, $child->validate());
+        }
+        return $result;
+
+    }
+
     /* Magic property accessors {{{ */
 
     /**
index f3be29afdbb492c16b7fe28f0e04888c3291fd43..35dd90f2305a9f0b93fc6ab8d569c88801c5bc6e 100644 (file)
@@ -129,5 +129,110 @@ class Sabre_VObject_Component_VCalendar extends Sabre_VObject_Component {
 
     } 
 
+    /**
+     * 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 Sabre_VObject_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 Sabre_VObject_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()
+        );
+
+    }
+
 }
 
index 7701309be659c97af676c651836aeb8508ce06e6..6c8319f760eb7bc49c4b13524980dde43c127582 100644 (file)
@@ -32,6 +32,23 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou
      */
     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 */
 
     /**
old mode 100755 (executable)
new mode 100644 (file)
index fe2372c..ff2c867
@@ -204,49 +204,17 @@ class Sabre_VObject_Property_DateTime extends Sabre_VObject_Property {
             );
         }
 
-        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()); 
-            }
+        // 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 = Sabre_VObject_TimeZoneUtil::getTimeZone((string)$tzid, $root);
+        } else {
+            $tz = Sabre_VObject_TimeZoneUtil::getTimeZone((string)$tzid);
+        }
+
         $dt = new DateTime($dateStr, $tz);
         $dt->setTimeZone($tz);
 
index 39d1b69907c859221a7f65e5fb8b1967d9040436..7ccd2049beaf862f940c4899ca8022fe1fc94f28 100644 (file)
@@ -337,6 +337,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator {
             $this->endDate = clone $this->startDate;
             if (isset($this->baseEvent->DURATION)) {
                 $this->endDate->add(Sabre_VObject_DateTimeParser::parse($this->baseEvent->DURATION->value));
+            } elseif ($this->baseEvent->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) {
+                $this->endDate->modify('+1 day');
             }
         }
         $this->currentDate = clone $this->startDate;
@@ -565,7 +567,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator {
      */
     public function fastForward(DateTime $dt) {
 
-        while($this->valid() && $this->getDTEnd() < $dt) {
+        while($this->valid() && $this->getDTEnd() <= $dt) {
             $this->next();
         }
 
@@ -838,9 +840,40 @@ class Sabre_VObject_RecurrenceIterator implements Iterator {
      */
     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');
@@ -892,8 +925,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator {
 
         } else {
 
-            // no byDay or byMonthDay, so we can just loop through the
-            // months.
+            // These are the 'byMonth' rules, if there are no byDay or
+            // byMonthDay sub-rules.
             do {
 
                 $currentMonth++;
@@ -903,6 +936,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator {
                 }
             } while (!in_array($currentMonth, $this->byMonth));
             $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
             return;
 
         }
diff --git a/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php b/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php
new file mode 100644 (file)
index 0000000..276288a
--- /dev/null
@@ -0,0 +1,351 @@
+<?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)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_VObject_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, Sabre_VObject_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());
+
+
+    }
+
+
+}
index 2617c7b129d620196f425083b2b149b11c5327f9..9ee03d871181899be61043c38e056978d8d082f1 100644 (file)
@@ -14,7 +14,7 @@ class Sabre_VObject_Version {
     /**
      * Full version number
      */
-    const VERSION = '1.3.3';
+    const VERSION = '1.3.4';
 
     /**
      * Stability : alpha, beta, 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;
-    }
-}
index e7f57a06f6a70630a0234ed88694dcdcd73d1486..76497d6ffe20bcdf5dce941243419501141fde2a 100644 (file)
@@ -24,8 +24,8 @@ include __DIR__ . '/Parameter.php';
 include __DIR__ . '/ParseException.php';
 include __DIR__ . '/Reader.php';
 include __DIR__ . '/RecurrenceIterator.php';
+include __DIR__ . '/TimeZoneUtil.php';
 include __DIR__ . '/Version.php';
-include __DIR__ . '/WindowsTimezoneMap.php';
 include __DIR__ . '/Element.php';
 include __DIR__ . '/Property.php';
 include __DIR__ . '/Component.php';
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 b75c398ab232880e9acd1f92812aae31fe76e41a..18c8330db203d1e31326a4d1ce6476203cb6388c 100644 (file)
@@ -343,6 +343,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 +612,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 +757,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 137b7d3ca0bb3a570979885a03479f2f94833966..d23b81231b2896d2401b18302d6c096056e2407f 100644 (file)
@@ -55,6 +55,56 @@ class Sabre_CalDAV_ICSExportPluginTest extends PHPUnit_Framework_TestCase {
         $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 = Sabre_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));
 
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/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(
index a5a855f60214ad0bbf22aa7c1bdd84d2202e74be..90579cb41a86093e3c847bdc2af317e738253dbe 100644 (file)
@@ -53,9 +53,14 @@ class Sabre_VObject_Component_VEventTest extends PHPUnit_Framework_TestCase {
         $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);
 
+        // 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;
 
     }
index 71b33c079a2cdde5bdbe8a227b896e1000fe9243..42f836dbd0d282269640e53bd622ca24aeab8a96 100644 (file)
@@ -340,11 +340,14 @@ class Sabre_VObject_ComponentTest extends PHPUnit_Framework_TestCase {
             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());
+
+        $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 testSerializeOrder() {
+    function testSerializeOrderCompAndProp() {
         
         $comp = new Sabre_VObject_Component('VCALENDAR');
         $comp->add(new Sabre_VObject_Component('VEVENT'));
@@ -358,4 +361,30 @@ class Sabre_VObject_ComponentTest extends PHPUnit_Framework_TestCase {
 
     }
 
+    function testAnotherSerializeOrderProp() {
+
+        $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
+
+        $comp = new Sabre_VObject_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);
+
+    }
+
 }
index a004eb08181f11b047323319ffe183f93e6e630b..f5136be12b4aeb847eb6f995fa1d67cd75e9ff59 100644 (file)
@@ -6,9 +6,9 @@ class Sabre_VObject_Issue154Test extends PHPUnit_Framework_TestCase {
 
         $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);
+        $vcard->UID = 'foo-bar';
 
         $result = $vcard->serialize();
         $expected = array(
index d1ef2da6048efd7ce7b8f6e9d43fbc8d9352dce3..0bb42bb87342519e533ed7a6b57139abab775bcb 100644 (file)
@@ -707,6 +707,51 @@ class Sabre_VObject_RecurrenceIteratorTest extends PHPUnit_Framework_TestCase {
 
     }
 
+    /**
+     * @depends testValues
+     */
+    function testYearlyLeapYear() {
+
+        $ev = new Sabre_VObject_Component('VEVENT');
+        $ev->UID = 'bla';
+        $ev->RRULE = 'FREQ=YEARLY;COUNT=3';
+        $dtStart = new Sabre_VObject_Property_DateTime('DTSTART');
+        $dtStart->setDateTime(new DateTime('2012-02-29'),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->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
      */
diff --git a/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php b/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php
new file mode 100644 (file)
index 0000000..be8cd49
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+
+class Sabre_VObject_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);
+            },
+            Sabre_VObject_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 = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_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 = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_Reader::read($vobj));
+
+        $this->assertEquals(new DateTimeZone(date_default_timezone_get()), $tz);
+
+    }
+
+    function testWindowsTimeZone() {
+
+        $tz = Sabre_VObject_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 = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_Reader::read($vobj));
+
+        $this->assertEquals(new DateTimeZone(date_default_timezone_get()), $tz);
+
+    }
+
+}
index af4a0175f5ed88900d264b6a9544347d69ee9494..fff3447e47f775fbbf5961f9ffde3327a2a3793d 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 
-$a = get_app();
-$uri = parse_url($a->get_baseurl());
+$a    = get_app();
+$uri  = parse_url($a->get_baseurl());
 $path = "/";
-if (strlen($uri["path"]) > 1) {
+if (isset($uri["path"]) && strlen($uri["path"]) > 1) {
        $path = $uri["path"] . "/";
 }
 
@@ -12,33 +12,36 @@ 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("CALDAV_FRIENDICA_MINE", "friendica-mine");
+define("CALDAV_FRIENDICA_CONTACTS", "friendica-contacts");
+
+$GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"] = array(CALDAV_FRIENDICA_MINE, CALDAV_FRIENDICA_CONTACTS);
 
 define("CARDDAV_NAMESPACE_COMMUNITYCONTACTS", 1);
 define("CARDDAV_NAMESPACE_PHONECONTACTS", 2);
 
-define("CALDAV_DB_VERSION", 1);
+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()
+{
+       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;
+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;
 }
 
 
@@ -48,7 +51,7 @@ function debug_time() {
  */
 function dav_compat_username2id($username = "")
 {
-       $x = q("SELECT `uid` FROM user WHERE nickname='%s' AND account_removed = 0 AND account_expired = 0", dbesc($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;
 }
@@ -59,7 +62,7 @@ function dav_compat_username2id($username = "")
  */
 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));
+       $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 "";
 }
@@ -67,7 +70,8 @@ function dav_compat_id2username($id = 0)
 /**
  * @return int
  */
-function dav_compat_get_curr_user_id() {
+function dav_compat_get_curr_user_id()
+{
        $a = get_app();
        return IntVal($a->user["uid"]);
 }
@@ -86,13 +90,38 @@ function dav_compat_principal2uid($principalUri = "")
        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 (x($_REQUEST, $name)) return $_REQUEST[$name];
+function dav_compat_getRequestVar($name = "")
+{
+       if (isset($_REQUEST[$name])) return $_REQUEST[$name];
        else return null;
 }
 
@@ -108,28 +137,73 @@ function dav_compat_parse_text_serverside($text)
 /**
  * @param string $uri
  */
-function dav_compat_redirect($uri = "") {
+function dav_compat_redirect($uri = "")
+{
        goaway($uri);
 }
 
+
+/**
+ * @return null|int
+ */
+function dav_compat_get_max_private_calendars()
+{
+       return null;
+}
+
 /**
- * @param int $user_id
  * @param int $namespace
  * @param int $namespace_id
- * @return AnimexxCalSource
+ * @param string $uri
+ * @param array $calendar
+ * @return Sabre_CalDAV_Backend_Common
  * @throws Exception
  */
-function wdcal_calendar_factory($user_id, $namespace, $namespace_id)
+function wdcal_calendar_factory($namespace, $namespace_id, $uri, $calendar = null)
 {
        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);
+                       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;
+}
 
 /**
  */
@@ -138,27 +212,9 @@ 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)
-               );
-       }
+       $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;
 
-       $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)
-               );
-       }
+       $stms = wdcal_create_std_calendars_get_statements($a->user["uid"]);
+       foreach ($stms as $stmt) q($stmt);
 }
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..1bfe97e2fd20b09579fd19f1434b48b862bd20f3 100644 (file)
@@ -1,6 +1,15 @@
 <?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,7 +92,7 @@ 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 */
@@ -136,41 +145,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 +187,192 @@ 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));
-               }
+       $caldavBackend_std       = Sabre_CalDAV_Backend_Private::getInstance();
+       $caldavBackend_community = Sabre_CalDAV_Backend_Friendica::getInstance();
 
-               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);
-                       }
-               }
-       }
+       return new Sabre_CalDAV_AnimexxCalendarRootNode(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), array(
+               $caldavBackend_std,
+               $caldavBackend_community,
+       ));
 }
 
-
 /**
- *
+ * @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);
-               }
-       }
-}
+       $carddavBackend_std       = Sabre_CardDAV_Backend_Std::getInstance();
+       $carddavBackend_community = Sabre_CardDAV_Backend_FriendicaCommunity::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(), array(
+               $carddavBackend_std,
+               $carddavBackend_community,
+       ));
 }
 
 
 /**
- * @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);
+
+       $server->setBaseUri(CALDAV_URL_PREFIX);
+
+       $authPlugin = new Sabre_DAV_Auth_Plugin(Sabre_DAV_Auth_Backend_Std::getInstance(), 'SabreDAV');
+       $server->addPlugin($authPlugin);
+
+       $aclPlugin                      = new Sabre_DAVACL_Plugin_Friendica();
+       $aclPlugin->defaultUsernamePath = "principals/users";
+       $server->addPlugin($aclPlugin);
+
+       if ($needs_caldav) {
+               $caldavPlugin = new Sabre_CalDAV_Plugin();
+               $server->addPlugin($caldavPlugin);
+       }
+       if ($needs_carddav) {
+               $carddavPlugin = new Sabre_CardDAV_Plugin();
+               $server->addPlugin($carddavPlugin);
+       }
+
+       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")) {
+               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);
-
-       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;
-       }
-
-       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);
-               }
+       $calendars = dav_get_current_user_calendars($server, $with_privilege);
 
-       } 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);
-               }
+       $calendar = null;
+       foreach ($calendars as $cal) {
+               $prop = $cal->getProperties(array("id"));
+               if (isset($prop["id"]) && $prop["id"] == $id) $calendar = $cal;
        }
+
+       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;
-       }
+       $a = get_app();
+       if ($uid == "") $uid = uniqid();
+       return Sabre_VObject_Reader::read("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\nBEGIN:VEVENT\r\nUID:" . $uid . "@" . $a->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..f822479
--- /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..99fcb6c102c835bbf3920ba3ff39f3aed0197b2f 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..5356280
--- /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",
+                       );
+                       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_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,
+                       ),
+
+               );
+
+       }
+}
index c63a74f30aaeac0baf6cad3f6f4fd6fa47368f1f..e257d156bdb4f448d06f2f1c72ad6ff3d56cfd31 100644 (file)
 class Sabre_CardDAV_Backend_Std extends Sabre_CardDAV_Backend_Abstract
 {
 
+       /**
+        * @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
         */
index 8ad8da73e27c140f7fd09b77f692c1f3048e7245..62837dc54d03665039151648c39883cf982455fd 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');
 
     }
 
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..9233da6
--- /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..dac4936
--- /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
index 06e3abc52066bd3c5df06339a62d62b8cf5e4d48..8cdd9b9b19bfc5a13eebeffe34edec5541a8b46d 100644 (file)
@@ -2,40 +2,78 @@
 
 
 /**
+ * @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`";
+
+       }
+
+       return $stms;
+}
+
+/**
+ * @param array $except
  * @return array
  */
-function dav_get_create_statements() {
+function dav_get_create_statements($except = array())
+{
        $arr = array();
 
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` (
-               `uid` int(11) NOT NULL,
+       if (!in_array("dav_addressbooks_community", $except)) $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";
+) ENGINE=MyISAM DEFAULT CHARSET=utf8";
 
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_phone` (
-               `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+       if (!in_array("dav_addressbooks_phone", $except)) $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,
+  `principaluri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `displayname` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `uri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
+  `description` text CHARACTER SET utf8 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,
+       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,
@@ -46,124 +84,111 @@ function dav_get_create_statements() {
   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,
+       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`,`namespace`,`namespace_id`),
-  KEY `namespace` (`namespace`,`namespace_id`)
+  UNIQUE KEY `uri` (`uri`),
+  KEY `calendar_id` (`calendar_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8";
 
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendars` (
-               `namespace` mediumint(9) NOT NULL,
+       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,
-  `uid` mediumint(8) unsigned NOT NULL,
   `calendarorder` int(11) NOT NULL DEFAULT '1',
-  `calendarcolor` varchar(20) NOT NULL DEFAULT '#5858FF',
+  `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 (`namespace`,`namespace_id`),
-  KEY `uid` (`uid`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8";
+  PRIMARY KEY (`id`),
+  UNIQUE KEY (`namespace` , `namespace_id` , `uri`),
+  KEY `uri` (`uri`)
+) 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',
+       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_subject` varchar(1000) NOT NULL,
+  `data_summary` 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,
+  `calendardata` text NOT NULL,
+  `size` int(11) NOT NULL,
+  `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";
+  KEY `data_uri` (`data_uri`),
+  KEY `ref_type` (`calendar_id`,`data_end`)
+) ENGINE=MyISAM  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";
 
-       $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cards` (
-               `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+       if (!in_array("dav_cards", $except)) $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,
+  `carddata` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
+  `uri` varchar(100) CHARACTER SET utf8 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,
+  `etag` varchar(15) CHARACTER SET utf8 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,
+) 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,
-  `IsAllDayEvent` smallint(6) NOT NULL,
-  `Color` varchar(20) DEFAULT NULL,
-  `RecurringRule` varchar(500) 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 `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,
+  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,
-  `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";
+  KEY `calendar_id` (`calendar_id`,`calendarobject_id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8";
 
        return $arr;
 }
@@ -171,11 +196,13 @@ function dav_get_create_statements() {
 /**
  * @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
+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
+       if (count($x) == 12) return 0; // Correct
+       return -2; // Unknown version
 }
 
 
@@ -184,7 +211,7 @@ function dav_check_tables() {
  */
 function dav_create_tables()
 {
-       $stms = dav_get_create_statements();
+       $stms   = dav_get_create_statements();
        $errors = array();
 
        global $db;
@@ -193,7 +220,25 @@ function dav_create_tables()
                if ($db->error) $errors[] = $db->error;
        }
 
-       if (count($errors) == 0) set_config("dav", "db_version", CALDAV_DB_VERSION);
+       return $errors;
+}
+
+/**
+ * @return array
+ */
+function dav_upgrade_tables()
+{
+       $ver = dav_check_tables();
+       if (!in_array($ver, array(1) )) return array("Unknown error");
+
+       $stms   = dav_get_update_statements($ver);
+       $errors = array();
+
+       global $db;
+       foreach ($stms as $st) {
+               $db->q($st);
+               if ($db->error) $errors[] = $db->error;
+       }
 
        return $errors;
 }
\ No newline at end of file
index 1f35b06ad5422c4f95211dfb364aa07acd27f590..05916d01ff59dc6ccf1dc1e7728729910f107f85 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * 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.2.0
  * Author: Tobias Hößl <https://github.com/CatoTH/>
  */
 
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_caldav_backend_virtual_friendica.inc.php b/dav/dav_caldav_backend_virtual_friendica.inc.php
new file mode 100644 (file)
index 0000000..9178aaa
--- /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("Friendicy-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/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_carddav_backend_virtual_friendica.inc.php b/dav/dav_carddav_backend_virtual_friendica.inc.php
new file mode 100644 (file)
index 0000000..8d8a156
--- /dev/null
@@ -0,0 +1,336 @@
+<?php
+
+class Sabre_CardDAV_Backend_FriendicaCommunity extends Sabre_CardDAV_Backend_Abstract
+{
+
+       /**
+        * @var null|Sabre_CardDAV_Backend_FriendicaCommunity
+        */
+       private static $instance = null;
+
+       /**
+        * @static
+        * @return Sabre_CardDAV_Backend_FriendicaCommunity
+        */
+       public static function getInstance() {
+               if (self::$instance == null) {
+                       self::$instance = new Sabre_CardDAV_Backend_FriendicaCommunity();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * 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;
+       }
+}
index 5082a2f9519e8b2f33195793188910aaa9ece667..acc33fa1e8fed132eb993acbbd42e73a5a60df44 100644 (file)
@@ -1,16 +1,39 @@
 <?php
 
-class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBasic {
+class Sabre_DAV_Auth_Backend_Std extends Sabre_DAV_Auth_Backend_AbstractBasic {
 
     public function __construct() {
     }
 
 
-    public function getUsers() {
+       /**
+        * @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);
     }
-    
-    public function getCurrentUser() {
+
+       /**
+        * @return null|string
+        */
+       public function getCurrentUser() {
         return $this->currentUser;
     }
 
@@ -27,6 +50,12 @@ class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBa
         */
        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);
@@ -47,12 +76,18 @@ class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBa
        }
 
 
+       /**
+        * @param string $username
+        * @param string $password
+        * @return bool
+        */
        protected function validateUserPass($username, $password) {
-
-               $user = array(
-                   'uri' => "/" . 'principals/users/' . strtolower($username),
+               $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 $user;
+               return ($r[0]["anz"] == 1);
     }
     
 }
index eba31c288aba9c7579fd8770b426114ea68cf2b5..780bcd24bade8caf76a83b1ee258ed66f63d6fef 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 
-class Sabre_DAVACL_PrincipalBackend_Friendica implements Sabre_DAVACL_IPrincipalBackend
+class Sabre_DAVACL_PrincipalBackend_Std implements Sabre_DAVACL_IPrincipalBackend
 {
 
        /**
@@ -24,6 +24,23 @@ class Sabre_DAVACL_PrincipalBackend_Friendica implements Sabre_DAVACL_IPrincipal
        }
 
 
+       /**
+        * @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.
         *
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']);
+});
index 550f90b6aacf6e04bef2b62c084865f65ffe3fa7..597e773d246941e7fe609953224649ffe3b92bf5 100644 (file)
@@ -8,8 +8,14 @@ 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/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/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";
@@ -20,6 +26,7 @@ function wdcal_addRequiredHeaders()
        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";
@@ -29,31 +36,129 @@ function wdcal_addRequiredHeaders()
        $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_addRequiredHeadersEdit()
+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";
 
-       $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";
+       echo $str;
+       killme();
+}
 
-       $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";
+/**
+ * @param int $calendar_id
+ * @return string
+ */
+function wdcal_import_user_ics($calendar_id) {
+       $a = get_app();
+       $calendar_id = IntVal($calendar_id);
+       $o = "";
 
-       $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";
+       $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|DBClass_friendica_calendars[] $calendars
- * @param array $calendar_preselected
+ * @param array|Sabre_CalDAV_Calendar[] $calendars
+ * @param array|int[] $calendars_selected
  * @param string $data_feed_url
  * @param string $view
  * @param int $theme
@@ -64,14 +169,17 @@ function wdcal_addRequiredHeadersEdit()
  * @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)
+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"]);
 
-       $cals_avail = array();
-       foreach ($calendars as $c) $cals_avail[] = array("ns" => $c->namespace, "id" => $c->namespace_id, "displayname" => $c->displayname);
+       if (count($calendars_selected) == 0) foreach ($calendars as $c) {
+               $prop                 = $c->getProperties(array("id"));
+               $calendars_selected[] = $prop["id"];
+       }
+
        $opts = array(
                "view"             => $view,
                "theme"            => $theme,
@@ -96,12 +204,13 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url,
 <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"] . '"';
+       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 ($calendar_preselected as $pre) if ($pre["ns"] == $cal["ns"] && $pre["id"] == $cal["id"]) $found = true;
+               foreach ($calendars_selected as $pre) if ($pre["id"] == $cal_id["id"]) $found = true;
                if ($found) $x .= ' checked';
-               $x .= '> ' . escape_tags($cal["displayname"]) . '</label> ';
+               $x .= '> ' . escape_tags($cal_id[DAV_DISPLAYNAME]) . '</label> ';
        }
 
        $x .= '</div>
@@ -179,166 +288,58 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url,
 
 
 /**
- * @param string $uri
- * @param string $recurr_uri
+ * @param int $calendar_id
+ * @param int $calendarobject_id
  * @return string
  */
-function wdcal_getDetailPage($uri, $recurr_uri)
+function wdcal_getDetailPage($calendar_id, $calendarobject_id)
 {
        $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/");
-               }
-       }
+       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);
 
-       return $uri . " / " . $recurr_uri . "<br>" . print_r($details, true);
+               $details = $obj;
+       } catch (Exception $e) {
+               info(t("Error") . ": " . $e);
+               goaway($a->get_baseurl() . "/dav/wdcal/");
+       }
+
+       return print_r($details, true);
 }
 
+
 /**
- * @param string $uri
- * @param string $recurr_uri
+ * @param int $calendar_id
+ * @param int $uri
  * @return string
  */
-function wdcal_getEditPage($uri, $recurr_uri = "")
+function wdcal_getEditPage($calendar_id, $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>";
+       return wdcal_getEditPage_str($localization, $a->get_baseurl(), $calendar_id, $uri);
+}
 
-       $out .= "<input type='submit' name='save' value='Save'></form>";
+/**
+ * @return string
+ */
+function wdcal_getNewPage()
+{
+       $a            = get_app();
+       $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
 
-       return $out;
+       return wdcal_getEditPage_str($localization, $a->get_baseurl(), 0, 0);
 }
 
 
@@ -355,11 +356,67 @@ function wdcal_getSettingsPage(&$a)
        }
 
        if (isset($_REQUEST["save"])) {
-               check_form_security_token_redirectOnErr($a->get_baseurl() . '/dav/settings/', 'calprop');
+               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>";
@@ -384,6 +441,58 @@ function wdcal_getSettingsPage(&$a)
        $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>";
index 6635d18aca544961977cc80f87f9226a2d9f3412..03f342bd2ef83b3ba0ea8a679d129dc001cd616d 100644 (file)
@@ -7,6 +7,7 @@ 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');
 }
 
 
@@ -15,6 +16,7 @@ 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');
 }
 
 
@@ -24,45 +26,29 @@ 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/calendar_rendering.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_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_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/dav_caldav_calendar_virtual.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__ . "/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_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__ . "/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");
 }
@@ -88,72 +74,34 @@ function dav_init(&$a)
        }
 
        wdcal_create_std_calendars();
-
+       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();
-               } elseif ($a->argc >= 3 && strlen($a->argv[2]) > 0) {
-                       wdcal_addRequiredHeadersEdit();
-               } else {
-                       wdcal_addRequiredHeaders();
                }
                return;
        }
+       if ($a->argc >= 2 && $a->argv[1] == "getExceptionDates") {
+               echo wdcal_getEditPage_exception_selector();
+               killme();
+       }
 
        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);
+       if (isset($_REQUEST["test"])) {
+               renderAllCalDavEntries();
+       }
 
-       $carddavPlugin = new Sabre_CardDAV_Plugin();
-       $server->addPlugin($carddavPlugin);
 
+       $server  = dav_create_server();
        $browser = new Sabre_DAV_Browser_Plugin();
        $server->addPlugin($browser);
-
        $server->exec();
 
        killme();
@@ -170,41 +118,55 @@ function dav_content()
        }
 
        $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") {
+       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");
-                                               $o .= wdcal_postEditPage($uri, $recurr_uri, $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
+                                               $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_getEditPage($uri, $recurr_uri);
+                                       $o .= wdcal_getNewPage();
                                        return $o;
                                } else {
-                                       return wdcal_getDetailPage($uri, $recurr_uri);
+                                       $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);
                        }
-               } 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);
                }
+       } 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;
 }
@@ -218,8 +180,8 @@ 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);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
 }
 
 /**
@@ -230,8 +192,8 @@ 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);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS);
+       Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE);
 }
 
 /**
@@ -248,6 +210,56 @@ function dav_profile_tabs_hook(&$a, &$b)
        );
 }
 
+
+/**
+ * @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
@@ -256,6 +268,7 @@ 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"])) {
@@ -263,15 +276,23 @@ function dav_plugin_admin_post(&$a = null, &$o = null)
                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 null|object $o
+ * @param string $o
  */
 function dav_plugin_admin(&$a, &$o)
 {
-
+       dav_include_files();
        require_once(__DIR__ . "/database-init.inc.php");
 
        $dbstatus = dav_check_tables();
@@ -286,18 +307,30 @@ function dav_plugin_admin(&$a, &$o)
                        $o .= t('Installed');
                        break;
                case 1:
-                       $o .= t('Upgrade needed') . "<br><br><input type='submit' name='upgrade' value='" . t('Upgrade') . "'>";
+                       $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>";
-       $tables = dav_get_create_statements();
-       foreach ($tables as $t) $o .= escape_tags($t . "\n\n");
+       switch ($dbstatus) {
+               case 1:
+                       $tables = dav_get_update_statements(1);
+                       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/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
index c64f0cf45e98d2e7b02ee414f889ae143bcde48c..18c25c585f9ea36187d9fe834fe30cbc00887f64 100644 (file)
@@ -10,4 +10,34 @@ div.colorPicker-picker { display: inline-block; }
 .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
+#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/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"]);
-       }
-}
index 6ec94f3a8ee2c9779967f1b8aa44af83270e608b..ff920dc60c6a7e0280d0b6972a2d94ce1abd2c88 100644 (file)
Binary files a/facebook.tgz and b/facebook.tgz differ
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 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() {
index f467bc00cdddc6e45084d31cb0b976a289f7821a..a36e792b1263c7403fa88d2b2ddb058a274cb357 100644 (file)
Binary files a/libertree.tgz and b/libertree.tgz differ
index fcdc04f69d036bb5b7a7455f99f9cb13f6a4fd44..4065a2b78bf1763de3026dd9f2bd2616e1c16fe8 100644 (file)
Binary files a/morechoice.tgz and b/morechoice.tgz differ
index e96f0c9708183dc867be512c4cd037f96106f26f..d22ec3aaf1dbfe3ab7e8e7c629e3d4303d996fb9 100644 (file)
@@ -14,7 +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');
-
 }
 
 
@@ -24,6 +23,9 @@ function morechoice_uninstall() {
        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');
+
 }
 
 // We aren't going to bother translating these to other languages. 
@@ -121,4 +123,4 @@ function morechoice_marital_selector($a,&$b) {
                $b[] = 'Hurt in the past';
                $b[] = 'Wallowing in self-pity';
        }
-}
+}
\ 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 4a65b72089994063e8cde0c67ef3edb34faf8759..60ab458139eb8583a5ca0247fb5e36f03453d993 100755 (executable)
@@ -10,7 +10,7 @@
  */
 
 function nsfw_install() {
-       register_hook('prepare_body', 'addon/nsfw/nsfw.php', 'nsfw_prepare_body');
+       register_hook('prepare_body', 'addon/nsfw/nsfw.php', 'nsfw_prepare_body', 10);
        register_hook('plugin_settings', 'addon/nsfw/nsfw.php', 'nsfw_addon_settings');
        register_hook('plugin_settings_post', 'addon/nsfw/nsfw.php', 'nsfw_addon_settings_post');
 
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 27943df533121e16e942d852087a7bcc07f4da81..9712a0677df36f61d419f37a8587024a2a9550ce 100644 (file)
Binary files a/privacy_image_cache.tgz and b/privacy_image_cache.tgz differ
index 394a99fda47e56a33721ca89057046d5b685d71a..3b6ac194f028355bfa342a2616b9749b418558c2 100644 (file)
Binary files a/showmore.tgz and b/showmore.tgz differ
index 2b4d5d0fcce8337670120eb0e0ae5ae9633bf831..1f40b027be3b7da947e3905e8a9a817ffd176c7a 100755 (executable)
@@ -66,6 +66,38 @@ 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
+       $dom = @DomDocument::loadHTML($body);
+       $xpath = new DOMXPath($dom);
+
+       /*
+        * Checking any possible syntax of the style attribute with xpath is impossible
+        * So we just get any element with a style attribute, and check them with a regexp
+        */
+       $xr = $xpath->query('//*[@style]');
+       foreach($xr as $node) {
+               if(preg_match('/.*display: *none *;.*/',$node->getAttribute('style'))) {
+                       // Hidden, remove it from its parent
+                       $node->parentNode->removeChild($node);
+               }
+       }
+       // Now we can get the body of our HTML DomDocument, it contains only what is visible
+       $string = $dom->saveHTML();
+
+       $string = strip_tags($string);
+       return strlen($string);
+}
+
 function showmore_prepare_body(&$a,&$b) {
 
        $words = null;
@@ -76,7 +108,7 @@ function showmore_prepare_body(&$a,&$b) {
        if(!$chars)
                $chars = 1100;
 
-       if (strlen(strip_tags(trim($b['html']))) > $chars) {
+       if (get_body_length($b['html']) > $chars) {
                $found = true;
                $shortened = trim(showmore_cutitem($b['html'], $chars))."...";
        }
index 51390d305f433cec8ed7cb15331790c44168a54f..a9fe979675c740aee6ffd8113ff5bda8eaa9a505 100755 (executable)
Binary files a/statusnet.tgz and b/statusnet.tgz differ
index 2a137b54faebad22bf01f139feb7abcc9eac8e12..fe03895547fb581ab75571aa33ce6cd85cd0679b 100755 (executable)
Binary files a/tumblr.tgz and b/tumblr.tgz differ
index 192d6f85db7eb6b6caeef64cc4d005272b50975f..7e61834ad3b59150bd034f99dbd0c860b2c30ac2 100755 (executable)
Binary files a/twitter.tgz and b/twitter.tgz differ
index cb7b03bb37086faa6464443838a0fc00d404e982..5fd053fa7f95d7b1b09e66bd363fd04ad274dcfe 100755 (executable)
@@ -209,7 +209,7 @@ function twitter_settings(&$a,&$s) {
                        $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Submit') . '" /></div>'; 
                }
        }
-        $s .= '</div><div class="clear"></div></div>';
+        $s .= '</div><div class="clear"></div>';
 }