]> git.mxchange.org Git - friendica-addons.git/commitdiff
Merge branch 'master' of git://github.com/friendica/friendica-addons
authorTobias Hößl <tobias@hoessl.eu>
Wed, 18 Jul 2012 17:07:14 +0000 (17:07 +0000)
committerTobias Hößl <tobias@hoessl.eu>
Wed, 18 Jul 2012 17:07:14 +0000 (17:07 +0000)
106 files changed:
dav/Changelog.txt [new file with mode: 0644]
dav/README.md
dav/SabreDAV/ChangeLog
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/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/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/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/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/ServerEventsTest.php
dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php
dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.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]

diff --git a/dav/Changelog.txt b/dav/Changelog.txt
new file mode 100644 (file)
index 0000000..a252673
--- /dev/null
@@ -0,0 +1,16 @@
+v0.2.0-pre
+======
+[FEATURE] Multiple private Calendars can be created.
+[FEATURE] Support for recurring events.
+[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..b4e7f3ace02ecb5bce7926d7cfdedd125503ce2e 100644 (file)
@@ -6,19 +6,24 @@ 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
+- Recurrences (not the whole set of options given in the iCalendar spec, but the most important ones)
+- 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)
 
+
 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.
 - The basic design of the system is aware of timezones; however this is not reflected in the UI yet. It currently assumes that the timezone set in the friendica-installation matches the user's local time and matches the local time set in the user's operating system.
 
 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,10 +31,9 @@ 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
-
+- ICS Export and Import
 
 
 Used libraries
@@ -46,10 +50,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..0fe7758f533636ab7a7e0836cfc34834ef481093 100644 (file)
@@ -1,31 +1,56 @@
 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.
+       * 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.
+       * 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.
+
+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.
 
-1.6.3-stable (2012-??-??)
+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 +68,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.
index 4340013b54ffb012522601d4a3cc4b8815806317..95db938f8a732be80fabd5372f5cf119dcb8a08d 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) {
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 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 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..97aea2d98ec6cf907d0e92a8e63f7199bf1c6584 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'
@@ -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..c31d0b1be14da89e70a4f451fec76318aeed8f13 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;
 
             }
         }
@@ -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 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..b5ce61efe247b2ef22d56994bc9c451dbad599c5 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.
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);
         }
 
     }
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..cac4049f2204c6b5317157efbf3653ac1a070813 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() {
@@ -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>' .
@@ -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 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 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 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 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..b1b0b3b358960b086713f5a5520a10ca993b54c2 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,38 @@ 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_DB_VERSION", 2);
+
+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 +53,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 +64,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 +72,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 +92,34 @@ 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));
+}
+
+
+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 +135,49 @@ 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);
+}
+
+
 
 /**
  */
@@ -138,27 +186,22 @@ 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();
+       $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;
+
+       $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, $a->user["uid"], dbesc($uri));
+               if (count($cals) == 0) {
+                       q("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($a->user["uid"]), dbesc($name), dbesc($a->timezone), dbesc($uri)
+                       );
                }
-               $nextid = IntVal($maxid[0]["maxid"]) + 1;
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $nextid, $a->user["uid"], dbesc(t("Private Calendar")), dbesc($a->timezone)
-               );
        }
 
-       $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_FRIENDICA_NATIVE);
-       if (count($cals) < 2) {
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_MINE, $a->user["uid"], dbesc(t("Friendica Events: Mine")), dbesc($a->timezone)
-               );
-               q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_CONTACTS, $a->user["uid"], dbesc(t("Friendica Events: Contacts")), dbesc($a->timezone)
-               );
-       }
 }
index 38975f8ed281a6f666554e114e289e87f26e8a6b..8b5aad4653608d7c36330be5bc9e2ea51cf815b2 100644 (file)
@@ -6,8 +6,8 @@
 <title>Really Simple Color Picker</title>
 
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
-<script language="javascript" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
-<script language="javascript" type="text/javascript" src="jquery.colorPicker.min.js"/></script>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+<script type="text/javascript" src="jquery.colorPicker.min.js"></script>
 
 <script type="text/javascript">
   //Run the code when document ready
index 410c60dd2a7ec1535b2fc0f51040fdf3e7202039..22f1d18c51d25caf7c6292ad242cfae2345f879b 100644 (file)
@@ -1,6 +1,13 @@
 <?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 vcard_source_data_email
 {
        public $email, $type;
@@ -83,7 +90,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 +143,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 +185,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_VEvent
  */
-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_VEvent $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..b6c009d
--- /dev/null
@@ -0,0 +1,185 @@
+<?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);
+
+               $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');
+       }
+
+
+       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', '%s', '%s', %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, '%s', %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..b3b32e3
--- /dev/null
@@ -0,0 +1,501 @@
+<?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);
+
+               // @TODO Events, die früher angefangen haben, aber noch andauern
+               $evs = q("SELECT * 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
+        * @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',
+                               "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
+        * @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("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..58e3624397418a1d2a86b4d09367aadf78f8285e 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,128 @@ 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());
+       });
+
+       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..c652d97
--- /dev/null
@@ -0,0 +1,693 @@
+<?php
+
+/**
+ * @param wdcal_local $localization
+ * @param string $baseurl
+ * @param int $uid
+ * @param int $calendar_id
+ * @param int $uri
+ * @param string $recurr_uri
+ * @return string
+ */
+function wdcal_getEditPage_str(&$localization, $baseurl, $uid, $calendar_id, $uri, $recurr_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();
+               }
+
+               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(array("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 24));
+               } else {
+                       $notifications = array(array("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 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("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 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'>" . 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 {
+               $byday = array("MO", "TU", "WE", "TH", "FR", "SA", "SU");
+       }
+       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->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->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>";
+
+       /*
+       $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() . "', '${baseurl}/dav/');
+       });</script>";
+
+       $out .= "<input type='submit' name='save' value='Save'></form>";
+
+       return $out;
+}
+
+
+/**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param wdcal_local $localization
+ */
+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);
+}
+
+/**
+ * @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 = "";
+       }
+/*
+        echo "<pre>!";
+        echo $part_freq . "\n";
+        var_dump($_REQUEST);
+        echo "</pre>";
+        die();
+*/
+
+       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 string $uri
+ * @param string $recurr_uri
+ * @param int $uid
+ * @param string $timezone
+ * @param string $goaway_url
+ * @return array
+ */
+function wdcal_postEditPage($uri, $recurr_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");
+       }
+
+       wdcal_set_component_date($component, $localization);
+       wdcal_set_component_recurrence($component, $localization);
+
+       $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..6372bf442668c432d581c6a7a6a35efff0268e57 100644 (file)
@@ -1,10 +1,80 @@
 <?php
 
 
+/**
+ * @param int $from_version
+ * @return array|string[]
+ */
+function dav_get_update_statements($from_version)
+{
+       $stms = array();
+
+       if ($from_version <= 0) {
+               $stms[] = "ALTER TABLE `dav_calendars` ADD `uri` VARCHAR( 50 ) NULL DEFAULT NULL AFTER `description` , ADD `has_vevent` TINYINT NOT NULL DEFAULT '1' AFTER `uri` , ADD `has_vtodo` TINYINT NOT NULL DEFAULT '1' AFTER `has_vevent`";
+
+               $stms[] = "UPDATE `dav_calendars` SET `uri` = 'private' WHERE `namespace` = 1";
+               $stms[] = "UPDATE `dav_calendars` SET `uri` = 'friendica-mine' WHERE `namespace` = 2 AND `namespace_id` = 1";
+               $stms[] = "UPDATE `dav_calendars` SET `uri` = 'friendica-contacts' WHERE `namespace` = 2 AND `namespace_id` = 2";
+               $stms[] = "ALTER TABLE `dav_calendars` DROP PRIMARY KEY ";
+               $stms[] = "ALTER TABLE `dav_calendars` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST ";
+
+               $stms[] = "ALTER TABLE `dav_calendarobjects` ADD `calendar_id` INT NOT NULL AFTER `id` ";
+               $stms[] = "UPDATE `dav_calendarobjects` a JOIN `dav_calendars` b ON a.`namespace` = b.`namespace` AND a.`namespace_id` = b.`namespace_id` SET a.`calendar_id` = b.`id`";
+               $stms[] = "ALTER TABLE `dav_calendarobjects` DROP `namespace` , DROP `namespace_id` ;";
+               $stms[] = "ALTER TABLE `dav_calendarobjects` ADD INDEX ( `calendar_id` ) ";
+               $stms[] = "ALTER TABLE `dav_calendarobjects` ADD `componentType` ENUM( 'VEVENT', 'VTODO' ) NOT NULL DEFAULT 'VEVENT' AFTER `calendardata` ,
+                       ADD `firstOccurence` TIMESTAMP NOT NULL AFTER `lastmodified` ,
+                       ADD `lastOccurence` TIMESTAMP NOT NULL AFTER `firstOccurence`";
+
+               $stms[] = "DROP TABLE `dav_jqcalendar`";
+               $stms[] = "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,
+                       `Subject` varchar(1000) NOT NULL,
+                       `StartTime` timestamp NULL DEFAULT NULL,
+                       `EndTime` timestamp NULL DEFAULT NULL,
+                       `IsEditable` tinyint(3) unsigned NOT NULL,
+                       `IsAllDayEvent` tinyint(4) NOT NULL,
+                       `IsRecurring` tinyint(4) NOT NULL,
+                       `Color` CHAR(6) DEFAULT NULL,
+                       PRIMARY KEY (`id`),
+                       KEY `calendarByStart` (`calendar_id`,`StartTime`),
+                       KEY `calendarobject_id` (`calendarobject_id`,`ical_recurr_uri`)
+                       ) DEFAULT CHARSET=utf8 ";
+
+
+               $stms[] = "ALTER TABLE `dav_notifications` ADD `calendar_id` INT NOT NULL AFTER `ical_recurr_uri` ";
+               $stms[] = "ALTER TABLE `dav_notifications` DROP INDEX `ical_uri` , ADD INDEX `ical_uri` ( `calendar_id` , `ical_uri` , `ical_recurr_uri` ) ";
+               $stms[] = "TRUNCATE TABLE `dav_notifications`";
+               $stms[] = "ALTER TABLE `dav_notifications` DROP `namespace` , DROP `namespace_id`";
+
+               $stms[] = "TRUNCATE TABLE `dav_cal_virtual_object_cache`";
+               $stme[] = "ALTER TABLE `dav_cal_virtual_object_cache` ADD `calendar_id` INT UNSIGNED NOT NULL AFTER `id` ";
+               $stms[] = "ALTER TABLE `dav_cal_virtual_object_cache` DROP INDEX `ref_type` , ADD INDEX `ref_type` ( `calendar_id` , `data_end` )  ";
+               $stms[] = "ALTER TABLE `dav_cal_virtual_object_cache` DROP `uid`, DROP `namespace` , DROP `namespace_id` ";
+
+               $stms[] = "CREATE TABLE `friendica`.`dav_cal_virtual_object_sync` (
+                       `calendar_id` INT UNSIGNED NOT NULL ,
+                       `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+                       PRIMARY KEY ( `calendar_id` )
+                       ) ENGINE=MyISAM DEFAULT CHARSET=utf8";
+
+               $stms[] = "DROP TABLE `dav_cache_synchronized` ";
+
+               $stms[] = "UPDATE `dav_calendars` SET `namespace` = 1, `namespace_id` = `uid`"; // last
+               $stms[] = "ALTER TABLE `dav_calendars` DROP `uid`";
+       }
+
+       return $stms;
+}
+
 /**
  * @return array
  */
-function dav_get_create_statements() {
+function dav_get_create_statements()
+{
        $arr = array();
 
        $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` (
@@ -69,6 +139,9 @@ function dav_get_create_statements() {
   `displayname` varchar(200) NOT NULL,
   `timezone` text NOT NULL,
   `description` varchar(500) NOT NULL,
+  `uri` varchar(50) DEFAULT NULL,
+  `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`)
@@ -171,10 +244,11 @@ function dav_get_create_statements() {
 /**
  * @return int
  */
-function dav_check_tables() {
+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)
+       if (is_numeric($dbv) || $dbv == "CALDAV_DB_VERSION") return 1; // Older version (update needed)
        return -1; // Not installed
 }
 
@@ -184,7 +258,29 @@ function dav_check_tables() {
  */
 function dav_create_tables()
 {
-       $stms = dav_get_create_statements();
+       $stms   = dav_get_create_statements();
+       $errors = array();
+
+       global $db;
+       foreach ($stms as $st) {
+               $db->q($st);
+               if ($db->error) $errors[] = $db->error;
+       }
+
+       if (count($errors) == 0) set_config("dav", "db_version", CALDAV_DB_VERSION);
+
+       return $errors;
+}
+
+/**
+ * @return array
+ */
+function dav_upgrade_tables()
+{
+       $dbv = get_config("dav", "db_version");
+       if ($dbv == "CALDAV_DB_VERSION") $ver = 0;
+       else $ver = IntVal($dbv);
+       $stms   = dav_get_update_statements($ver);
        $errors = array();
 
        global $db;
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..e25dce3
--- /dev/null
@@ -0,0 +1,252 @@
+<?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"             => "#f8f8ff",
+                       "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',
+                               "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..6f59d745626c835eaf5ac9b5065f5e9a480519ad 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,60 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url,
 
 
 /**
- * @param string $uri
+ * @param int $calendar_id
+ * @param int $calendarobject_id
  * @param string $recurr_uri
  * @return string
  */
-function wdcal_getDetailPage($uri, $recurr_uri)
+function wdcal_getDetailPage($calendar_id, $calendarobject_id, $recurr_uri)
 {
        $a = get_app();
 
-       $details = null;
-       $cals    = dav_getMyCals($a->user["uid"]);
-       foreach ($cals as $c) {
-               $cs = wdcal_calendar_factory($a->user["uid"], $c->namespace, $c->namespace_id);
-               $p  = $cs->getPermissionsItem($a->user["uid"], $uri, $recurr_uri);
-               if ($p["read"]) try {
-                       $redirect = $cs->getItemDetailRedirect($uri);
-                       if ($redirect !== null) goaway($redirect);
-                       $details = $cs->getItemByUri($uri);
-               } catch (Exception $e) {
-                       notification(t("Error") . ": " . $e);
-                       goaway($a->get_baseurl() . "/dav/wdcal/");
-               }
-       }
+       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 int $calendar_id
+ * @param int $uri
  * @param string $recurr_uri
  * @return string
  */
-function wdcal_getEditPage($uri, $recurr_uri = "")
+function wdcal_getEditPage($calendar_id, $uri, $recurr_uri = "")
 {
-
        $a            = get_app();
        $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
 
-       if ($uri != "" && $uri != "new") {
-               $o = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
-               );
-               if (count($o) != 1) return t('Not found');
-               $event = $o[0];
-
-               $calendarSource = wdcal_calendar_factory($a->user["uid"], $event["namespace"], $event["namespace_id"]);
-
-               $permissions = $calendarSource->getPermissionsItem($a->user["uid"], $uri, $recurr_uri, $event);
-
-               if (!$permissions["write"]) return t('No access');
-
-               $n = q("SELECT * FROM %s%snotifications WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
-                       CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
-               );
-               if (count($n) > 0) {
-                       $notification_type  = $n[0]["rel_type"];
-                       $notification_value = -1 * $n[0]["rel_value"];
-                       $notification       = true;
-               } else {
-                       if ($event["IsAllDayEvent"]) {
-                               $notification_type  = "hour";
-                               $notification_value = 24;
-                       } else {
-                               $notification_type  = "minute";
-                               $notification_value = 60;
-                       }
-                       $notification = false;
-               }
-
-
-       } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) {
-               $event = array(
-                       "id"            => 0,
-                       "Subject"       => $_REQUEST["title"],
-                       "Location"      => "",
-                       "Description"   => "",
-                       "StartTime"     => wdcal_php2MySqlTime($_REQUEST["start"]),
-                       "EndTime"       => wdcal_php2MySqlTime($_REQUEST["end"]),
-                       "IsAllDayEvent" => $_REQUEST["isallday"],
-                       "Color"         => null,
-                       "RecurringRule" => null,
-               );
-               if ($_REQUEST["isallday"]) {
-                       $notification_type  = "hour";
-                       $notification_value = 24;
-               } else {
-                       $notification_type  = "hour";
-                       $notification_value = 1;
-               }
-
-               $notification = true;
-       } else {
-               $event              = array(
-                       "id"            => 0,
-                       "Subject"       => "",
-                       "Location"      => "",
-                       "Description"   => "",
-                       "StartTime"     => date("Y-m-d H:i:s"),
-                       "EndTime"       => date("Y-m-d H:i:s", time() + 3600),
-                       "IsAllDayEvent" => "0",
-                       "Color"         => "#5858ff",
-                       "RecurringRule" => null,
-               );
-               $notification_type  = "hour";
-               $notification_value = 1;
-               $notification       = true;
-       }
-
-       $postto = $a->get_baseurl() . "/dav/wdcal/" . ($uri == "new" ? "new/" : $uri . "/edit/");
-
-       $out = "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
-       $out .= "<form method='POST' action='$postto'><input type='hidden' name='form_security_token' value='" . get_form_security_token('caledit') . "'>\n";
-
-       $out .= "<label for='cal_subject'>Subject:</label>
-               <input name='color' id='cal_color' value='" . (strlen($event["Color"]) != 7 ? "#5858ff" : escape_tags($event["Color"])) . "'>
-               <input name='subject' id='cal_subject' value='" . escape_tags($event["Subject"]) . "'><br>\n";
-       $out .= "<label for='cal_allday'>Is All-Day event:</label><input type='checkbox' name='allday' id='cal_allday' " . ($event["IsAllDayEvent"] ? "checked" : "") . "><br>\n";
-
-       $out .= "<label for='cal_startdate'>" . t("Starts") . ":</label>";
-       $out .= "<input name='start_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["StartTime"])) . "' id='cal_start_date'>";
-       $out .= "<input name='start_time' value='" . substr($event["StartTime"], 11, 5) . "' id='cal_start_time'>";
-       $out .= "<br>\n";
-
-       $out .= "<label for='cal_enddate'>" . t("Ends") . ":</label>";
-       $out .= "<input name='end_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["EndTime"])) . "' id='cal_end_date'>";
-       $out .= "<input name='end_time' value='" . substr($event["EndTime"], 11, 5) . "' id='cal_end_time'>";
-       $out .= "<br>\n";
-
-       $out .= "<label for='cal_location'>" . t("Location") . ":</label><input name='location' id='cal_location' value='" . escape_tags($event["Location"]) . "'><br>\n";
-
-       $out .= "<label for='event-desc-textarea'>" . t("Description") . ":</label> <textarea id='event-desc-textarea' name='wdcal_desc' style='vertical-align: top; width: 400px; height: 100px;'>" . escape_tags($event["Description"]) . "</textarea>";
-       $out .= "<br style='clear: both;'>";
-
-       $out .= "<label for='notification'>" . t('Notification') . ":</label>";
-       $out .= '<input type="checkbox" name="notification" id="notification" ';
-       if ($notification) $out .= "checked";
-       $out .= '> ';
-       $out .= '<span id="notification_detail" style="display: none;">
-                       <input name="notification_value" value="' . $notification_value . '" size="3">
-                       <select name="notification_type" size="1">
-                               <option value="minute" ';
-       if ($notification_type == "minute") $out .= "selected";
-       $out .= '> ' . t('Minutes') . '</option>
-                               <option value="hour" ';
-       if ($notification_type == "hour") $out .= "selected";
-       $out .= '> ' . t('Hours') . '</option>
-                               <option value="day" ';
-       if ($notification_type == "day") echo "selected";
-       $out .= '> ' . t('Days') . '</option>
-                       </select> ' . t('before') . '
-               </span><br><br>';
-
-
-       $out .= "<script>\$(function() {
-               wdcal_edit_init('" . $localization->dateformat_datepicker_js() . "');
-       });</script>";
+       return wdcal_getEditPage_str($localization, $a->get_baseurl(), $a->user["uid"], $calendar_id, $uri, $recurr_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(), $a->user["uid"], 0, 0);
 }
 
 
@@ -355,11 +358,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 +443,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..2a0092acab8d39f79d31ca5552aa186db2b03e0b 100644 (file)
@@ -24,12 +24,6 @@ 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");
@@ -38,31 +32,27 @@ function dav_include_files()
                        */
        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");
 }
@@ -81,79 +71,41 @@ function dav_init(&$a)
 
        dav_include_files();
 
-       if (false) {
+       if (true) {
                dbg(true);
                error_reporting(E_ALL);
                ini_set("display_errors", 1);
        }
 
        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();
@@ -174,36 +126,48 @@ function dav_content()
        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") {
+               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("new", "", $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("new");
+                               $o .= wdcal_getNewPage();
                                return $o;
                        } else {
-                               $recurr_uri = ""; // @TODO
-                               if (isset($a->argv[3]) && $a->argv[3] == "edit") {
-                                       $o = "";
-                                       if (isset($_REQUEST["save"])) {
-                                               check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit");
-                                               $o .= wdcal_postEditPage($uri, $recurr_uri, $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/");
+                               $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) {
+                                       $recurr_uri = ""; // @TODO
+                                       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], $recurr_uri, $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], $recurr_uri);
+                                               return $o;
+                                       } else {
+                                               return wdcal_getDetailPage($calendar_id, $a->argv[3], $recurr_uri);
                                        }
-                                       $o .= wdcal_getEditPage($uri, $recurr_uri);
-                                       return $o;
                                } else {
-                                       return wdcal_getDetailPage($uri, $recurr_uri);
+                                       // @TODO Edit Calendar
                                }
                        }
                } 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);
+                       $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);
                }
        }
        return $x;
@@ -218,8 +182,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 +194,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);
 }
 
 /**
@@ -256,6 +220,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 +228,20 @@ 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) 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();
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"]);
-       }
-}