From fefee23e903a4d82944dcaf75b8953c5181a6ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B6=C3=9Fl?= Date: Sun, 8 Jul 2012 17:12:58 +0000 Subject: [PATCH] Heavily refactored, including multiple calendars per user and recurring events. Not in an installable state yet, though --- dav/Changelog.txt | 15 + dav/README.md | 20 +- dav/SabreDAV/ChangeLog | 45 +- dav/SabreDAV/bin/migrateto17.php | 22 +- dav/SabreDAV/docs/caldav-notifications.txt | 1568 +++++++++++++++++ dav/SabreDAV/docs/caldav-sharing-02.txt | 224 +++ dav/SabreDAV/docs/rfc5785.txt | 451 +++++ .../examples/webserver/apache2_vhost.conf | 30 +- .../examples/webserver/apache2_vhost_cgi.conf | 16 +- .../lib/Sabre/CalDAV/Backend/Abstract.php | 132 +- .../Sabre/CalDAV/Backend/BackendInterface.php | 231 +++ .../CalDAV/Backend/NotificationSupport.php | 44 + dav/SabreDAV/lib/Sabre/CalDAV/Calendar.php | 6 +- .../lib/Sabre/CalDAV/CalendarObject.php | 6 +- .../Sabre/CalDAV/CalendarQueryValidator.php | 37 +- .../lib/Sabre/CalDAV/CalendarRootNode.php | 6 +- .../CalDAV/Exception/InvalidComponentType.php | 32 + .../Sabre/CalDAV/Notifications/Collection.php | 80 + .../CalDAV/Notifications/ICollection.php | 22 + .../lib/Sabre/CalDAV/Notifications/INode.php | 29 + .../Notifications/INotificationType.php | 46 + .../lib/Sabre/CalDAV/Notifications/Node.php | 68 + .../Notification/SystemStatus.php | 158 ++ dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php | 167 +- .../lib/Sabre/CalDAV/UserCalendars.php | 11 +- .../lib/Sabre/CardDAV/Backend/PDO.php | 2 +- dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php | 6 + dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php | 5 +- dav/SabreDAV/lib/Sabre/DAV/Property.php | 2 +- .../lib/Sabre/DAV/Property/Response.php | 2 +- .../lib/Sabre/DAV/PropertyInterface.php | 21 + dav/SabreDAV/lib/Sabre/DAV/Server.php | 6 +- .../lib/Sabre/DAV/Tree/Filesystem.php | 4 +- .../lib/Sabre/VObject/Property/DateTime.php | 52 +- .../lib/Sabre/VObject/TimeZoneUtil.php | 351 ++++ dav/SabreDAV/lib/Sabre/VObject/includes.php | 2 +- .../tests/Sabre/CalDAV/Backend/Mock.php | 55 +- .../Sabre/CalDAV/ICSExportPluginTest.php | 50 + .../tests/Sabre/CalDAV/Issue220Test.php | 96 + .../CalDAV/Notifications/CollectionTest.php | 27 + .../Sabre/CalDAV/Notifications/NodeTest.php | 23 + .../Notification/SystemStatusTest.php | 55 + .../tests/Sabre/CalDAV/OutboxPostTest.php | 73 +- .../tests/Sabre/CalDAV/PluginTest.php | 96 +- .../tests/Sabre/CalDAV/ValidateICalTest.php | 36 + .../tests/Sabre/CardDAV/ValidateVCardTest.php | 21 +- .../tests/Sabre/DAV/ServerEventsTest.php | 22 + .../tests/Sabre/DAV/Tree/FilesystemTest.php | 2 + .../tests/Sabre/VObject/TimeZoneUtilTest.php | 153 ++ dav/calendar.friendica.fnk.php | 138 +- dav/colorpicker/demo.html | 4 +- dav/common/calendar.fnk.php | 657 ++----- dav/common/calendar_rendering.fnk.php | 185 ++ dav/common/dav_caldav_backend_common.inc.php | 239 ++- dav/common/dav_caldav_backend_private.inc.php | 495 ++++++ dav/common/dav_caldav_backend_virtual.inc.php | 186 ++ .../dav_caldav_calendar_virtual.inc.php | 44 + dav/common/dav_carddav_backend_std.inc.php | 17 + dav/common/dav_user_calendars.inc.php | 18 +- dav/common/wdcal.js | 134 +- dav/common/wdcal/js/jquery.calendar.js | 52 +- dav/common/wdcal/js/wdCalendar_lang_DE.js | 21 +- dav/common/wdcal/js/wdCalendar_lang_EN.js | 21 +- dav/common/wdcal_backend.inc.php | 238 +++ dav/common/wdcal_configuration.php | 46 + dav/common/wdcal_edit.inc.php | 510 ++++++ dav/database-init.inc.php | 104 +- dav/dav.php | 2 +- ...v_caldav_backend_virtual_friendica.inc.php | 245 +++ ..._carddav_backend_virtual_friendica.inc.php | 336 ++++ dav/dav_friendica_auth.inc.php | 51 +- dav/dav_friendica_principal.inc.php | 19 +- dav/jqueryui/jquery-ui-1.8.21.custom.css | 375 ++++ dav/jqueryui/jquery-ui-1.8.21.custom.min.js | 21 + dav/jqueryui/jquery.ui.datepicker-de.js | 23 + dav/layout.fnk.php | 250 +-- dav/main.php | 140 +- dav/wdcal.css | 32 +- 78 files changed, 8026 insertions(+), 1205 deletions(-) create mode 100644 dav/Changelog.txt create mode 100644 dav/SabreDAV/docs/caldav-notifications.txt create mode 100644 dav/SabreDAV/docs/caldav-sharing-02.txt create mode 100644 dav/SabreDAV/docs/rfc5785.txt create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INode.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/INotificationType.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Node.php create mode 100644 dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php create mode 100644 dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php mode change 100755 => 100644 dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php create mode 100644 dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php create mode 100644 dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php create mode 100644 dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php create mode 100644 dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php create mode 100644 dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php create mode 100644 dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php create mode 100644 dav/common/calendar_rendering.fnk.php create mode 100644 dav/common/dav_caldav_backend_private.inc.php create mode 100644 dav/common/dav_caldav_backend_virtual.inc.php create mode 100644 dav/common/dav_caldav_calendar_virtual.inc.php create mode 100644 dav/common/wdcal_backend.inc.php create mode 100644 dav/common/wdcal_edit.inc.php create mode 100644 dav/dav_caldav_backend_virtual_friendica.inc.php create mode 100644 dav/dav_carddav_backend_virtual_friendica.inc.php create mode 100644 dav/jqueryui/jquery-ui-1.8.21.custom.css create mode 100644 dav/jqueryui/jquery-ui-1.8.21.custom.min.js create mode 100644 dav/jqueryui/jquery.ui.datepicker-de.js diff --git a/dav/Changelog.txt b/dav/Changelog.txt new file mode 100644 index 00000000..4ee89498 --- /dev/null +++ b/dav/Changelog.txt @@ -0,0 +1,15 @@ +v0.2.0 +====== +[FEATURE] Multiple private Calendars can be created. +[COMPATIBILITY] When creating or updating an event using CalDAV, the etag is returned. + +v0.1.1 +====== +[FEATURE] A "New Event" Button in the navigation bar of the calendar is added. +[FEATURE] When creating an event by dragging in the calendar, the "Edit Details"-Link leads to a page where the details can be added before actually creating the event. +[BUGFIX] When editing a event, the start time cannot be set befor the end time anymore. +[BUGFIX] Fixed some problems with Magic Quotes + +v0.1.0 +====== +Initial Release \ No newline at end of file diff --git a/dav/README.md b/dav/README.md index bab0a1dd..b4e7f3ac 100644 --- a/dav/README.md +++ b/dav/README.md @@ -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 diff --git a/dav/SabreDAV/ChangeLog b/dav/SabreDAV/ChangeLog index 7ba68c47..adafd9c9 100644 --- a/dav/SabreDAV/ChangeLog +++ b/dav/SabreDAV/ChangeLog @@ -1,31 +1,47 @@ 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!) -1.6.3-stable (2012-??-??) +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. + +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 +59,7 @@ compatibility. * Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See https://bugs.kde.org/show_bug.cgi?id=300047 + * Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken. 1.6.2-stable (2012-04-16) * Fixed: Sabre_VObject_Node::$parent should have been public. diff --git a/dav/SabreDAV/bin/migrateto17.php b/dav/SabreDAV/bin/migrateto17.php index 4340013b..95db938f 100644 --- a/dav/SabreDAV/bin/migrateto17.php +++ b/dav/SabreDAV/bin/migrateto17.php @@ -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(<<getAttribute(PDO::ATTR_DRIVER_NAME)) { + + case 'mysql' : + + $pdo->exec(<<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 index 00000000..75c2e5e0 --- /dev/null +++ b/dav/SabreDAV/docs/caldav-notifications.txt @@ -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] + + 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] + + 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] + + 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] + + 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: + + + +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] + + 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: + + + + +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: + + + +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] + + CalDAV User Notifications March 2012 + + + Definition: + + + + +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: + + + + + +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] + + 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: + + + + + + + + + Example: This is an example of the body of a notification resource + for an emergency system outage: + + + + 2011-12-09T11:12:53-05:00 + + http://example.com/emergency_shutdown.html + Emergency shutdown now + + + + Example: This is an example of the WebDAV property on the example + notification resource above: + + + + + + +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] + + 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: + + + + + + + + + + + + + + + + Example: This is an example of the body of a notification resource + for a quota warning: + + + + + + + + + + + + +Daboo [Page 9] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:12:53-05:00 + + + + 80 + https://example.com/your-account.html + + + + + 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: + + + + 2011-12-09T11:12:53-05:00 + + + + 102 + https://example.com/fix-account.html + + + + 82 + 4980 + https://example.com/buy-more-space.html + + + + +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] + + 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] + + 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] + + CalDAV User Notifications March 2012 + + + + + + + + + + + + + + + + + + + + + + + Example: This is an example of the body of a notification resource + for changes where one resource has been created: + + + + + + + + + + + + + + + +Daboo [Page 13] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus Daboo + /principals/cyrusdaboo + + + + + + 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: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/event.ics + + Oliver + Daboo + mailto:oliver@example.com + + + + http://example.com/cyrus/calendar/event.ics + + Eleanor + Daboo + mailto:eleanor@example.com + + + + + + + + + + + +Daboo [Page 14] + + 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: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/old.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + 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: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/ + 1 + 2 + 1 + + + + +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] + + 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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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] + + CalDAV User Notifications March 2012 + + + + + + + + + + + + Example: This example indicates that an instance of a recurring + component was changed and that the change was to the "DTSTART" + iCalendar property. + + + + 20111215T160000Z + + + + + + +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] + + 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] + + CalDAV User Notifications March 2012 + + + of a calendar object resource, the other for deletion of a + calendar collection. + + Definition: + + + + + + + + + + + + + + + + + + + + + Example: This example indicates deletion of a non-recurring event + that was yet to occur at the time of deletion. + + + VEVENT + Birthday Party + + Holidays + + +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: + + + + + + + + +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] + + 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] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + +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. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + + + + + + + + + + + + + +Daboo [Page 22] + + 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. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + + + + + + + + + + + + + +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] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + 20120209T170000Z + + + + + + + 20120210T170000Z + + + + + + + + + + +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] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + 20120209T170000Z + + + + + + + + + http://example.com/cyrus/calendar/new.ics + + Eric + York + /principals/ericyork + + + + 20120210T170000Z + + + + + + + + + + +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] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + VEVENT + CalDAV Meeting + 20120210T170000Z + + + + + +A.7. Collection Created + + This is an example of the body of a notification resource where a + calendar collection has been created. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/new-calendar/ + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + +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] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/ + 1 + 2 + 1 + + + + +A.9. Collection Deleted + + This is an example of the body of a notification resource where a + calendar collection has been deleted. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/old-calendar/ + + Cyrus + Daboo + /principals/cyrusdaboo + + + Holidays + + + + + + + + + + + + + + + + + + +Daboo [Page 27] + + 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] + diff --git a/dav/SabreDAV/docs/caldav-sharing-02.txt b/dav/SabreDAV/docs/caldav-sharing-02.txt new file mode 100644 index 00000000..7850e0aa --- /dev/null +++ b/dav/SabreDAV/docs/caldav-sharing-02.txt @@ -0,0 +1,224 @@ + + + + + + + + + + + + + /CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt – Calendar and Contacts Server + + + + + + + + + + + + + + + + +
+ +
+
+
Projects
+ + +
+
+
+ Wiki +     + Timeline +     + Roadmap +     + Browse Source +     + View Tickets +     + New Ticket +     + Search +
+
+ +
+ +
+

+ root/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt +
+

+
+
+
+ + +
+
+
+ + + + + + + +
+ Revision 6401, 53.0 KB + (checked in by cdaboo@…, 21 months ago) +
+

+Sharing extension specification.
+

+
+
+
Line 
1
2
3
4Calendar Server Extension                                       C. Daboo
5                                                                 E. York
6                                                              Apple Inc.
7                                                         October 6, 2010
8
9
10                Shared and Published Calendars in CalDAV
11
12Abstract
13
14   This specification defines an extension to CalDAV that enables the
15   sharing of calendars between users on a CalDAV server.
16
17
18Table of Contents
19
20   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
21   2.  Conventions Used in This Document  . . . . . . . . . . . . . .  3
22   3.  Overview . . . . . . . . . . . . . . . . . . . . . . . . . . .  4
23   4.  Notifications  . . . . . . . . . . . . . . . . . . . . . . . .  5
24     4.1.  Additional Principal Properties  . . . . . . . . . . . . .  5
25       4.1.1.  CS:notification-URL Property . . . . . . . . . . . . .  6
26     4.2.  Properties on Notification Resources . . . . . . . . . . .  6
27       4.2.1.  CS:notificationtype Property . . . . . . . . . . . . .  6
28   5.  Shared Calendaring . . . . . . . . . . . . . . . . . . . . . .  7
29     5.1.  Feature Discovery  . . . . . . . . . . . . . . . . . . . .  7
30     5.2.  Additional Properties for Calendars  . . . . . . . . . . .  7
31       5.2.1.  DAV:resourcetype Property  . . . . . . . . . . . . . .  7
32       5.2.2.  CS:invite Property . . . . . . . . . . . . . . . . . .  8
33       5.2.3.  CS:allowed-sharing-modes Property  . . . . . . . . . .  8
34       5.2.4.  CS:shared-url Property . . . . . . . . . . . . . . . .  9
35     5.3.  Sharer Actions on Shared Calendars . . . . . . . . . . . .  9
36       5.3.1.  Creating a Shared Calendar . . . . . . . . . . . . . .  9
37         5.3.1.1.  Example: Successful MKCALENDAR Request . . . . . . 10
38         5.3.1.2.  Example: Successful Extended MKCOL Request . . . . 10
39       5.3.2.  Sharing an Existing Calendar . . . . . . . . . . . . . 11
40         5.3.2.1.  Example: Successful PROPPATCH Request  . . . . . . 11
41       5.3.3.  Manipulating Sharees of a Shared Calendar  . . . . . . 12
42         5.3.3.1.  Example: Successful Sharee Add Request . . . . . . 13
43         5.3.3.2.  Example: Successful Multiple Sharee Change
44                   Request  . . . . . . . . . . . . . . . . . . . . . 14
45     5.4.  Sharee Actions on Shared Calendars . . . . . . . . . . . . 15
46       5.4.1.  Replying to a Sharing Invite . . . . . . . . . . . . . 15
47       5.4.2.  Removing a Shared Calendar . . . . . . . . . . . . . . 16
48     5.5.  General Considerations . . . . . . . . . . . . . . . . . . 16
49       5.5.1.  Access Levels  . . . . . . . . . . . . . . . . . . . . 16
50       5.5.2.  Allowing or Disallowing Sharing  . . . . . . . . . . . 16
51       5.5.3.  Per-user WebDAV Properties . . . . . . . . . . . . . . 17
52
53
54
55Daboo & York                                                    [Page 1]
56
57                      CalDAV Sharing and Publishing         October 2010
58
59
60       5.5.4.  Per-user Calendar Data . . . . . . . . . . . . . . . . 17
61       5.5.5.  Scheduling . . . . . . . . . . . . . . . . . . . . . . 18
62   6.  XML Element Definitions  . . . . . . . . . . . . . . . . . . . 19
63     6.1.  CS:shared-owner  . . . . . . . . . . . . . . . . . . . . . 19
64     6.2.  CS:shared  . . . . . . . . . . . . . . . . . . . . . . . . 20
65     6.3.  CS:can-be-shared . . . . . . . . . . . . . . . . . . . . . 20
66     6.4.  CS:can-be-published  . . . . . . . . . . . . . . . . . . . 21
67     6.5.  CS:user  . . . . . . . . . . . . . . . . . . . . . . . . . 21
68     6.6.  CS:invite-noresponse . . . . . . . . . . . . . . . . . . . 21
69     6.7.  CS:invite-deleted  . . . . . . . . . . . . . . . . . . . . 22
70     6.8.  CS:invite-accepted . . . . . . . . . . . . . . . . . . . . 22
71     6.9.  CS:invite-declined . . . . . . . . . . . . . . . . . . . . 22
72     6.10. CS:invite-invalid  . . . . . . . . . . . . . . . . . . . . 23
73     6.11. CS:access  . . . . . . . . . . . . . . . . . . . . . . . . 23
74     6.12. CS:read  . . . . . . . . . . . . . . . . . . . . . . . . . 24
75     6.13. CS:read-write  . . . . . . . . . . . . . . . . . . . . . . 24
76     6.14. CS:summary . . . . . . . . . . . . . . . . . . . . . . . . 24
77     6.15. CS:invite-notification . . . . . . . . . . . . . . . . . . 25
78     6.16. CS:uid . . . . . . . . . . . . . . . . . . . . . . . . . . 25
79     6.17. CS:hosturl . . . . . . . . . . . . . . . . . . . . . . . . 25
80     6.18. CS:organizer . . . . . . . . . . . . . . . . . . . . . . . 26
81     6.19. CS:common-name . . . . . . . . . . . . . . . . . . . . . . 26
82     6.20. CS:invite-reply  . . . . . . . . . . . . . . . . . . . . . 26
83     6.21. CS:in-reply-to . . . . . . . . . . . . . . . . . . . . . . 27
84     6.22. CS:notification  . . . . . . . . . . . . . . . . . . . . . 27
85     6.23. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . . . 28
86     6.24. CS:share . . . . . . . . . . . . . . . . . . . . . . . . . 28
87     6.25. CS:set . . . . . . . . . . . . . . . . . . . . . . . . . . 28
88     6.26. CS:remove  . . . . . . . . . . . . . . . . . . . . . . . . 29
89     6.27. CS:shared-as . . . . . . . . . . . . . . . . . . . . . . . 29
90   7.  Security Considerations  . . . . . . . . . . . . . . . . . . . 29
91   8.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 29
92   9.  Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 30
93   10. Normative References . . . . . . . . . . . . . . . . . . . . . 30
94   Appendix A.  Change History  . . . . . . . . . . . . . . . . . . . 30
95   Appendix B.  Change History  . . . . . . . . . . . . . . . . . . . 30
96   Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 31
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111Daboo & York                                                    [Page 2]
112
113                      CalDAV Sharing and Publishing         October 2010
114
115
1161.  Introduction
117
118   CalDAV [RFC4791] provides a way for calendar users to store calendar
119   data and exchange this data via scheduling operations.  Based on the
120   WebDAV [RFC4918] protocol, it also includes the ability to manage
121   access to calendar data via the WebDAV ACL [RFC3744] extension.
122
123   WebDAV ACL [RFC3744] provides a way to manage fine-grained access
124   controls on WebDAV resources.  Whilst this could be used directly to
125   manage sharing of calendars, experience has shown that client
126   developers are averse to using it due to its complexity.  Instead a
127   simpler process for sharing calendars is preferred.
128
129   This extension defines a way for individual calendar users to share
130   calendars with other users.  This is done via an "opt-in" process in
131   which a sharing invite is sent from the sharer to a sharee, allowing
132   the sharee to accept or decline.  If the sharee accepts the sharing
133   invite, the shared calendar is made available to them in their own
134   calendar home collection (i.e., alongside their own personal
135   calendars).  HTTP POST operations are used to manage the sharing
136   invitations and replies, and WebDAV properties are used to expose the
137   state of shared calendars.
138
139
1402.  Conventions Used in This Document
141
142   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
143   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
144   document are to be interpreted as described in [RFC2119].
145
146   When XML element types in the namespaces "DAV:" and
147   "urn:ietf:params:xml:ns:caldav" are referenced in this document
148   outside of the context of an XML fragment, the string "DAV:" and
149   "CALDAV:" will be prefixed to the element type names respectively.
150
151   The namespace "http://calendarserver.org/ns/" is used for XML
152   elements defined in this specification.  When XML element types in
153   that namespace are referenced in this document outside of the context
154   of an XML fragment, the string "CS:" will be prefixed to the element
155   type names.
156
157   Terms Used:
158
159   Sharer  A calendar user who is sharing a calendar with other calendar
160      users.
161
162
163
164
165
166
167Daboo & York                                                    [Page 3]
168
169                      CalDAV Sharing and Publishing         October 2010
170
171
172   Sharee  A calendar user to whom a calendar has been shared.
173
174   Sharing Invite  A message sent by a sharer to a sharee to indicate
175      the status of a shared calendar.
176
177   Sharing Reply  A message sent by a sharee to a sharer to indicate the
178      status of a shared calendar.
179
180
1813.  Overview
182
183   This section provides a basic overview of this protocol by way of a
184   simple use case of a sharer sharing a calendar with a single sharee.
185
186   To share a calendar with another user, the sharer's client executes
187   an HTTP POST request against the calendar collection resource for the
188   calendar to be shared.  The POST request body will contain details of
189   the calendar user to whom the calendar is to be shared as well as the
190   access right to be granted to them.  If the request succeeds, a
191   notification is sent to the sharee with details of the calendar being
192   shared to them.
193
194   The sharer's client will show the notification to the sharee and
195   present them with the choice to accept or decline the invitation to
196   the shared calendar.  If the sharee chooses to decline, then nothing
197   changes for that sharee.  If the sharee chooses to accept, then the
198   server automatically creates a new calendar collection resource in
199   the sharee's calendar home collection, and ensures that calendar
200   provides a mapping to the actual shared calendar of the sharer.  Thus
201   the shared calendar is available to the sharee as just another
202   calendar in their calendar home.  The server enforces the appropriare
203   access privileges for the sharee.
204
205   At any time, the sharer can inspect properties on the calendar
206   collection being shared, and determine the accept/decline status of
207   each sharee.  Additional sharees can be added and existing ones
208   removed.  The access privileges for existing sharees can also be
209   changed.
210
211   Once a sharee has a shared calendar set to appear in their calendar
212   home collection, they can remove it and decline the sharing invite by
213   simply having their client issue an HTTP DELETE request on the shared
214   calendar collection.  That does not delete any calendar data, but
215   rather simply removes the "link" to the sharer's calendar collection
216   and sets the sharee's inviate status to declined.
217
218
219
220
221
222
223Daboo & York                                                    [Page 4]
224
225                      CalDAV Sharing and Publishing         October 2010
226
227
2284.  Notifications
229
230   In order to facilitate the process of sharing invitations, this
231   specification defines a new generic notification mechanism for CalDAV
232   servers.  When this feature is available, a CS:notification-URL
233   (Section 4.1.1) property appears on principal resources for those
234   principals who are able to receive notifications.  That property
235   specifies a single DAV:href element whose content refers to a WebDAV
236   collection resource.  Notification "messages" are deposited into this
237   collection and can be retrieved by clients and acted on accordingly.
238
239   The notification collection referenced by the CS:notification-URL
240   (Section 4.1.1) property MUST have a DAV:resourcetype property with
241   DAV:collection and CS:notification (Section 6.22) child elements.
242
243   Notification "messages" are XML documents stored as resources in the
244   notification collection.  Each XML document contains a CS:
245   notification (Section 6.22) element as its root.  The root element
246   contains a CS:dtstamp (Section 6.23) element, and one additional
247   element which represents the type of notification being conveyed in
248   the message.  That child element will typically contain additional
249   content that describes the notification.
250
251   Each notification resource has a CS:notificationtype (Section 4.2.1)
252   property which contains as its single child element an empty element
253   that matches the child element of the notification resource XML
254   document root.  Any attributes on the child element in the XML
255   document are also present in the property child element.
256
257   Notifications are automatically generated by the server (perhaps in
258   response to a client action) with an appropriate resource stored in
259   the notifications collection of the user to whom the notification is
260   targeted.  Clients SHOULD monitor the notification collection looking
261   for new notification resources.  When doing so, clients SHOULD look
262   at the CS:notificationtype (Section 4.2.1) property to ensure that
263   the notification is of a type that the client can handle.  Once a
264   client has handled the notification in whatever way is appropriate it
265   SHOULD delete the notification resource.  Servers MAY delete
266   notification resources on their own if they determine that the
267   notifications are no longer relevant or valid.  Servers MAY coalesce
268   notifications as appropriate.
269
2704.1.  Additional Principal Properties
271
272   This section defines new properties for WebDAV principal resources as
273   defined in RFC3744 [RFC3744].  These properties are likely to be
274   protected but the server MAY allow them to be written by appropriate
275   users.
276
277
278
279Daboo & York                                                    [Page 5]
280
281                      CalDAV Sharing and Publishing         October 2010
282
283
2844.1.1.  CS:notification-URL Property
285
286   Name:  notification-URL
287
288   Namespace:  http://calendarserver.org/ns/
289
290   Purpose:  Identify the URL of the notification collection owned by
291      the associated principal resource.
292
293   Protected:  This property SHOULD be protected.
294
295   PROPFIND behavior:  This property SHOULD NOT be returned by a
296      PROPFIND allprop request (as defined in Section 14.2 of
297      [RFC4918]).
298
299   COPY/MOVE behavior:  This property value SHOULD be preserved in COPY
300      and MOVE operations.
301
302   Description:  This property is needed for a client to determine where
303      the notification collection of the current user is located so that
304      processing of notification messages can occur.  If not present,
305      then the associated calendar user is not enabled for notification
306      messages on the server.
307
308   Definition:
309
310   <!ELEMENT notification-URL (DAV:href)>
311
3124.2.  Properties on Notification Resources
313
314   The following new WebDAV properties are defined for notification
315   resources.
316
3174.2.1.  CS:notificationtype Property
318
319   Name:  notificationtype
320
321   Namespace:  http://calendarserver.org/ns/
322
323   Purpose:  Identify the type of notification of the corresponding
324      resource.
325
326   Protected:  This property MUST be protected.
327
328   PROPFIND behavior:  This property SHOULD NOT be returned by a
329      PROPFIND allprop request (as defined in Section 14.2 of
330      [RFC4918]).
331
332
333
334
335Daboo & York                                                    [Page 6]
336
337                      CalDAV Sharing and Publishing         October 2010
338
339
340   COPY/MOVE behavior:  This property value MUST be preserved in COPY
341      and MOVE operations.
342
343   Description:  This property allows a client, via a PROPFIND Depth:1
344      request, to quickly find notification messages that the client can
345      handle in a notification collection.  The single child element is
346      the notification resource root element's child defining the
347      notification itself.  This element MUST be empty, though any
348      attributes on the element in the notification resource MUST be
349      present in the property element.
350
351   Definition:
352
353   <!ELEMENT notificationtype (invite-notification | invite-reply)>
354   <!-- Child elements are empty but will have appropriate attributes.
355        Any valid notification message child element can appear.-->
356
357
3585.  Shared Calendaring
359
3605.1.  Feature Discovery
361
362   A server that supports the features described in this document MUST
363   include "calendarserver-sharing" as a field in the DAV response
364   header from an OPTIONS request on any resource that supports these
365   features.
366
3675.2.  Additional Properties for Calendars
368
369   The following new or modified WebDAV properties are defined for
370   calendar collections and used to view or manipulate shared calendar
371   features.
372
3735.2.1.  DAV:resourcetype Property
374
375   Calendar collections that are shared have addition elements listed in
376   their DAV:resourcetype property in addition to DAV:collection and
377   CALDAV:calendar.
378
379   o  CS:shared-owner (Section 6.1): used to indicate that the calendar
380      is owned by the current user and is being shared by them.
381
382   o  CS:shared (Section 6.2): used to indicate that the calendar is
383      owned by another user and is being shared to the current user.
384
385
386
387
388
389
390
391Daboo & York                                                    [Page 7]
392
393                      CalDAV Sharing and Publishing         October 2010
394
395
3965.2.2.  CS:invite Property
397
398   Name:  invite
399
400   Namespace:  http://calendarserver.org/ns/
401
402   Purpose:  Used to show to whom a calendar has been shared.
403
404   Protected:  This property MUST be protected.
405
406   PROPFIND behavior:  This property SHOULD NOT be returned by a
407      PROPFIND allprop request (as defined in Section 14.2 of
408      [RFC4918]).
409
410   COPY/MOVE behavior:  This property value MUST be preserved in COPY
411      and MOVE operations.
412
413   Description:  This WebDAV property is present on a calendar
414      collection resource that has been shared by the owner.  It MUST
415      NOT appear on the calendar collection resources of the sharees of
416      the calendar.  It provides a list of users to whom the calendar
417      has been shared, along with the "status" of the sharing invites
418      sent to each user.
419
420   Definition:
421
422   <!ELEMENT invite (user*)>
423
4245.2.3.  CS:allowed-sharing-modes Property
425
426   Name:  allowed-sharing-modes
427
428   Namespace:  http://calendarserver.org/ns/
429
430   Purpose:  Used to show which modes of sharing are supported on a
431      calendar collection.
432
433   Protected:  This property MUST be protected.
434
435   PROPFIND behavior:  This property SHOULD NOT be returned by a
436      PROPFIND allprop request (as defined in Section 14.2 of
437      [RFC4918]).
438
439   COPY/MOVE behavior:  This property value MUST be preserved in COPY
440      and MOVE operations.
441
442
443
444
445
446
447Daboo & York                                                    [Page 8]
448
449                      CalDAV Sharing and Publishing         October 2010
450
451
452   Description:  This WebDAV property is present on a calendar
453      collection resource that can been shared or published.  It
454      provides a list of options indicating what sharing modes are
455      allowed as per Section 5.5.2.
456
457   Definition:
458
459   <!ELEMENT allowed-sharing-modes
460             (can-be-shared?, can-be-published?)>
461
4625.2.4.  CS:shared-url Property
463
464   Name:  shared-url
465
466   Namespace:  http://calendarserver.org/ns/
467
468   Purpose:  Indicates the URL of the owner's copy of a shared calendar.
469
470   Protected:  This property MUST be protected.
471
472   PROPFIND behavior:  This property SHOULD NOT be returned by a
473      PROPFIND allprop request (as defined in Section 14.2 of
474      [RFC4918]).
475
476   COPY/MOVE behavior:  This property value MUST be preserved in COPY
477      and MOVE operations.
478
479   Description:  This WebDAV property is present on a shared calendar
480      collection resource that appears in a sharee's calendar home
481      collection.  Its content is a single DAV:href element whose value
482      is the URL of the sharer's calendar being shared.
483
484   Definition:
485
486   <!ELEMENT shared-url (DAV:href)>
487
4885.3.  Sharer Actions on Shared Calendars
489
4905.3.1.  Creating a Shared Calendar
491
492   To create a shared calendar, clients use the MKCALENDAR [RFC4791] or
493   extended MKCOL [RFC5689] requests, and include a DAV:resourcetype
494   property to be set upon creation.  That property MUST contain DAV:
495   collection, CALDAV:calendar and CS:shared-owner child elements to
496   enable sharing.
497
498
499
500
501
502
503Daboo & York                                                    [Page 9]
504
505                      CalDAV Sharing and Publishing         October 2010
506
507
5085.3.1.1.  Example: Successful MKCALENDAR Request
509
510   This example shows how the MKCALENDAR request is used to create a
511   shared calendar collection.  The response body is empty as the
512   request completed successfully.
513
514   >> Request <<
515
516   MKCALENDAR /calendars/users/cyrus/shared/ HTTP/1.1
517   Host: calendar.example.com
518   Content-Type: application/xml; charset="utf-8"
519   Content-Length: xxxx
520
521   <?xml version="1.0" encoding="utf-8" ?>
522   <C:mkcalendar xmlns:D="DAV:"
523                 xmlns:C="urn:ietf:params:xml:ns:caldav"
524                 xmlns:CS="http://calendarserver.org/ns/">
525     <D:set>
526       <D:prop>
527         <D:resourcetype>
528           <D:collection/>
529           <C:calendar/>
530           <CS:shared-owner/>
531         </D:resourcetype>
532       </D:prop>
533     </D:set>
534   </C:mkcalendar>
535
536   >> Response <<
537
538   HTTP/1.1 201 Created
539   Cache-Control: no-cache
540   Date: Sat, 11 Nov 2006 09:32:12 GMT
541
5425.3.1.2.  Example: Successful Extended MKCOL Request
543
544   This example shows how the extended MKCOL request is used to create a
545   shared calendar collection.  The response body is empty as the
546   request completed successfully.
547
548
549
550
551
552
553
554
555
556
557
558
559Daboo & York                                                   [Page 10]
560
561                      CalDAV Sharing and Publishing         October 2010
562
563
564   >> Request <<
565
566   MKCOL /calendars/users/cyrus/shared/ HTTP/1.1
567   Host: calendar.example.com
568   Content-Type: application/xml; charset="utf-8"
569   Content-Length: xxxx
570
571   <?xml version="1.0" encoding="utf-8" ?>
572   <D:mkcol xmlns:D="DAV:"
573                 xmlns:C="urn:ietf:params:xml:ns:caldav"
574                 xmlns:CS="http://calendarserver.org/ns/">
575     <D:set>
576       <D:prop>
577         <D:resourcetype>
578           <D:collection/>
579           <C:calendar/>
580           <CS:shared-owner/>
581         </D:resourcetype>
582       </D:prop>
583     </D:set>
584   </D:mkcol>
585
586   >> Response <<
587
588   HTTP/1.1 201 Created
589   Cache-Control: no-cache
590   Date: Sat, 11 Nov 2006 09:32:12 GMT
591
5925.3.2.  Sharing an Existing Calendar
593
594   Sharing an existing calendar can be accomplished in two ways.  One
595   option is to use a PROPPATCH request to set the DAV:resourcetype
596   property to include CS:shared-owner as a child element.  Another
597   option is to add sharee's directly to the calendar collection (as
598   described in Section 5.3.3) - that action MUST upgrade a non-shared
599   calendar to a shared calendar when it completes successfully,
600   assuming that such an upgrade is allowed as per Section 5.5.2.
601
6025.3.2.1.  Example: Successful PROPPATCH Request
603
604   This example shows how the PROPPATCH request is used to upgrade to a
605   shared calendar collection.
606
607
608
609
610
611
612
613
614
615Daboo & York                                                   [Page 11]
616
617                      CalDAV Sharing and Publishing         October 2010
618
619
620   >> Request <<
621
622   PROPPATCH /calendars/users/cyrus/shared/ HTTP/1.1
623   Host: calendar.example.com
624   Content-Type: application/xml; charset="utf-8"
625   Content-Length: xxxx
626
627   <?xml version="1.0" encoding="utf-8" ?>
628   <D:propertyupdate xmlns:D="DAV:"
629                 xmlns:C="urn:ietf:params:xml:ns:caldav"
630                 xmlns:CS="http://calendarserver.org/ns/">
631     <D:set>
632       <D:prop>
633         <D:resourcetype>
634           <D:collection/>
635           <C:calendar/>
636           <CS:shared-owner/>
637         </D:resourcetype>
638       </D:prop>
639     </D:set>
640   </D:propertyupdate>
641
642   >> Response <<
643
644   HTTP/1.1 207 Multi-Status
645   Date: Sat, 11 Nov 2006 09:32:12 GMT
646   Content-Type: application/xml; charset="utf-8"
647   Content-Length: xxxx
648
649   <?xml version="1.0" encoding="utf-8" ?>
650   <D:multistatus xmlns:D="DAV:">
651     <D:response>
652       <D:href>/calendars/users/cyrus/shared/</D:href>
653       <D:propstat>
654         <D:prop>
655           <D:resourcetype/>
656         </D:prop>
657         <D:status>HTTP/1.1 200 OK</D:status>
658       </D:propstat>
659     </D:response>
660   </D:multistatus>
661
6625.3.3.  Manipulating Sharees of a Shared Calendar
663
664   The sharer of a shared calendar is able to manipulate the sharee list
665   by issuing a POST request targeted at the shared calendar collection
666   resource.  The POST request MUST contain an XML document as its body
667   with the root element being CS:share (Section 6.24).
668
669
670
671Daboo & York                                                   [Page 12]
672
673                      CalDAV Sharing and Publishing         October 2010
674
675
676   The CS:share (Section 6.24) element in the POST requests MUST contain
677   one or more CS:set (Section 6.25) or CS:remove (Section 6.26)
678   elements.  For each CS:set (Section 6.25) element, the server MUST
679   add the specified sharee access to the shared calendar.  For each CS:
680   remove (Section 6.26) element the server MUST remove the specified
681   sharee access from the shared calendar.  In each case the server MUST
682   send a notification message to any sharees whose status is changed
683   (added, modified or removed), indicating to them a change in status
684   for the shared calendar.  The server SHOULD NOT send notification
685   messages to sharees whose status is unchanged.
686
687   Sharee's are identified via a DAV:href element whose value is either
688   a principal-URL for a sharee hosted on the same server, a calendar
689   user address or email address.  In the case of the later two, the
690   sharee might not be a user on the same server - though in that case
691   how invitations are sent or access enabled is out of scope for this
692   specification.  A server MAY change the sharee's "address" to any
693   suitable alternative that it might prefer when returning the list of
694   sharees via the CS:invite property (Section 5.2.2).
695
696   The client MAY include a CS:common-name (Section 6.19) element in the
697   CS:set (Section 6.25) element.  When provided, the value represents
698   the common name for the sharee, and is returned in the list of
699   sharees via the CS:invite property (Section 5.2.2).  The server MAY
700   change this to a suitable alternative when it is able to match the
701   sharee to a known user.  If absent from the client request, the
702   server SHOULD add a CS:common-name when it is able to match the
703   sharee with a known user, and a common name for that user can be
704   determined.
705
706   When the sharee list on a shared calendar is changed, the server MUST
707   send notifications to each sharee to update them on their current
708   sharing status.  This is accomplished by sending a CS:invite-
709   notification (Section 6.15) notification to each sharee.
710
7115.3.3.1.  Example: Successful Sharee Add Request
712
713   This example shows how to add a single sharee (with calendar user
714   address "mailto:eric@example.com") to a shared calendar with CS:read-
715   write access.
716
717
718
719
720
721
722
723
724
725
726
727Daboo & York                                                   [Page 13]
728
729                      CalDAV Sharing and Publishing         October 2010
730
731
732   >> Request <<
733
734   POST /calendars/users/cyrus/shared/ HTTP/1.1
735   Host: calendar.example.com
736   Content-Type: application/xml; charset="utf-8"
737   Content-Length: xxxx
738
739   <?xml version="1.0" encoding="utf-8" ?>
740   <CS:share xmlns:D="DAV:"
741                 xmlns:CS="http://calendarserver.org/ns/">
742     <CS:set>
743       <D:href>mailto:eric@example.com</D:href>
744       <CS:common-name>Eric York</CS:common-name>
745       <CS:summary>Shared workspace</CS:summary>
746       <CS:read-write />
747     </CS:set>
748   </CS:share>
749
750   >> Response <<
751
752   HTTP/1.1 200 OK
753   Cache-Control: no-cache
754   Date: Sat, 11 Nov 2006 09:32:12 GMT
755
7565.3.3.2.  Example: Successful Multiple Sharee Change Request
757
758   This example shows how multiple sharee's can be manipulated in a
759   single request.  The sharee with calendar user address
760   "mailto:eric@example.com" has their access downgraded to CS:read,
761   whilst another sharee is removed from the access list entirely.
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783Daboo & York                                                   [Page 14]
784
785                      CalDAV Sharing and Publishing         October 2010
786
787
788   >> Request <<
789
790   POST /calendars/users/cyrus/shared/ HTTP/1.1
791   Host: calendar.example.com
792   Content-Type: application/xml; charset="utf-8"
793   Content-Length: xxxx
794
795   <?xml version="1.0" encoding="utf-8" ?>
796   <CS:share xmlns:D="DAV:"
797                 xmlns:CS="http://calendarserver.org/ns/">
798     <CS:set>
799       <D:href>mailto:eric@example.com</D:href>
800       <CS:summary>Shared workspace</CS:summary>
801       <CS:read-write />
802     </CS:set>
803     <CS:remove>
804       <D:href>mailto:wilfredo@example.com</D:href>
805     </CS:remove>
806   </CS:share>
807
808   >> Response <<
809
810   HTTP/1.1 200 OK
811   Cache-Control: no-cache
812   Date: Sat, 11 Nov 2006 09:32:12 GMT
813
8145.4.  Sharee Actions on Shared Calendars
815
8165.4.1.  Replying to a Sharing Invite
817
818   When a sharee is invited to a shared calendar they can accept or
819   decline the invite by issuing a POST request to the sharee's calendar
820   home collection resource.  The POST request MUST contain an XML
821   document as its body with the root element being CS:invite-reply
822   (Section 6.20).
823
824   The CS:invite-reply (Section 6.20) element in the POST request
825   specifies the sharee who is replying in the DAV:href element, the
826   accept or decline action via the CS:invite-accepted or CS:invite-
827   declined elements, the URL of the shared calendar in the CS:hosturl
828   element, the unique identifier of the invite to which it is a reply
829   in the CS:in-reply-to element, and an optional CS:summary element.
830
831   The response to a POST request that accepts a shared calendar invite
832   MUST be an XML document containing CS:shared-as (Section 6.27) as its
833   root element.  That root element contains a single DAV:href element
834   whose content is the URI of the shared calendar in the sharee's
835   calendar home created by the invite acceptance.
836
837
838
839Daboo & York                                                   [Page 15]
840
841                      CalDAV Sharing and Publishing         October 2010
842
843
844   When the sharee replies to an invite, the server SHOULD send a
845   notification to the sharer to update them on the change in the sharee
846   state.  This is accomplished by sending a CS:invite-reply
847   (Section 6.20) notification to the sharer.
848
8495.4.2.  Removing a Shared Calendar
850
851   To remove a shared calendar from a sharee's calendar home collection
852   a DELETE request is targeted at the shared calendar URI.  When such a
853   request is received the server MUST remove the shared calendar from
854   the sharee's calendar home and automatically update the sharee's
855   status in the sharer's calendar's CS:invite property.
856
8575.5.  General Considerations
858
8595.5.1.  Access Levels
860
861   Two levels of access ca be granted by a sharer to any sharee.  These
862   are governed by the CS:access element used in the CS:invite/CS:user
863   element that specifies a shared user invite.  CS:access contains a
864   single empty element that defines the type of access granted:
865
866   CS:read  When present this indicates that sharees can read calendar
867      data but cannot change it.
868
869   CS:read-write  When present this indicates that sharees can read and
870      write calendar data.
871
8725.5.2.  Allowing or Disallowing Sharing
873
874   Servers MAY support calendar sharing on a per-calendar basis - e.g.,
875   they could treat some calendars as always private (cannot be shared)
876   or always public (always shared).  As a result clients need a way to
877   determine which calendar could be shared so they can enable or
878   disable sharing options on a per-calendar basis.
879
880   This specification adds a CS:allowed-sharing-modes (Section 5.2.3)
881   WebDAV property which servers can return on calendar collection
882   resources.  This property contains XML elements that describe which
883   sharing or publishing capabilities can be supported by the
884   corresponding calendar collection:
885
886      CS:can-be-shared (Section 6.3): when present indicates that the
887      calendar collection can be shared.  When not present, the calendar
888      collection cannot be shared.
889
890      CS:can-be-published (Section 6.4): when present indicates that the
891      calendar collection can be published.  When not present, the
892
893
894
895Daboo & York                                                   [Page 16]
896
897                      CalDAV Sharing and Publishing         October 2010
898
899
900      calendar collection cannot be published.
901
902   When not present on a calendar collection, sharing or publishing of
903   that calendar is not allowed.  Clients SHOULD NOT attempt to use
904   requests to enable sharing or publishing targeted at those calendar
905   collections.
906
9075.5.3.  Per-user WebDAV Properties
908
909   Servers MUST support "per-user" WebDAV properties on shared calendar
910   collections and MAY support them on calendar object resources within
911   shared calendar collections.  A "per-user" WebDAV property is one
912   whose value can be set and retrieved independently by each user with
913   appropriate access rights. e.g., user "A" changes the DAV:displayname
914   property on a shared calendar in their calendar home to "My
915   calendar", and user "B" changes the same property to "Shared" on the
916   same shared calendar in their calendar home.  When each user
917   retrieves the property value they will see their own last stored
918   value and not the value of the other user.
919
920   For shared calendars, the server MUST allow all users to write "per-
921   user" WebDAV properties on the shared calendar collection and MAY
922   allow property writes on calendar object resources within the shared
923   calendar collection.  This is required even in the case where the
924   sharee has been granted read access only (i.e., the ability to change
925   calendar data is disallowed).  This requirement ensures that sharees
926   can always change "personal" properties such as calendar colors and
927   display names.
928
929   Servers MUST treat the following properties as "per-user":
930
931      DAV:displayname
932
933      CALDAV:calendar-description
934
935      CALDAV:schedule-calendar-transp
936
937      ICAL:calendar-color
938
939   Servers MAY treat any dead property as per-user.
940
941   Servers MUST NOT treat live properties as per-user.
942
9435.5.4.  Per-user Calendar Data
944
945   Servers MUST support "per-user" calendar data in calendar object
946   resources stored in shared calendars.  This allows each sharee and
947   the sharer to store their own alarms and free busy transparency
948
949
950
951Daboo & York                                                   [Page 17]
952
953                      CalDAV Sharing and Publishing         October 2010
954
955
956   status without "interfering" with other users who also have access to
957   the same calendar object resources.
958
959   For calendaring object resources in shared calendar collections, the
960   server MUST treat the following iCalendar data objects as per-user:
961
962      TRANSP property
963
964      VALARM component
965
966   Servers MAY treat any non-standard X- iCalendar properties as per-
967   user.
968
969   When handling per-user data in recurring components, servers SHOULD
970   eliminate overridden instances when returning iCalendar data to
971   clients in the case where there are no differences between the
972   overridden component and the instance that could be derived from the
973   "master" recurrence component.  For example, consider a daily
974   recurring event, Monday through Friday, initially defined without any
975   overridden instances, that is in a shared calendar.  If user "A"
976   overrides the Tuesday instance and adds their own "VALARM" component
977   only, then when user "A" later retrieves the data again they would
978   see that overridden instance, but when user "B" does so, they would
979   not.  This ensures that each user sees the most "compact"
980   representation of the calendar data.
981
9825.5.5.  Scheduling
983
984   CalDAV Scheduling [I-D.desruisseaux-caldav-sched] defines how a
985   CalDAV server carries out scheduling operations when calendar object
986   resources are created, modified or deleted and include "ORGANIZER"
987   and "ATTENDEE" iCalendar properties.
988
989   When calendar object resources are created, modified or deleted in
990   shared calendars by sharees, the following restrictions apply:
991
992   1.  The "ORGANIZER" iCalendar property value in the iCalendar data
993       MUST match a calendar user address of the sharer (owner) of the
994       shared calendar.  The DAV:owner WebDAV property MUST be present
995       on a shared calendar and MUST provide a reference to a principal-
996       URL of the sharer (owner) of the shared calendar.  Clients can
997       use this value to determine what the allowed "ORGANIZER"
998       iCalendar property values are.  The server MUST reject any
999       attempt by a sharee to create an iCalendar component with an
1000       "ORGANIZER" property value other than the sharer (owner) of the
1001       shared calendar.
1002
1003
1004
1005
1006
1007Daboo & York                                                   [Page 18]
1008
1009                      CalDAV Sharing and Publishing         October 2010
1010
1011
1012   2.  The server MUST reject any attempt by a sharee to MOVE a calendar
1013       object resource in a shared calendar to some other collection.
1014
1015   3.  When a sharee is listed as an Attendee in a calendar object
1016       resource in a shared calendar, and write access is granted, the
1017       sharee is allowed to change not only iCalendar data related to
1018       the Organizer, but also data related to the Attendee. i.e., a
1019       sharee can change their own participation status on the
1020       "ATTENDEE" iCalendar property referring to them.  Additionally,
1021       if the sharee is not listed as an Attendee, and write access is
1022       granted, the sharee can add themselves as an Attendee.
1023
1024   4.  The default calendar collection defined in Section 6.3 of
1025       [I-D.desruisseaux-caldav-sched] MUST NOT be a calendar shared to
1026       the corresponding calendar user.
1027
1028   Following are additional considerations for scheduling with shared
1029   calendars:
1030
1031   1.  A scheduled iCalendar component could appear in more than one
1032       calendar collection within a sharee's calendar home if the sharee
1033       is an Attendee and the Organizer or other Attendees have shared a
1034       calendar with the sharee that includes their copies of the
1035       iCalendar component.  It is important to note that the scheduled
1036       component in the shared calendar could have different access
1037       rights than the one in the sharee's owned calendar.
1038
1039   2.  A scheduled iCalendar component appearing in a sharee's shared
1040       calendar could include the sharee as an Attendee.  For recurring
1041       events, it is possible for the sharee to only be listed as an
1042       Attendee in some instances, as opposed to all.  Clients will need
1043       to be aware of this when allowing sharee's to set their own
1044       participation status.
1045
1046   In addition, when a shared calendar is first accepted by a sharee,
1047   the server SHOULD set the CALDAV:schedule-calendar-transp property to
1048   the value CALDAV:transparent to ensure newly accepted shared
1049   calendars do not contribute to the sharee's freebusy time until the
1050   sharee explicitly requests it.
1051
1052
10536.  XML Element Definitions
1054
10556.1.  CS:shared-owner
1056
1057
1058
1059
1060
1061
1062
1063Daboo & York                                                   [Page 19]
1064
1065                      CalDAV Sharing and Publishing         October 2010
1066
1067
1068   Name:  shared-owner
1069
1070   Namespace:  http://calendarserver.org/ns/
1071
1072   Purpose:  Used to indicate that a calendar is being shared by the
1073      owner.
1074
1075   Description:  This property appears in the DAV:resourcetype property
1076      on the calendar collection resource shared by a sharer.  See
1077      Section 5.2.
1078
1079   Definition:
1080
1081   <!ELEMENT shared-owner EMPTY>
1082
10836.2.  CS:shared
1084
1085   Name:  shared
1086
1087   Namespace:  http://calendarserver.org/ns/
1088
1089   Purpose:  Used to indicate that a calendar is being shared to a
1090      sharee.
1091
1092   Description:  This property appears in the DAV:resourcetype property
1093      on a calendar collection resource that is shared to a sharee and
1094      appears in the sharee's calendar home collection.  See
1095      Section 5.2.
1096
1097   Definition:
1098
1099   <!ELEMENT shared EMPTY>
1100
11016.3.  CS:can-be-shared
1102
1103   Name:  can-be-shared
1104
1105   Namespace:  http://calendarserver.org/ns/
1106
1107   Purpose:  Used to indicate that a calendar can be shared.
1108
1109   Description:  This element indicates that a calendar can be shared
1110      with other users.  See Section 5.2.3
1111
1112   Definition:
1113
1114   <!ELEMENT can-be-shared EMPTY>
1115
1116
1117
1118
1119Daboo & York                                                   [Page 20]
1120
1121                      CalDAV Sharing and Publishing         October 2010
1122
1123
11246.4.  CS:can-be-published
1125
1126   Name:  can-be-published
1127
1128   Namespace:  http://calendarserver.org/ns/
1129
1130   Purpose:  Used to indicate that a calendar can be published.
1131
1132   Description:  This element indicates that a calendar can be published
1133      to anyone.  See Section 5.2.3
1134
1135   Definition:
1136
1137   <!ELEMENT can-be-published EMPTY>
1138
11396.5.  CS:user
1140
1141   Name:  user
1142
1143   Namespace:  http://calendarserver.org/ns/
1144
1145   Purpose:  Used to show status of sharing invites sent to sharees.
1146
1147   Description:  This element provides the "status" of a sharing invite
1148      sent to a particular user.  See Section 5.2.2.
1149
1150   Definition:
1151
1152   <!ELEMENT user (DAV:href, common-name?, (invite-noresponse |
1153                   invite-accepted | invite-declined | invite-invalid),
1154                   access, summary?)>
1155
11566.6.  CS:invite-noresponse
1157
1158   Name:  invite-noresponse
1159
1160   Namespace:  http://calendarserver.org/ns/
1161
1162   Purpose:  Sharing invite status.
1163
1164   Description:  When used in a CS:user (Section 6.5) element, this
1165      element is used to indicate that the sharee has never replied to
1166      the corresponding sharing invite.  When used in a CS:invite-
1167      notification (Section 6.15) element, this element is used to
1168      indicate to the sharee that a sharing reply is needed.
1169
1170
1171
1172
1173
1174
1175Daboo & York                                                   [Page 21]
1176
1177                      CalDAV Sharing and Publishing         October 2010
1178
1179
1180   Definition:
1181
1182   <!ELEMENT invite-noresponse EMPTY>
1183
11846.7.  CS:invite-deleted
1185
1186   Name:  invite-deleted
1187
1188   Namespace:  http://calendarserver.org/ns/
1189
1190   Purpose:  Sharing invite status.
1191
1192   Description:  When used in a CS:invite-notification (Section 6.15)
1193      element, this element is used to indicate to the sharee that a
1194      shared calendar has been unshared by the sharer.
1195
1196   Definition:
1197
1198   <!ELEMENT invite-deleted EMPTY>
1199
12006.8.  CS:invite-accepted
1201
1202   Name:  invite-accepted
1203
1204   Namespace:  http://calendarserver.org/ns/
1205
1206   Purpose:  Sharing invite status.
1207
1208   Description:  When used in a CS:user (Section 6.5) element, this
1209      element is used to indicate that the sharee has accepted the
1210      corresponding sharing invite.  When used in a CS:invite-
1211      notification (Section 6.15) element, this element is used to
1212      indicate to the sharee that the sharing invite is an update for
1213      one they previously accepted.
1214
1215   Definition:
1216
1217   <!ELEMENT invite-accepted EMPTY>
1218
12196.9.  CS:invite-declined
1220
1221   Name:  invite-declined
1222
1223   Namespace:  http://calendarserver.org/ns/
1224
1225
1226
1227
1228
1229
1230
1231Daboo & York                                                   [Page 22]
1232
1233                      CalDAV Sharing and Publishing         October 2010
1234
1235
1236   Purpose:  Sharing invite status.
1237
1238   Description:  When used in a CS:user (Section 6.5) element, this
1239      element is used to indicate that the sharee has declined the
1240      corresponding sharing invite.  When used in a CS:invite-
1241      notification (Section 6.15) element, this element is used to
1242      indicate to the sharee that the sharing invite is an update for
1243      one they previously declined.
1244
1245   Definition:
1246
1247   <!ELEMENT invite-declined EMPTY>
1248
12496.10.  CS:invite-invalid
1250
1251   Name:  invite-invalid
1252
1253   Namespace:  http://calendarserver.org/ns/
1254
1255   Purpose:  Sharing invite status.
1256
1257   Description:  When used in a CS:user (Section 6.5) element, this
1258      element is used to indicate that the corresponding sharee is not a
1259      valid calendar user known to the server.
1260
1261   Definition:
1262
1263   <!ELEMENT invite-invalid EMPTY>
1264
12656.11.  CS:access
1266
1267   Name:  access
1268
1269   Namespace:  http://calendarserver.org/ns/
1270
1271   Purpose:  Shared calendar access level.
1272
1273   Description:  When used in a CS:user (Section 6.5) element, this
1274      element is used to indicate the sharing access level granted to
1275      the corresponding sharee.
1276
1277   Definition:
1278
1279   <!ELEMENT invite-invalid (read | read-write)>
1280
1281
1282
1283
1284
1285
1286
1287Daboo & York                                                   [Page 23]
1288
1289                      CalDAV Sharing and Publishing         October 2010
1290
1291
12926.12.  CS:read
1293
1294   Name:  read
1295
1296   Namespace:  http://calendarserver.org/ns/
1297
1298   Purpose:  Shared calendar access level privilege.
1299
1300   Description:  Indicates that the access level granted only allows
1301      sharees to read data in the shared calendar (though they can write
1302      per-user data (Section 5.5.4)).
1303
1304   Definition:
1305
1306   <!ELEMENT read EMPTY>
1307
13086.13.  CS:read-write
1309
1310   Name:  read-write
1311
1312   Namespace:  http://calendarserver.org/ns/
1313
1314   Purpose:  Shared calendar access level privilege.
1315
1316   Description:  Indicates that the access level granted allows sharees
1317      to read and write all data in the shared calendar, with the
1318      exception of components that would trigger scheduling.
1319
1320   Definition:
1321
1322   <!ELEMENT read-write EMPTY>
1323
13246.14.  CS:summary
1325
1326   Name:  summary
1327
1328   Namespace:  http://calendarserver.org/ns/
1329
1330   Purpose:  Summary or title of shared calendar.
1331
1332   Description:  A brief description of a shared calendar.  This can be
1333      used by sharers to communicate the nature of a shared calendar to
1334      sharees, as well as used by sharees to indicate back to the sharer
1335      how each sharee is refering to the shared calendar.
1336
1337
1338
1339
1340
1341
1342
1343Daboo & York                                                   [Page 24]
1344
1345                      CalDAV Sharing and Publishing         October 2010
1346
1347
1348   Definition:
1349
1350   <!ELEMENT summary (#PCDATA)>
1351
13526.15.  CS:invite-notification
1353
1354   Name:  invite-notification
1355
1356   Namespace:  http://calendarserver.org/ns/
1357
1358   Purpose:  A notification used as a shared calendar invite.
1359
1360   Description:  Defines a notification message sent automatically by
1361      the server when a sharer adds, changes or removes a sharee from a
1362      shared calendar.  The DAV:href element specifies the calendar user
1363      address of the sharee to whom the message was sent.
1364
1365   Definition:
1366
1367   <!ELEMENT invite-notification (uid, DAV:href,
1368                                  (invite-noresponse | invite-deleted |
1369                                   invite-accepted | invite-declined),
1370                                  access, hosturl, organizer, summary?>
1371
13726.16.  CS:uid
1373
1374   Name:  uid
1375
1376   Namespace:  http://calendarserver.org/ns/
1377
1378   Purpose:  Unique identifier.
1379
1380   Description:  A unique identifier for an invitation to a shared
1381      calendar.
1382
1383   Definition:
1384
1385   <!ELEMENT uid (#PCDATA)>
1386
13876.17.  CS:hosturl
1388
1389   Name:  hosturl
1390
1391   Namespace:  http://calendarserver.org/ns/
1392
1393
1394
1395
1396
1397
1398
1399Daboo & York                                                   [Page 25]
1400
1401                      CalDAV Sharing and Publishing         October 2010
1402
1403
1404   Purpose:  Identifies the source URL of a shared calendar.
1405
1406   Description:  Contains a single DAV:href element that refers to the
1407      source of a shared calendar - i.e., the URL of the calendar shared
1408      by the sharer.
1409
1410   Definition:
1411
1412   <!ELEMENT hosturl (DAV:href)>
1413
14146.18.  CS:organizer
1415
1416   Name:  organizer
1417
1418   Namespace:  http://calendarserver.org/ns/
1419
1420   Purpose:  Identifies the sharer of a shared calendar.
1421
1422   Description:  Contains a single DAV:href element that identifies the
1423      calendar user address of the sharer of a shared calendar, and an
1424      optional CS:common-name element that matches that user.
1425
1426   Definition:
1427
1428   <!ELEMENT organizer (DAV:href, CS:common-name?)>
1429
14306.19.  CS:common-name
1431
1432   Name:  common-name
1433
1434   Namespace:  http://calendarserver.org/ns/
1435
1436   Purpose:  The common name of a sharer or sharee.
1437
1438   Description:  The common name is optionally provided by a client when
1439      adding a sharee and optionally included (or modified) by the
1440      server when returning results for sharers or sharees and in
1441      notifications.
1442
1443   Definition:
1444
1445   <!ELEMENT common-name (#PCDATA)>
1446
14476.20.  CS:invite-reply
1448
1449
1450
1451
1452
1453
1454
1455Daboo & York                                                   [Page 26]
1456
1457                      CalDAV Sharing and Publishing         October 2010
1458
1459
1460   Name:  invite-reply
1461
1462   Namespace:  http://calendarserver.org/ns/
1463
1464   Purpose:  A notification used as a reply to a shared calendar invite.
1465
1466   Description:  Defines a notification message sent automatically by
1467      the server when a sharee replies to a shared calendar invite.  The
1468      DAV:href element specifies the calendar user address of the sharee
1469      to whom the original invite message was sent.
1470
1471   Definition:
1472
1473   <!ELEMENT invite-reply (DAV:href,
1474                           (invite-accepted | invite-declined),
1475                           hosturl, in-reply-to, summary?>
1476
14776.21.  CS:in-reply-to
1478
1479   Name:  in-reply-to
1480
1481   Namespace:  http://calendarserver.org/ns/
1482
1483   Purpose:  Unique identifier.
1484
1485   Description:  Specifies the unique identifier of the inviate message
1486      that this notification message is a reply to.
1487
1488   Definition:
1489
1490   <!ELEMENT in-reply-to (#PCDATA)>
1491
14926.22.  CS:notification
1493
1494   Name:  notification
1495
1496   Namespace:  http://calendarserver.org/ns/
1497
1498   Purpose:  Notification message root element.
1499
1500   Description:  The root element used in notification resources.
1501
1502   Definition:
1503
1504   <!ELEMENT notification (CS:dtstamp,
1505                           (invite-notification | invite-reply)>
1506   <!-- Any notification type element can appear after CS:dtstamp,
1507        this specification defines only the two listed above -->
1508
1509
1510
1511Daboo & York                                                   [Page 27]
1512
1513                      CalDAV Sharing and Publishing         October 2010
1514
1515
15166.23.  CS:dtstamp
1517
1518   Name:  dtstamp
1519
1520   Namespace:  http://calendarserver.org/ns/
1521
1522   Purpose:  Date-time stamp.
1523
1524   Description:  Contains the date-time stamp corresponding to the
1525      creation of a notification message.
1526
1527   Definition:
1528
1529   <!ELEMENT dtstamp (#PCDATA)>
1530
15316.24.  CS:share
1532
1533   Name:  share
1534
1535   Namespace:  http://calendarserver.org/ns/
1536
1537   Purpose:  Describes changes to sharees.
1538
1539   Description:  The root element used in POST requests on calendars by
1540      sharers to manipulate the sharee list of a shared calendar.
1541
1542   Definition:
1543
1544   <!ELEMENT share (set | remove)*>
1545
15466.25.  CS:set
1547
1548   Name:  set
1549
1550   Namespace:  http://calendarserver.org/ns/
1551
1552   Purpose:  Sets access for a sharee.
1553
1554   Description:  Used to add or modify sharee access to a shared
1555      calendar.  The specified access to the shared calendar is given to
1556      the sharee.
1557
1558   Definition:
1559
1560   <!ELEMENT set (DAV:href, common-name?, summary?,
1561                  (read | read-write)>
1562
1563
1564
1565
1566
1567Daboo & York                                                   [Page 28]
1568
1569                      CalDAV Sharing and Publishing         October 2010
1570
1571
15726.26.  CS:remove
1573
1574   Name:  remove
1575
1576   Namespace:  http://calendarserver.org/ns/
1577
1578   Purpose:  Removes access for a sharee.
1579
1580   Description:  Used to remove sharee access to a shared calendar.  All
1581      access to the shared calendar is removed for the sharee.
1582
1583   Definition:
1584
1585   <!ELEMENT remove (DAV:href)>
1586
15876.27.  CS:shared-as
1588
1589   Name:  shared-as
1590
1591   Namespace:  http://calendarserver.org/ns/
1592
1593   Purpose:  Identifies a shared calendar.
1594
1595   Description:  Returned by the server for a POST request by a sharee
1596      accepting a shared calendar invite.  The DAV:href element
1597      specifies the URI of the calendar created by the acceptance.
1598
1599   Definition:
1600
1601   <!ELEMENT shared-as (DAV:href)>
1602
1603
16047.  Security Considerations
1605
1606   Per-user WebDAV properties and iCalendar data MUST only be accessible
1607   by the user that created them.
1608
1609   Alarms set by the sharer SHOULD NOT be propagated to sharees by
1610   default.  Clients SHOULD NOT automatically enable triggering of
1611   alarms on shared calendars that have just been accepted without
1612   confirmation by the user.
1613
1614   TBD
1615
1616
16178.  IANA Considerations
1618
1619   This document does not require any actions on the part of IANA.
1620
1621
1622
1623Daboo & York                                                   [Page 29]
1624
1625                      CalDAV Sharing and Publishing         October 2010
1626
1627
16289.  Acknowledgments
1629
1630   This specification is the result of discussions between the Apple
1631   calendar server and client teams.
1632
1633
163410.  Normative References
1635
1636   [I-D.desruisseaux-caldav-sched]
1637              Daboo, C. and B. Desruisseaux, "CalDAV Scheduling
1638              Extensions to WebDAV", draft-desruisseaux-caldav-sched-08
1639              (work in progress), August 2009.
1640
1641   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
1642              Requirement Levels", BCP 14, RFC 2119, March 1997.
1643
1644   [RFC3744]  Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
1645              Distributed Authoring and Versioning (WebDAV)
1646              Access Control Protocol", RFC 3744, May 2004.
1647
1648   [RFC4791]  Daboo, C., Desruisseaux, B., and L. Dusseault,
1649              "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
1650              March 2007.
1651
1652   [RFC4918]  Dusseault, L., "HTTP Extensions for Web Distributed
1653              Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
1654
1655   [RFC5689]  Daboo, C., "Extended MKCOL for Web Distributed Authoring
1656              and Versioning (WebDAV)", RFC 5689, September 2009.
1657
1658
1659Appendix A.  Change History
1660
1661   Changes in -02:
1662
1663   1.  Removed read-write-shared access mode - now a server that does
1664       not support shared scheduling should advertise that via a DAV
1665       header
1666
1667
1668Appendix B.  Change History
1669
1670   Changes in -01:
1671
1672   1.  Added CS:shared-url property
1673
1674   2.  Clarified that notifications are only required to be sent when
1675       sharee status is changed
1676
1677
1678
1679Daboo & York                                                   [Page 30]
1680
1681                      CalDAV Sharing and Publishing         October 2010
1682
1683
1684Authors' Addresses
1685
1686   Cyrus Daboo
1687   Apple Inc.
1688   1 Infinite Loop
1689   Cupertino, CA  95014
1690   USA
1691
1692   Email: cyrus@daboo.name
1693   URI:   http://www.apple.com/
1694
1695
1696   Eric York
1697   Apple Inc.
1698   1 Infinite Loop
1699   Cupertino, CA  95014
1700   USA
1701
1702   Email:
1703   URI:   http://www.apple.com/
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735Daboo & York                                                   [Page 31]
1736
+
+
+ Note: See TracBrowser + for help on using the browser. +
+
+
+
+ + + + + +
+
+
+
+ +
+
+
+
+ + +
+ + \ No newline at end of file diff --git a/dav/SabreDAV/docs/rfc5785.txt b/dav/SabreDAV/docs/rfc5785.txt new file mode 100644 index 00000000..c28ccf6b --- /dev/null +++ b/dav/SabreDAV/docs/rfc5785.txt @@ -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] + +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 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] + +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] + +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] + +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] + +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, + . + + [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, + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 6] + +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] + +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] + diff --git a/dav/SabreDAV/examples/webserver/apache2_vhost.conf b/dav/SabreDAV/examples/webserver/apache2_vhost.conf index 5a816e1f..bb374eb0 100644 --- a/dav/SabreDAV/examples/webserver/apache2_vhost.conf +++ b/dav/SabreDAV/examples/webserver/apache2_vhost.conf @@ -7,27 +7,27 @@ # settings as well. - # 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 diff --git a/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf b/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf index a034d7fc..607254c6 100644 --- a/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf +++ b/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf @@ -6,16 +6,16 @@ # This configuration assumes CGI or FastCGI is used. - # 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}] diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php index 064cad7c..480e6329 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php @@ -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 index 00000000..881538ab --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php @@ -0,0 +1,231 @@ + 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 index 00000000..ad905b1a --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php @@ -0,0 +1,44 @@ +caldavBackend = $caldavBackend; $this->principalBackend = $principalBackend; diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php index 1adf8689..318a4fb5 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php @@ -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; diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php index 4bcd32cd..8f674840 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php @@ -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. diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php index 5c4265c5..eb62eea7 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php @@ -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 index 00000000..4ac617d2 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php @@ -0,0 +1,32 @@ +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 index 00000000..8f6cb260 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php @@ -0,0 +1,80 @@ +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 index 00000000..eb873af3 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php @@ -0,0 +1,22 @@ +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 index 00000000..21c86632 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php @@ -0,0 +1,158 @@ +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; + + } + +} diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php b/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php index faf426f8..97aea2d9 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php @@ -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(); } diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php b/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php index b8d3f057..da8c3b60 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php @@ -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; } diff --git a/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php b/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php index 1ec584bb..413a77f3 100644 --- a/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php +++ b/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php @@ -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. diff --git a/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php b/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php index 66f91af3..e1d70e92 100644 --- a/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php @@ -358,6 +358,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 +444,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']}); diff --git a/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php b/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php index 3719d095..957ac506 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php @@ -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) { diff --git a/dav/SabreDAV/lib/Sabre/DAV/Property.php b/dav/SabreDAV/lib/Sabre/DAV/Property.php index 1cfada32..db4fef51 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Property.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Property.php @@ -11,7 +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 class Sabre_DAV_Property implements Sabre_DAV_PropertyInterface { abstract function serialize(Sabre_DAV_Server $server, DOMElement $prop); diff --git a/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php b/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php index 88afbcfb..9f21163d 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php @@ -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 index 00000000..515072cb --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php @@ -0,0 +1,21 @@ +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. diff --git a/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php b/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php index 85a9ee31..40580ae3 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php @@ -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); } } diff --git a/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php b/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php old mode 100755 new mode 100644 index fe2372ca..ff2c867a --- a/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php @@ -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); diff --git a/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php b/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php new file mode 100644 index 00000000..276288aa --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php @@ -0,0 +1,351 @@ +'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()); + + + } + + +} diff --git a/dav/SabreDAV/lib/Sabre/VObject/includes.php b/dav/SabreDAV/lib/Sabre/VObject/includes.php index e7f57a06..76497d6f 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/includes.php +++ b/dav/SabreDAV/lib/Sabre/VObject/includes.php @@ -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'; diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php b/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php index 511dd9fd..d9a5a7a0 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php @@ -1,14 +1,16 @@ 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!'); + + } + } diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php index 137b7d3c..d23b8123 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php @@ -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 index 00000000..f4f87535 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php @@ -0,0 +1,96 @@ + 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(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'PHPUnit_Framework_Error_Warning'), '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 index 00000000..1d396d6d --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php @@ -0,0 +1,27 @@ + 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 index 00000000..dba636e9 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php @@ -0,0 +1,23 @@ + 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 index 00000000..ddaf5ce4 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php @@ -0,0 +1,55 @@ +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'), + '' . "\n" . '' . "\n", + '' . "\n" . '' . "\n", + ), + + array( + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_MEDIUM,'bar'), + '' . "\n" . '' . "\n", + '' . "\n" . 'bar' . "\n", + ), + + array( + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_LOW,null,'http://example.org/'), + '' . "\n" . '' . "\n", + '' . "\n" . 'http://example.org/' . "\n", + ) + ); + + } + +} diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php index 4d45c8ae..6c618cac 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php @@ -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( diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php index eb097c9e..cac4049f 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php @@ -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'; '' . ' ' . ' ' . - ' ' . + ' ' . ' ' . '' . '' . @@ -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 = +' + + + +'; + + $this->assertEquals($expected, $httpResponse->body); + + + } + } diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php index f2a5e556..42e8aebb 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php @@ -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); + + } } diff --git a/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php b/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php index 80a5d081..a4c8e801 100644 --- a/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php +++ b/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php @@ -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); diff --git a/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php b/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php index a5e88f3a..7208fed2 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php @@ -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; + + } } diff --git a/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php b/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php index cf310ec8..67d5bbeb 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php @@ -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()); } diff --git a/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php b/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php new file mode 100644 index 00000000..be8cd49c --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php @@ -0,0 +1,153 @@ +assertEquals(new DateTimeZone('Europe/Sarajevo'), $tz); + + } + + function testUnknownExchangeId() { + + $vobj = <<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 = <<assertEquals(new DateTimeZone(date_default_timezone_get()), $tz); + + } + +} diff --git a/dav/calendar.friendica.fnk.php b/dav/calendar.friendica.fnk.php index af4a0175..2783339f 100644 --- a/dav/calendar.friendica.fnk.php +++ b/dav/calendar.friendica.fnk.php @@ -1,9 +1,9 @@ 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"]) . "
\n"; - } - $GLOBALS["debug_time_last"] = $cur; +function debug_time() +{ + $cur = getCurMicrotime(); + if ($GLOBALS["debug_time_last"] > 0) { + echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "
\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,19 @@ function wdcal_create_std_calendars() $a = get_app(); if (!local_user()) return; - $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_PRIVATE); - if (count($cals) == 0) { - $maxid = q("SELECT MAX(`namespace_id`) maxid FROM %s%scalendars WHERE `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE); - if (!$maxid) { - notification("Something went wrong when trying to create your calendar."); - goaway("/"); - killme(); - } - $nextid = IntVal($maxid[0]["maxid"]) + 1; - q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $nextid, $a->user["uid"], dbesc(t("Private Calendar")), dbesc($a->timezone) - ); - } + $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 `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) - ); + $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, %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) + ); + } } } diff --git a/dav/colorpicker/demo.html b/dav/colorpicker/demo.html index 38975f8e..8b5aad46 100644 --- a/dav/colorpicker/demo.html +++ b/dav/colorpicker/demo.html @@ -6,8 +6,8 @@ Really Simple Color Picker - - + + "; + + $out .= ""; + + 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 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"; + break; + case "FREQ=yearly": + $part_freq = "FREQ=YEARLY"; + break; + default: + $part_freq = ""; + } + + if ($part_freq == "") return; + + if (isset($_REQUEST["rec_interval"])) $part_freq .= ";INTERVAL=" . InTVal($_REQUEST["rec_interval"]); + + if (isset($_REQUEST["rec_exceptions"])) { + $arr = array(); + foreach ($_REQUEST["rec_exceptions"] as $except) { + $arr[] = new DateTime(date("Y-m-d H:i:s", $except)); + } + /** @var Sabre_VObject_Property_MultiDateTime $prop */ + $prop = Sabre_VObject_Property::create("EXDATE"); + $prop->setDateTimes($arr); + $component->add($prop); + } + + $rrule = $part_freq . $part_until; + $component->add(new Sabre_VObject_Property("RRULE", $rrule)); + +} + + + +/** + * @param 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-ANIMEXXCOLOR"); + $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"))); + $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 = wdcal_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 = "\n"; + + return $o; +} \ No newline at end of file diff --git a/dav/database-init.inc.php b/dav/database-init.inc.php index 06e3abc5..6372bf44 100644 --- a/dav/database-init.inc.php +++ b/dav/database-init.inc.php @@ -1,10 +1,80 @@ 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; diff --git a/dav/dav.php b/dav/dav.php index 1f35b06a..05916d01 100644 --- a/dav/dav.php +++ b/dav/dav.php @@ -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 */ diff --git a/dav/dav_caldav_backend_virtual_friendica.inc.php b/dav/dav_caldav_backend_virtual_friendica.inc.php new file mode 100644 index 00000000..60bf8317 --- /dev/null +++ b/dav/dav_caldav_backend_virtual_friendica.inc.php @@ -0,0 +1,245 @@ + 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_virtual_friendica.inc.php b/dav/dav_carddav_backend_virtual_friendica.inc.php new file mode 100644 index 00000000..8d8a156c --- /dev/null +++ b/dav/dav_carddav_backend_virtual_friendica.inc.php @@ -0,0 +1,336 @@ + CARDDAV_NAMESPACE_COMMUNITYCONTACTS . "-" . $uid, + 'uri' => "friendica", + 'principaluri' => $principalUri, + '{DAV:}displayname' => t("Friendica-Contacts"), + '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => t("Your Friendica-Contacts"), + '{http://calendarserver.org/ns/}getctag' => $ctag, + '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' => + new Sabre_CardDAV_Property_SupportedAddressData(), + ); + + return $addressBooks; + + } + + + /** + * Updates an addressbook's properties + * + * See Sabre_DAV_IProperties for a description of the mutations array, as + * well as the return value. + * + * @param string $addressBookId + * @param array $mutations + * @throws Sabre_DAV_Exception_Forbidden + * @see Sabre_DAV_IProperties::updateProperties + * @return bool|array + */ + public function updateAddressBook($addressBookId, array $mutations) + { + throw new Sabre_DAV_Exception_Forbidden(); + } + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @throws Sabre_DAV_Exception_Forbidden + * @return void + */ + public function createAddressBook($principalUri, $url, array $properties) + { + throw new Sabre_DAV_Exception_Forbidden(); + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param int $addressBookId + * @throws Sabre_DAV_Exception_Forbidden + * @return void + */ + public function deleteAddressBook($addressBookId) + { + throw new Sabre_DAV_Exception_Forbidden(); + } + + + /** + * @param array $contact + * @return array + */ + private function dav_contactarr2vcardsource($contact) + { + $name = explode(" ", $contact["name"]); + $first_name = $last_name = ""; + $middle_name = array(); + $num = count($name); + for ($i = 0; $i < $num && $first_name == ""; $i++) if ($name[$i] != "") { + $first_name = $name[$i]; + unset($name[$i]); + } + for ($i = $num - 1; $i >= 0 && $last_name == ""; $i--) if (isset($name[$i]) && $name[$i] != "") { + $last_name = $name[$i]; + unset($name[$i]); + } + foreach ($name as $n) if ($n != "") $middle_name[] = $n; + $vcarddata = new vcard_source_data($first_name, implode(" ", $middle_name), $last_name); + $vcarddata->homepages[] = new vcard_source_data_homepage("pref", $contact["url"]); + $vcarddata->last_update = ($contact["last-update"] > 0 ? $contact["last-update"] : $contact["created"]); + + $photo = q("SELECT * FROM photo WHERE `contact-id` = %d ORDER BY scale DESC", $contact["id"]); //prefer size 80x80 + if ($photo && count($photo) > 0) { + $photodata = new vcard_source_data_photo(); + $photodata->width = $photo[0]["width"]; + $photodata->height = $photo[0]["height"]; + $photodata->type = "JPEG"; + $photodata->binarydata = $photo[0]["data"]; + $vcarddata->photo = $photodata; + } + + switch ($contact["network"]) { + case "face": + $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("facebook", $contact["notify"], "http://www.facebook.com/" . $contact["notify"]); + break; + case "dfrn": + $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("dfrn", $contact["nick"], $contact["url"]); + break; + case "twitter": + $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("twitter", $contact["nick"], "http://twitter.com/" . $contact["nick"]); // @TODO Stimmt das? + break; + } + + $vcard = vcard_source_compile($vcarddata); + return array( + "id" => $contact["id"], + "carddata" => $vcard, + "uri" => $contact["id"] . ".vcf", + "lastmodified" => wdcal_mySql2PhpTime($vcarddata->last_update), + "etag" => md5($vcard), + "size" => strlen($vcard), + ); + + } + + /** + * @param int $uid + * @param array|int[] $exclude_ids + * @return array + */ + private function dav_getCommunityContactsVCards($uid = 0, $exclude_ids = array()) + { + $notin = (count($exclude_ids) > 0 ? " AND id NOT IN (" . implode(", ", $exclude_ids) . ") " : ""); + $uid = IntVal($uid); + $contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0 $notin ORDER BY `name` ASC", $uid); + + $retdata = array(); + foreach ($contacts as $contact) { + $x = $this->dav_contactarr2vcardsource($contact); + $x["contact"] = $contact["id"]; + $retdata[] = $x; + } + return $retdata; + } + + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param string $addressbookId + * @return array + */ + public function getCards($addressbookId) + { + $add = explode("-", $addressbookId); + + $indb = q('SELECT id, carddata, uri, lastmodified, etag, size, contact, manually_deleted FROM %s%scards WHERE namespace = %d AND namespace_id = %d', + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($add[0]), IntVal($add[1]) + ); + $found_contacts = array(); + $contacts = array(); + foreach ($indb as $x) { + if ($x["manually_deleted"] == 0) $contacts[] = $x; + $found_contacts[] = IntVal($x["contact"]); + } + $new_found = $this->dav_getCommunityContactsVCards($add[1], $found_contacts); + foreach ($new_found as $new) { + q("INSERT INTO %s%scards (namespace, namespace_id, contact, carddata, uri, lastmodified, manually_edited, manually_deleted, etag, size) + VALUES (%d, %d, %d, '%s', '%s', %d, 0, 0, '%s', %d)", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, + IntVal($add[0]), IntVal($add[1]), IntVal($new["contact"]), dbesc($new["carddata"]), dbesc($new["uri"]), time(), md5($new["carddata"]), strlen($new["carddata"]) + ); + } + return array_merge($contacts, $new_found); + } + + /** + * Returns a specfic card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * @param mixed $addressBookId + * @param string $cardUri + * @throws Sabre_DAV_Exception_NotFound + * @return array + */ + public function getCard($addressBookId, $cardUri) + { + $x = explode("-", $addressBookId); + $x = q("SELECT id, carddata, uri, lastmodified, etag, size FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri)); + if (count($x) == 0) throw new Sabre_DAV_Exception_NotFound(); + return $x[0]; + } + + /** + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param string $addressBookId + * @param string $cardUri + * @param string $cardData + * @throws Sabre_DAV_Exception_Forbidden + * @return string + */ + public function createCard($addressBookId, $cardUri, $cardData) + { + throw new Sabre_DAV_Exception_Forbidden(); + } + + /** + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param string $addressBookId + * @param string $cardUri + * @param string $cardData + * @throws Sabre_DAV_Exception_Forbidden + * @return string|null + */ + public function updateCard($addressBookId, $cardUri, $cardData) + { + $x = explode("-", $addressBookId); + + $etag = md5($cardData); + q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1]) + ); + q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1])); + + return '"' . $etag . '"'; + } + + /** + * Deletes a card + * + * @param string $addressBookId + * @param string $cardUri + * @throws Sabre_DAV_Exception_Forbidden + * @return bool + */ + public function deleteCard($addressBookId, $cardUri) + { + $x = explode("-", $addressBookId); + + q("UPDATE %s%scards SET manually_deleted = 1 WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri)); + q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1])); + + return true; + } +} diff --git a/dav/dav_friendica_auth.inc.php b/dav/dav_friendica_auth.inc.php index 5082a2f9..acc33fa1 100644 --- a/dav/dav_friendica_auth.inc.php +++ b/dav/dav_friendica_auth.inc.php @@ -1,16 +1,39 @@ 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); } } diff --git a/dav/dav_friendica_principal.inc.php b/dav/dav_friendica_principal.inc.php index eba31c28..780bcd24 100644 --- a/dav/dav_friendica_principal.inc.php +++ b/dav/dav_friendica_principal.inc.php @@ -1,7 +1,7 @@ =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;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a0?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||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
")).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("
")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=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("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").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("
").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
").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('').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||" "))}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()").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'))}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($('
')):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=$(''+c+""),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")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").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;db&&(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=$(''),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-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-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+112?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&&pp)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)?''+q+"":e?"":''+q+"",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)?''+s+"":e?"":''+s+"",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?"":'",x=d?'
'+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
":"",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;L1)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+='
'+(/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)+'
'+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";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";var _=z?'":"";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&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
'+this._get(a,"weekHeader")+"
'+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
"+(j?""+(g[0]>0&&N==g[1]-1?'
':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),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='
',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";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+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
",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&&bd?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 index 00000000..ac2d516a --- /dev/null +++ b/dav/jqueryui/jquery.ui.datepicker-de.js @@ -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: '<zurück', + nextText: 'Vor>', + currentText: 'heute', + monthNames: ['Januar','Februar','März','April','Mai','Juni', + 'Juli','August','September','Oktober','November','Dezember'], + monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun', + 'Jul','Aug','Sep','Okt','Nov','Dez'], + dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], + dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], + dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'], + weekHeader: 'Wo', + dateFormat: 'dd.mm.yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['de']); +}); diff --git a/dav/layout.fnk.php b/dav/layout.fnk.php index 550f90b6..07eec421 100644 --- a/dav/layout.fnk.php +++ b/dav/layout.fnk.php @@ -8,8 +8,8 @@ function wdcal_addRequiredHeaders() { $a = get_app(); - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; @@ -20,6 +20,7 @@ function wdcal_addRequiredHeaders() switch (get_config("system", "language")) { case "de": $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; break; default: $a->page['htmlhead'] .= '' . "\r\n"; @@ -34,10 +35,12 @@ function wdcal_addRequiredHeaders() */ function wdcal_addRequiredHeadersEdit() { - $a = get_app(); - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; + $a = get_app(); + $localization = wdcal_local::getInstanceByUser($a->user["uid"]); + + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; @@ -48,12 +51,56 @@ function wdcal_addRequiredHeadersEdit() $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; + switch ($localization->getLanguageCode()) { + case "de": + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + break; + default: + $a->page['htmlhead'] .= '' . "\r\n"; + } + +} + +/** + * @param array|int[] $calendars + */ +function wdcal_print_user_ics($calendars = array()) +{ + $add = ""; + if (count($calendars) > 0) { + $c = array(); + foreach ($calendars as $i) $c[] = IntVal($i); + $add = " AND `id` IN (" . implode(", ", $c) . ")"; + } + + $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 `namespace` = %d AND `namespace_id` = %d %s", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $a->user["uid"], $add); + if (count($cals) > 0) { + $ids = array(); + foreach ($cals as $c) $ids[] = IntVal($c["id"]); + $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` IN (" . implode(", ", $ids) . ") ORDER BY `firstOccurence`", CALDAV_SQL_DB, CALDAV_SQL_PREFIX); + + foreach ($objs as $obj) { + preg_match("/BEGIN:VEVENT(.*)END:VEVENT/siu", $obj["calendardata"], $matches); + $str2 = preg_replace("/([^\\r])\\n/siu", "\\1\r\n", $matches[0]); + $str2 = preg_replace("/MAILTO:.*[^:a-z0-9_\+äöüß\\n\\n@-]+.*(:|\\r\\n[^ ])/siU", "\\1", $str2); + $str .= $str2 . "\r\n"; + } + } + $str .= "END:VCALENDAR\r\n"; + + echo $str; + killme(); } /** - * @param 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 +111,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 +146,13 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url,
Available Calendars:'; - foreach ($cals_avail as $cal) { - $x .= '
@@ -179,166 +230,57 @@ 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); + + $details = $obj; + } catch (Exception $e) { + info(t("Error") . ": " . $e); + goaway($a->get_baseurl() . "/dav/wdcal/"); } - - return $uri . " / " . $recurr_uri . "
" . print_r($details, true); + 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]; + return wdcal_getEditPage_str($localization, $a->get_baseurl(), $a->user["uid"], $calendar_id, $uri, $recurr_uri); +} - $calendarSource = wdcal_calendar_factory($a->user["uid"], $event["namespace"], $event["namespace_id"]); +function wdcal_getNewPage() +{ + $a = get_app(); + $localization = wdcal_local::getInstanceByUser($a->user["uid"]); - $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 = "" . t("Go back to the calendar") . "

"; - $out .= "
\n"; - - $out .= " - -
\n"; - $out .= "
\n"; - - $out .= ""; - $out .= ""; - $out .= ""; - $out .= "
\n"; - - $out .= ""; - $out .= ""; - $out .= ""; - $out .= "
\n"; - - $out .= "
\n"; - - $out .= " "; - $out .= "
"; - - $out .= ""; - $out .= ' - ' . t('before') . ' -

'; - - - $out .= ""; - - $out .= "
"; - - return $out; + return wdcal_getEditPage_str($localization, $a->get_baseurl(), $a->user["uid"], 0, 0); } diff --git a/dav/main.php b/dav/main.php index 6635d18a..b7287a03 100644 --- a/dav/main.php +++ b/dav/main.php @@ -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,7 +71,7 @@ function dav_init(&$a) dav_include_files(); - if (false) { + if (true) { dbg(true); error_reporting(E_ALL); ini_set("display_errors", 1); @@ -102,58 +92,24 @@ function dav_init(&$a) } 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()); + if (isset($_REQUEST["test"])) { + renderAllCalDavEntries(); } -// The object tree needs in turn to be passed to the server class - $server = new Sabre_DAV_Server($tree); - - $url = parse_url($a->get_baseurl()); - $server->setBaseUri(CALDAV_URL_PREFIX); - - $authPlugin = new Sabre_DAV_Auth_Plugin($authBackend, 'SabreDAV'); - $server->addPlugin($authPlugin); - - $aclPlugin = new Sabre_DAVACL_Plugin_Friendica(); - $aclPlugin->defaultUsernamePath = "principals/users"; - $server->addPlugin($aclPlugin); - - $caldavPlugin = new Sabre_CalDAV_Plugin(); - $server->addPlugin($caldavPlugin); - - $carddavPlugin = new Sabre_CardDAV_Plugin(); - $server->addPlugin($carddavPlugin); + $server = dav_create_server(); $browser = new Sabre_DAV_Browser_Plugin(); $server->addPlugin($browser); - $server->exec(); killme(); @@ -174,36 +130,44 @@ 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] == "ics") { + wdcal_print_user_ics(); + } elseif ($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"]); } - $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] > 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"]); + } + $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/wdcal.css b/dav/wdcal.css index c64f0cf4..18c25c58 100644 --- a/dav/wdcal.css +++ b/dav/wdcal.css @@ -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