--- /dev/null
+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
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.
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
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
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.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-??-??)
+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.
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.
if ($found === 0) {
echo "The database had the 1.6 schema. Table will now be altered.\n";
echo "This may take some time for large tables\n";
- $pdo->exec(<<<SQL
+
+ switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) {
+
+ case 'mysql' :
+
+ $pdo->exec(<<<SQL
ALTER TABLE calendarobjects
ADD etag VARCHAR(32),
ADD size INT(11) UNSIGNED,
ADD firstoccurence INT(11) UNSIGNED,
ADD lastoccurence INT(11) UNSIGNED
SQL
-);
+ );
+ break;
+ case 'sqlite' :
+ $pdo->exec('ALTER TABLE calendarobjects ADD etag text');
+ $pdo->exec('ALTER TABLE calendarobjects ADD size integer');
+ $pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT');
+ $pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer');
+ $pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer');
+ break;
+
+ default :
+ die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n");
+
+ }
echo "Database schema upgraded.\n";
} elseif ($found === 5) {
--- /dev/null
+
+
+
+Calendar Server Extension C. Daboo
+ Apple Inc.
+ March 19, 2012
+
+
+ CalDAV: Calendar User Notifications
+
+Abstract
+
+ This specification defines an extension to CalDAV that allows the
+ server to provide notifications to calendar users.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 1]
+\f
+ CalDAV User Notifications March 2012
+
+
+Table of Contents
+
+ 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
+ 2. Open Issues . . . . . . . . . . . . . . . . . . . . . . . . . 3
+ 3. Conventions Used in This Document . . . . . . . . . . . . . . 3
+ 4. Notifications . . . . . . . . . . . . . . . . . . . . . . . . 3
+ 4.1. Additional Principal Properties . . . . . . . . . . . . . 4
+ 4.1.1. CS:notification-URL Property . . . . . . . . . . . . . 5
+ 4.2. Properties on Notification Resources . . . . . . . . . . . 5
+ 4.2.1. CS:notificationtype Property . . . . . . . . . . . . . 5
+ 4.3. XML Element Definitions . . . . . . . . . . . . . . . . . 6
+ 4.3.1. CS:notifications . . . . . . . . . . . . . . . . . . . 6
+ 4.3.2. CS:notification . . . . . . . . . . . . . . . . . . . 6
+ 4.3.3. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . 7
+ 5. Notification Definitions . . . . . . . . . . . . . . . . . . . 7
+ 5.1. System Status Notification . . . . . . . . . . . . . . . . 7
+ 5.1.1. CS:systemstatus Element Definition . . . . . . . . . . 8
+ 5.2. Quota Notification . . . . . . . . . . . . . . . . . . . . 8
+ 5.2.1. CS:quotastatus Element Definition . . . . . . . . . . 9
+ 5.3. Resource Changes Notification . . . . . . . . . . . . . . 10
+ 5.3.1. CS:resource-change Element Definition . . . . . . . . 11
+ 5.3.2. CS:calendar-changes Element Definition . . . . . . . . 15
+ 5.3.2.1. Handling Recurrences in CS:calendar-changes . . . 17
+ 5.3.3. CS:deleted-details Element Definition . . . . . . . . 18
+ 5.3.4. CS:notify-changes Property . . . . . . . . . . . . . . 20
+ 6. Security Considerations . . . . . . . . . . . . . . . . . . . 20
+ 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 21
+ 8. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 21
+ 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 21
+ 9.1. Normative References . . . . . . . . . . . . . . . . . . . 21
+ 9.2. Informative References . . . . . . . . . . . . . . . . . . 21
+ Appendix A. Examples . . . . . . . . . . . . . . . . . . . . . . 21
+ A.1. Resource Created . . . . . . . . . . . . . . . . . . . . . 21
+ A.2. Resource Updated - Property Change . . . . . . . . . . . . 22
+ A.3. Resource Updated - Parameter Change . . . . . . . . . . . 23
+ A.4. Resource Updated - Multiple Instances Change . . . . . . . 23
+ A.5. Resource Updated - Multiple User Change . . . . . . . . . 24
+ A.6. Resource Deleted . . . . . . . . . . . . . . . . . . . . . 25
+ A.7. Collection Created . . . . . . . . . . . . . . . . . . . . 26
+ A.8. Collection Updated . . . . . . . . . . . . . . . . . . . . 26
+ A.9. Collection Deleted . . . . . . . . . . . . . . . . . . . . 27
+ Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 27
+
+
+
+
+
+
+
+
+
+Daboo [Page 2]
+\f
+ CalDAV User Notifications March 2012
+
+
+1. Introduction
+
+ CalDAV [RFC4791] provides a way for calendar users to store calendar
+ data and exchange this data via scheduling operations. Based on the
+ WebDAV [RFC4918] protocol, it also includes the ability to manage
+ access to calendar data via the WebDAV ACL [RFC3744] extension.
+
+ It is often useful for servers to communicate arbitrary information
+ to calendar users, e.g., system status, message of the day, quota
+ warnings, changes to shared resources made by others etc. This
+ specification defines a generic "notification" mechanism that allows
+ a server to do that. Whilst primarily aimed at CalDAV [RFC4791],
+ this mechanism has been designed to be adaptable to WebDAV [RFC4918].
+
+
+2. Open Issues
+
+ 1. Define specific child elements for system status notification,
+ e.g. "server-maintenance-period", "server-read-only-period",
+ "client-upgrade-required".
+
+
+3. Conventions Used in This Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [RFC2119].
+
+ When XML element types in the namespaces "DAV:" and
+ "urn:ietf:params:xml:ns:caldav" are referenced in this document
+ outside of the context of an XML fragment, the string "DAV:" and
+ "CALDAV:" will be prefixed to the element type names respectively.
+
+ The namespace "http://calendarserver.org/ns/" is used for XML
+ elements defined in this specification. When XML element types in
+ that namespace are referenced in this document outside of the context
+ of an XML fragment, the string "CS:" will be prefixed to the element
+ type names.
+
+
+4. Notifications
+
+ When this feature is available, a CS:notification-URL (Section 4.1.1)
+ property appears on principal resources for those principals who are
+ able to receive notifications. That property specifies a single DAV:
+ href element whose content refers to a WebDAV collection resource.
+ Notification "messages" are deposited into this collection and can be
+ retrieved by clients and acted on accordingly.
+
+
+
+Daboo [Page 3]
+\f
+ CalDAV User Notifications March 2012
+
+
+ The notification collection referenced by the CS:notification-URL
+ (Section 4.1.1) property MUST have a DAV:resourcetype property with
+ DAV:collection and CS:notifications (Section 4.3.1) child elements.
+
+ Notification "messages" are XML documents stored as resources in the
+ notification collection. Each XML document contains a CS:
+ notification (Section 4.3.2) element as its root. The root element
+ contains a CS:dtstamp element, and one additional element which
+ represents the type of notification being conveyed in the message.
+ That child element will typically contain additional content that
+ describes the notification.
+
+ Each notification resource has a CS:notificationtype (Section 4.2.1)
+ property which contains as its single child element an empty element
+ that matches the child element of the notification resource XML
+ document root. Any attributes on the child element in the XML
+ document are also present in the property child element.
+
+ Notifications are automatically generated by the server (perhaps in
+ response to a action) with an appropriate resource stored in the
+ notifications collection of the user to whom the notification is
+ targeted. Clients SHOULD monitor the notification collection looking
+ for new notification resources. When doing so, clients SHOULD look
+ at the CS:notificationtype (Section 4.2.1) property to ensure that
+ the notification is of a type that the client can handle. Once a
+ client has handled the notification in whatever way is appropriate it
+ SHOULD delete the notification resource. Clients SHOULD remove
+ notifications being displayed to a user when the notification
+ resource is removed from the notification collection, to enable the
+ user to dismiss a notification on one device and have it
+ automatically removed from others. Clients MUST ignore all
+ notifications for types they do not recognize. Servers MAY delete
+ notification resources on their own if they determine that the
+ notifications are no longer relevant or valid. Servers MAY coalesce
+ notifications as appropriate.
+
+ Servers MUST prevent clients from adding resources in the
+ notification collection.
+
+4.1. Additional Principal Properties
+
+ This section defines new properties for WebDAV principal resources as
+ defined in RFC3744 [RFC3744]. These properties are likely to be
+ protected but the server MAY allow them to be written by appropriate
+ users.
+
+
+
+
+
+
+Daboo [Page 4]
+\f
+ CalDAV User Notifications March 2012
+
+
+4.1.1. CS:notification-URL Property
+
+ Name: notification-URL
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Identify the URL of the notification collection owned by
+ the associated principal resource.
+
+ Protected: This property SHOULD be protected.
+
+ PROPFIND behavior: This property SHOULD NOT be returned by a
+ PROPFIND allprop request (as defined in Section 14.2 of
+ [RFC4918]).
+
+ COPY/MOVE behavior: This property value SHOULD be preserved in COPY
+ and MOVE operations.
+
+ Description: This property is needed for a client to determine where
+ the notification collection of the current user is located so that
+ processing of notification messages can occur. If not present,
+ then the associated calendar user is not enabled for notification
+ messages on the server.
+
+ Definition:
+
+ <!ELEMENT notification-URL (DAV:href)>
+
+4.2. Properties on Notification Resources
+
+ The following new WebDAV properties are defined for notification
+ resources.
+
+4.2.1. CS:notificationtype Property
+
+ Name: notificationtype
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Identify the type of notification of the corresponding
+ resource.
+
+ Protected: This property MUST be protected.
+
+ PROPFIND behavior: This property SHOULD NOT be returned by a
+ PROPFIND allprop request (as defined in Section 14.2 of
+ [RFC4918]).
+
+
+
+
+Daboo [Page 5]
+\f
+ CalDAV User Notifications March 2012
+
+
+ COPY/MOVE behavior: This property value MUST be preserved in COPY
+ and MOVE operations.
+
+ Description: This property allows a client, via a PROPFIND Depth:1
+ request, to quickly find notification messages that the client can
+ handle in a notification collection. The single child element is
+ the notification resource root element's child defining the
+ notification itself. This element MUST be empty, though any
+ attributes on the element in the notification resource MUST be
+ present in the property element.
+
+ Definition:
+
+ <!ELEMENT notificationtype ANY>
+ <!-- Child elements are empty but will have appropriate attributes.
+ Any valid notification message child element can appear.-->
+
+4.3. XML Element Definitions
+
+4.3.1. CS:notifications
+
+ Name: notifications
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Indicates a notification collection.
+
+ Description: This XML element is used in a DAV:resourcetype element
+ to indicate that the corresponding resource is a notification
+ collection.
+
+ Definition:
+
+ <!ELEMENT notifications EMPTY>
+
+4.3.2. CS:notification
+
+ Name: notification
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Notification message root element.
+
+ Description: The root element used in notification resources.
+
+
+
+
+
+
+
+Daboo [Page 6]
+\f
+ CalDAV User Notifications March 2012
+
+
+ Definition:
+
+ <!ELEMENT notification (dtstamp, XXX) >
+ <!-- Any notification type element can appear after
+ CS:dtstamp -->
+
+4.3.3. CS:dtstamp
+
+ Name: dtstamp
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Date-time stamp.
+
+ Description: Contains the date-time stamp corresponding to the
+ creation of a notification message, using the format defined in
+ [RFC3339], or the "compact" format without "-" and ":" characters
+ between date and time elements, respectively.
+
+ Definition:
+
+ <!ELEMENT dtstamp (#PCDATA)>
+ <!-- Value is a date-time in UTZ as per [RFC3339] with
+ "compact" format allowed.-->
+
+
+5. Notification Definitions
+
+ This section defines a set of common notification types.
+
+5.1. System Status Notification
+
+ The system status notification is used to convey a URI and/or textual
+ description to the user. The assumption is that the URI points to a
+ webpage where current system status is described in detail, with the
+ provided description being a summary of that. A "type" attribute on
+ the element is used to indicate the importance of the current status
+ notification, and has the values "low", "medium" and "high",
+ representing the increasing level of importance of the message
+ respectively.
+
+ Servers might have knowledge of specific calendar user language
+ preferences, in which case it MAY localise the CS:description value
+ as appropriate based on the calendar user accessing the notification,
+ but if it does, it SHOULD include an xml:lang attribute on the CS:
+ description element to indicate what language is being used.
+
+
+
+
+
+Daboo [Page 7]
+\f
+ CalDAV User Notifications March 2012
+
+
+5.1.1. CS:systemstatus Element Definition
+
+ Name: systemstatus
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Indicates a system status notification.
+
+ Description: This XML element is used in a CS:notification element
+ to describe a system status notification.
+
+ Definition:
+
+ <!ELEMENT systemstatus (DAV:href?, CS:description?)>
+ <!ATTLIST systemstatus type (low | medium | high) "low">
+
+ <!ELEMENT description CDATA>
+
+ <!-- One of DAV:href of CS:description MUST be present -->
+
+ Example: This is an example of the body of a notification resource
+ for an emergency system outage:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+ <CS:systemstatus type="high">
+ <D:href>http://example.com/emergency_shutdown.html</D:href>
+ <CS:description xml:lang='en_US'
+ >Emergency shutdown now</CS:description>
+ </CS:systemstatus>
+ </CS:notification>
+
+ Example: This is an example of the WebDAV property on the example
+ notification resource above:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notificationtype xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:systemstatus type="high" />
+ </CS:notificationtype>
+
+5.2. Quota Notification
+
+ The quota notification is used to convey information about the status
+ of one or more quotas for the user. The notification contains
+ elements for different types of quota being reported to the user. In
+
+
+
+Daboo [Page 8]
+\f
+ CalDAV User Notifications March 2012
+
+
+ some cases these may be warnings (e.g., a user getting to 80% of
+ their quota limit), or in other cases errors (e.g., a user exceeding
+ their quota).
+
+5.2.1. CS:quotastatus Element Definition
+
+ Name: quotastatus
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Indicates a quota status notification.
+
+ Description: This XML element is used in a CS:notification element
+ to describe a quota status notification. The CS:quota-percent-
+ used element contains an integer greater than or equal to zero.
+ If the value is greater than or equal to 100, then the user's
+ quota has been reached or exceeded. The DAV:href element contains
+ a URI for a webpage where the user can go to get further
+ information about their quota status or take corrective action.
+
+ Definition:
+
+ <!ELEMENT quota-status (quota+)>
+
+ <!ELEMENT quota (quota-type, quota-percent-used?,
+ quota-count?, DAV:href?)>
+ <!ATTLIST quota type (warning | exceeded) "exceeded">
+
+ <!ELEMENT quota-type ANY>
+ <!-- Child elements are application specific -->
+
+ <!ELEMENT quota-percent-used CDATA>
+ <!-- Integer value greater than or equal to zero -->
+
+ <!ELEMENT quota-count CDATA>
+ <!-- Integer value greater than or equal to zero -->
+
+ Example: This is an example of the body of a notification resource
+ for a quota warning:
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 9]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+ <CS:quota-status>
+ <CS:quota type="warning">
+ <CS:quota-type><CS:attachments /></CS:quota-type>
+ <CS:quota-percent-used>80</CS:quota-percent-used>
+ <D:href>https://example.com/your-account.html</D:href>
+ </CS:quota>
+ </CS:quota-status>
+ </CS:notification>
+
+ Example: This is an example of the body of a notification resource
+ for a quota that has been exceeded, and a count-based limit that
+ is shown as a warning:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:12:53-05:00</CS:dtstamp>
+ <CS:quota-status>
+ <CS:quota type="exceeded">
+ <CS:quota-type><CS:attachments /></CS:quota-type>
+ <CS:quota-percent-used>102</CS:quota-percent-used>
+ <D:href>https://example.com/fix-account.html</D:href>
+ </CS:quota>
+ <CS:quota type="warning">
+ <CS:quota-type><CS:events /></CS:quota-type>
+ <CS:quota-percent-used>82</CS:quota-percent-used>
+ <CS:quota-count>4980</CS:quota-count>
+ <D:href>https://example.com/buy-more-space.html</D:href>
+ </CS:quota>
+ </CS:quota-status>
+ </CS:notification>
+
+5.3. Resource Changes Notification
+
+ The resource change notification is used to inform the user of new,
+ updated or deleted resources caused by changes made by someone else
+ (note: servers MUST NOT generate notifications to users for changes
+ they themselves make, though the possibility of an automated process
+ acting on behalf of a user needs to be considered). This
+ notification can be used by clients to show changes that a user can
+ acknowledge in their own time. When the notification is present, it
+ can be displayed on all devices a user is accessing their data from.
+ When the user acknowledges and dismisses the notification on one
+ device, other devices SHOULD also remove the notification when they
+
+
+
+Daboo [Page 10]
+\f
+ CalDAV User Notifications March 2012
+
+
+ next synchronize the notification collection.
+
+ A new WebDAV property CS:notify-changes (Section 5.3.4) is defined
+ for calendar collections. This allows users to enable or disable the
+ sending of resource change notifications for the calendar and its
+ child resources. Servers MUST allow users to set this property on a
+ per-user basis on any calendars accessible to them. Servers MUST
+ honor the chosen setting to enable or disable change notifications.
+
+ Servers can send notifications for calendar object resources, and
+ ones for calendar collections. Servers SHOULD coalesce notifications
+ that refer to the same resource into a single notification resource,
+ containing multiple CS:created, CS:updated or CS:deleted elements all
+ with the same DAV:href child element value. Servers MAY coalesce
+ changes to multiple resources into a change notification for the
+ parent collection of those resources and use a CS:collection-changes
+ element to indicate the number of individual resources that changed.
+
+5.3.1. CS:resource-change Element Definition
+
+ Name: resource-change
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Indicates that resources have been created, updated or
+ deleted.
+
+ Description: This XML element is used in a CS:notification element
+ to describe a resource change notification. It can describe a
+ change directly to a calendar object resource or to a calendar
+ collection.
+
+ When used for a calendar object resource change, it can contain
+ one of the CS:created, or CS:deleted elements, or multiple CS:
+ updated elements, which indicate a created, deleted or updated
+ resource, respectively. The DAV:href element within those
+ elements, contains the URI of the changed resource, optional
+ information about who changed the resource and when that change
+ was made (the CS:changed-by element), and information specific to
+ the nature of the change. Servers SHOULD coalesce resource change
+ notifications for the same resource into a single notification
+ resource where possible. The CS:updated element optionally
+ contains CS:content and/or DAV:prop elements to indicate a change
+ to the body of the resource or resource WebDAV properties,
+ respectively. The DAV:prop element MAY contain a list of property
+ elements to indicate which properties changed. The CS:updated
+ element can also contain zero or more CS:calendar-changes elements
+ to list details of the changes. If no CS:calendar-changes element
+
+
+
+Daboo [Page 11]
+\f
+ CalDAV User Notifications March 2012
+
+
+ is present, the specific details are not provided, and clients
+ will need to assume that some set of changes occurred, but the
+ server is unwilling to disclose the full details. The CS:deleted
+ element can also contain zero or more CS:deleted-details elements
+ to list details of the deleted resource.
+
+ When used for a calendar collection change, it can contain a CS:
+ collection-changes element. The DAV:href element within that
+ element, contains the URI of the changed calendar collection. The
+ DAV:prop element indicates a change to WebDAV properties on the
+ calendar collection resource. The CS:child-created, CS:child-
+ updated, and CS:child-deleted elements each contain a positive
+ integer value indicating how many child resources were added,
+ updated or deleted in the collection, respectively.
+
+ Definition:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 12]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <!ELEMENT resource-change (created | updated+ | deleted |
+ collection-changes)>
+ <!ELEMENT created (DAV:href, changed-by?, ANY)>
+ <!ELEMENT updated (DAV:href, changed-by?, content?,
+ DAV:prop?, calendar-changes*)>
+ <!ELEMENT content EMPTY>
+ <!ELEMENT deleted (DAV:href, changed-by?, deleted-details)>
+
+ <!ELEMENT changed-by (common-name | (first-name, last-name),
+ dtstamp?, DAV:href)>
+ <!ELEMENT common-name CDATA>
+ <!ELEMENT first-name CDATA>
+ <!ELEMENT last-name CDATA>
+ <!-- CS:changed-by indicates who made the change that caused the
+ notification. CS:first-name and CS:last-name are the first
+ and last names of the corresponding user. or the
+ CS:common-name is the overall display name. CS:dtstamp is the
+ time in UTC when the change was made. The DAV:href element
+ is the principal URI or email address of the user who made
+ the change. -->
+
+ <!ELEMENT collection-changes (DAV:href, changed-by*, DAV:prop?,
+ child-created?, child-updated?,
+ child-deleted?>
+ <!-- When coalescing changes from multiple users, the changed-by
+ element can appear more than once. -->
+
+ <!ELEMENT child-created CDATA>
+ <!ELEMENT child-updated CDATA>
+ <!ELEMENT child-deleted CDATA>
+ <!-- Each of the three elements above MUST contain a positive,
+ non-zero integer value indicate the total number of changes
+ being reported for the collection. -->
+
+ Example: This is an example of the body of a notification resource
+ for changes where one resource has been created:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 13]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:created>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:common-name>Cyrus Daboo</CS:common-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ </CS:created>
+ </CS:resource-change>
+ </CS:notification>
+
+ Example: This is an example of the body of a notification resource
+ for changes where a resource has been updated twice. One of the
+ updated resources elements contains additional information
+ indicating which recurrence instances in the iCalendar data were
+ changed:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/event.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Oliver</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>mailto:oliver@example.com</D:href>
+ </CS:changed-by>
+ </CS:updated>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/event.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Eleanor</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>mailto:eleanor@example.com</D:href>
+ </CS:changed-by>
+ </CS:updated>
+ </CS:resource-change>
+ </CS:notification>
+
+
+
+
+
+
+
+Daboo [Page 14]
+\f
+ CalDAV User Notifications March 2012
+
+
+ Example: This is an example of the body of a notification resource
+ for changes where one resource has been deleted:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:deleted>
+ <D:href>http://example.com/cyrus/calendar/old.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ </CS:deleted>
+ </CS:resource-change>
+ </CS:notification>
+
+ Example: This example is the same as the previous three, except that
+ all the individual resource changes have been coalesced into a
+ single notification about changes to the parent calendar
+ collection:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:collection-changes>
+ <D:href>http://example.com/cyrus/calendar/</D:href>
+ <CS:child-created>1</CS:child-created>
+ <CS:child-updated>2</CS:child-updated>
+ <CS:child-deleted>1</CS:child-deleted>
+ </CS:collection-changes>
+ </CS:resource-change>
+ </CS:notification>
+
+5.3.2. CS:calendar-changes Element Definition
+
+ Name: calendar-changes
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Indicates which portions of an calendar object resource
+ have changed, or provides details of deleted calendar object
+ resources.
+
+
+
+
+Daboo [Page 15]
+\f
+ CalDAV User Notifications March 2012
+
+
+ Description: This XML element is used in a CS:updated element to
+ describe how a calendar object resource changed, or in a CS:
+ deleted element to provide details of a deleted resource. It can
+ identify the master instance, or individual recurrence instances,
+ and for each indicate which iCalendar properties and parameters
+ changed during the update for which the notification was
+ generated. For details of handling recurrences please see
+ Section 5.3.2.1.
+
+ Definition:
+
+ <!ELEMENT calendar-changes (recurrence+) >
+
+ <!ELEMENT recurrence
+ ((master | recurrenceid), added?, removed?, changes?)>
+ <!-- Which instances were affected by the change,
+ and details on the per-instance changes -->
+
+ <!ELEMENT master EMPTY>
+ <!-- The "master" instance was affected -->
+
+ <!ELEMENT recurrenceid CDATA>
+ <!-- RECURRENCE-ID value in iCalendar form (in UTC if a
+ non-floating DATE-TIME value) for the affected instance -->
+
+ <!ELEMENT added EMPTY>
+ <!-- The component was added -->
+
+ <!ELEMENT removed EMPTY>
+ <!-- The component was removed -->
+
+ <!ELEMENT changes changed-property*>
+ <!-- Detailed changes in the iCalendar data -->
+
+ <!ELEMENT changed-property changed-parameter*>
+ <!ATTLIST changed-property name PCDATA>
+ <!-- An iCalendar property changed -->
+
+ <!ELEMENT changed-parameter EMPTY>
+ <!ATTLIST changed-parameter name PCDATA>
+ <!-- An iCalendar property parameter changed -->
+
+ Example: This example indicates that a non-recurring component, or
+ the master component in a recurring component, was changed and
+ that the change was to the "SUMMARY" iCalendar property.
+
+
+
+
+
+
+Daboo [Page 16]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <CS:calendar-changes xmlns:CS="http://calendarserver.org/ns/">
+ <CS:recurrence>
+ <CS:master/>
+ <CS:changes>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+
+ Example: This example indicates that an instance of a recurring
+ component was changed and that the change was to the "DTSTART"
+ iCalendar property.
+
+ <CS:calendar-changes xmlns:CS="http://calendarserver.org/ns/">
+ <CS:recurrence>
+ <CS:recurrenceid>20111215T160000Z</CS:recurrenceid>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+
+5.3.2.1. Handling Recurrences in CS:calendar-changes
+
+ Changes to recurring components can be complex. This section
+ describes the possible set of changes that could occur, and what the
+ CS:calendar-changes element will contain as a result.
+
+ Master exists, unchanged override added In this case, a CS:
+ recurrence element will be present, containing a CS:recurrence-id
+ element with a value equal to the RECURRENCE-ID property value (in
+ UTC) of the added component. A CS:added element will be present.
+ There will not be any CS:removed or CS:changes elements.
+
+ Master exists, changed override added In this case, a CS:recurrence
+ element will be present, containing a CS:recurrence-id element
+ with a value equal to the RECURRENCE-ID property value (in UTC) of
+ the added component. Both CS:added and CS:changes elements will
+ be present. There will not be a CS:removed element.
+
+ Master exists, override changed In this case, a CS:recurrence
+ element will be present, containing a CS:recurrence-id element
+ with a value equal to the RECURRENCE-ID property value (in UTC) of
+ the added component. A CS:changes element will be present. There
+ will not be any CS:added or CS:removed elements.
+
+
+
+
+
+
+Daboo [Page 17]
+\f
+ CalDAV User Notifications March 2012
+
+
+ Master exists, override removed In this case, a CS:recurrence
+ element will be present, containing a CS:recurrence-id element
+ with a value equal to the RECURRENCE-ID property value (in UTC) of
+ the added component. A CS:removed element will be present. There
+ will not be a CS:added element. A CS:changes element will only be
+ present if the removed component differs from the "derived" master
+ instance.
+
+ Master exists, override cancelled In this case, a CS:recurrence
+ element will be present, containing a CS:recurrence-id element
+ with a value equal to the RECURRENCE-ID property value (in UTC) of
+ the added component. A CS:removed element will be present. There
+ will not be any CS:added or CS:changes element. There will also
+ be a CS:master element present, with an appropriate CS:changes
+ element, likely covering a change to "RRULE" or addition of
+ "EXDATE" properties.
+
+ Master does not exist, override added In this case, a CS:recurrence
+ element will be present, containing a CS:recurrence-id element
+ with a value equal to the RECURRENCE-ID property value (in UTC) of
+ the added component. A CS:added element will be present. There
+ will not be a CS:removed or CS:changes element.
+
+ Master does not exist, override changed In this case, a CS:
+ recurrence element will be present, containing a CS:recurrence-id
+ element with a value equal to the RECURRENCE-ID property value (in
+ UTC) of the added component. A CS:changes element will be
+ present. There will not be any CS:added or CS:removed elements.
+
+ Master does not exist, override removed In this case, a CS:
+ recurrence element will be present, containing a CS:recurrence-id
+ element with a value equal to the RECURRENCE-ID property value (in
+ UTC) of the added component. A CS:removed element will be
+ present. There will not be any CS:added or CS:changes element.
+
+5.3.3. CS:deleted-details Element Definition
+
+ Name: deleted-details
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Provides summary information about a deleted resource or
+ collection.
+
+ Description: This XML element is used in a CS:deleted element to
+ describe useful information about a deleted resource or
+ collection, so clients can provide a meaningful notification
+ message to users. This element has two variants: one for deletion
+
+
+
+Daboo [Page 18]
+\f
+ CalDAV User Notifications March 2012
+
+
+ of a calendar object resource, the other for deletion of a
+ calendar collection.
+
+ Definition:
+
+ <!ELEMENT deleted-details ((deleted-component,
+ deleted-summary,
+ deleted-next-instance?,
+ deleted-had-more-instances?) |
+ deleted-displayname)>
+ <!-- deleted-displayname is used for a collection delete, the other
+ elements used for a resource delete. -->
+
+ <!ELEMENT deleted-component CDATA>
+ <!-- The main calendar component type of the deleted
+ resource, e.g., "VEVENT", "VTODO" -->
+
+ <!ELEMENT deleted-summary CDATA>
+ <!-- Indicates the "SUMMARY" of the next future instance at the
+ time of deletion, or the previous instance if no future
+ instances existed at the time of deletion. -->
+
+ <!ELEMENT deleted-next-instance CDATA>
+ <!ATTLIST deleted-next-instance tzid PCDATA>
+ <!-- If present, indicates when the next deleted instance would
+ have occurred. For a VEVENT that would be the DTSTART value,
+ for a VTODO that would be either DTSTART or DUE, if present.
+ In each case the value must match the value in the iCalendar
+ data, and any TZID iCalendar property parameter value must
+ be included in the tzid XML element attribute value. -->
+
+ <!ELEMENT deleted-had-more-instances EMPTY>
+ <!-- If present indicates that there was more than one future
+ instances still to occur at the time of deletion. -->
+
+ <!ELEMENT deleted-displayname CDATA>
+ <!-- The DAV:getdisplayname property for the collection that
+ was deleted. -->
+
+ Example: This example indicates deletion of a non-recurring event
+ that was yet to occur at the time of deletion.
+
+ <CS:deleted-details xmlns:CS="http://calendarserver.org/ns/">
+ <CS:deleted-component>VEVENT</CS:deleted-component>
+ <CS:deleted-summary>Birthday Party</CS:deleted-summary>
+ <CS:deleted-next-instance tzid="America/New_York
+ >20120505T120000</CS:deleted-next-instance>
+ </CS:deleted-details>
+
+
+
+Daboo [Page 19]
+\f
+ CalDAV User Notifications March 2012
+
+
+ Example: This example indicates deletion of a calendar.
+
+ <CS:deleted-details xmlns:CS="http://calendarserver.org/ns/">
+ <CS:deleted-displayname>Holidays</CS:deleted-displayname>
+ </CS:deleted-details>
+
+5.3.4. CS:notify-changes Property
+
+ Name: notify-changes
+
+ Namespace: http://calendarserver.org/ns/
+
+ Purpose: Allows a user to specify whether resource change
+ notifications are generated by the server.
+
+ Protected: This property MUST NOT be protected.
+
+ PROPFIND behavior: This property SHOULD NOT be returned by a
+ PROPFIND allprop request (as defined in Section 14.2 of
+ [RFC4918]).
+
+ COPY/MOVE behavior: This property value MUST be preserved in COPY
+ and MOVE operations.
+
+ Description: This property allows a user to enable or disable the
+ server generation of resource change notifications for the
+ calendar collection, and all its child resources, on which the
+ property resides. If the property is not present on a calendar
+ collection, the client and server MUST assume that resource change
+ notifications are enabled.
+
+ Definition:
+
+ <!ELEMENT notify-changes (true|false)>
+ <!ELEMENT true EMPTY>
+ <!ELEMENT false EMPTY>
+
+ <!-- true - notifications enabled,
+ false - notifications disabled -->
+
+
+6. Security Considerations
+
+ Some notification mechanisms might allow a user to trigger a
+ notification to be delivered to other users (e.g., an invitation to
+ share a calendar). In such cases servers MUST ensure that suitable
+ limits are placed on the number and frequency of such user generated
+ notifications.
+
+
+
+Daboo [Page 20]
+\f
+ CalDAV User Notifications March 2012
+
+
+ TBD: More?
+
+
+7. IANA Considerations
+
+ This document does not require any actions on the part of IANA.
+
+
+8. Acknowledgments
+
+ This specification is the result of discussions between the various
+ Apple calendar server and client teams.
+
+
+9. References
+
+9.1. Normative References
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [RFC3339] Klyne, G., Ed. and C. Newman, "Date and Time on the
+ Internet: Timestamps", RFC 3339, July 2002.
+
+ [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
+ Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
+
+9.2. Informative References
+
+ [RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
+ Distributed Authoring and Versioning (WebDAV)
+ Access Control Protocol", RFC 3744, May 2004.
+
+ [RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
+ "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
+ March 2007.
+
+
+Appendix A. Examples
+
+ This section provides more detailed examples of resource change
+ notifications for illustrative purposes only.
+
+A.1. Resource Created
+
+ This is an example of the body of a notification resource where one
+ resource has been created.
+
+
+
+
+Daboo [Page 21]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:created>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ </CS:created>
+ </CS:resource-change>
+ </CS:notification>
+
+A.2. Resource Updated - Property Change
+
+ This is an example of the body of a notification resource where one
+ non-recurring event has had its "DTSTART" and "SUMMARY" iCalendar
+ property values changed.
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:calendar-changes>
+ <CS:recurrence>
+ <CS:master/>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+ </CS:updated>
+ </CS:resource-change>
+ </CS:notification>
+
+
+
+
+
+Daboo [Page 22]
+\f
+ CalDAV User Notifications March 2012
+
+
+A.3. Resource Updated - Parameter Change
+
+ This is an example of the body of a notification resource where one
+ non-recurring event has had the "PARTSTAT" iCalendar property
+ parameter on an "ATTENDEE" property changed, and a "TRANSP" property
+ added.
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:calendar-changes>
+ <CS:recurrence>
+ <CS:master/>
+ <CS:added>
+ <CS:changed-property name="TRANSP"/>
+ </CS:added>
+ <CS:changes>
+ <CS:changed-property name="ATTENDEE">
+ <CS:changed-parameter name="PARTSTAT"/>
+ </CS:changed-property>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+ </CS:updated>
+ </CS:resource-change>
+ </CS:notification>
+
+A.4. Resource Updated - Multiple Instances Change
+
+ This is an example of the body of a notification resource where two
+ instances of a recurring event have their "DTSTART" and "SUMMARY"
+ iCalendar property values changed.
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 23]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:calendar-changes>
+ <CS:recurrence>
+ <CS:recurrenceid>20120209T170000Z</CS:recurrenceid>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ <CS:recurrence>
+ <CS:recurrenceid>20120210T170000Z</CS:recurrenceid>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+ </CS:updated>
+ </CS:resource-change>
+ </CS:notification>
+
+A.5. Resource Updated - Multiple User Change
+
+ This is an example of the body of a notification resource where two
+ instances of a recurring event have their "DTSTART" and "SUMMARY"
+ iCalendar property values changed. Each instance was changed by a
+ different user.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 24]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:calendar-changes>
+ <CS:recurrence>
+ <CS:recurrenceid>20120209T170000Z</CS:recurrenceid>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+ </CS:updated>
+ <CS:updated>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Eric</CS:first-name>
+ <CS:last-name>York</CS:last-name>
+ <D:href>/principals/ericyork</D:href>
+ </CS:changed-by>
+ <CS:calendar-changes>
+ <CS:recurrence>
+ <CS:recurrenceid>20120210T170000Z</CS:recurrenceid>
+ <CS:changes>
+ <CS:changed-property name="DTSTART"/>
+ <CS:changed-property name="SUMMARY"/>
+ </CS:changes>
+ </CS:recurrence>
+ </CS:calendar-changes>
+ </CS:updated>
+ </CS:resource-change>
+ </CS:notification>
+
+A.6. Resource Deleted
+
+ This is an example of the body of a notification resource where one
+ resource has been deleted. The resource was a VEVENT whose next
+ occurrence was in the future on 20120210T170000Z.
+
+
+
+
+Daboo [Page 25]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:deleted>
+ <D:href>http://example.com/cyrus/calendar/new.ics</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:deleted-details>
+ <CS:deleted-component>VEVENT</CS:deleted-component>
+ <CS:deleted-summary>CalDAV Meeting</CS:deleted-summary>
+ <CS:deleted-next-instance
+ >20120210T170000Z</CS:deleted-next-instance>
+ </CS:deleted-details>
+ </CS:deleted>
+ </CS:resource-change>
+ </CS:notification>
+
+A.7. Collection Created
+
+ This is an example of the body of a notification resource where a
+ calendar collection has been created.
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:created>
+ <D:href>http://example.com/cyrus/new-calendar/</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ </CS:created>
+ </CS:resource-change>
+ </CS:notification>
+
+A.8. Collection Updated
+
+ This is an example of the body of a notification resource where
+ coalesced changes in a calendar collection are shown. In this case 1
+ child resource was created, 2 updated, and 1 deleted.
+
+
+
+Daboo [Page 26]
+\f
+ CalDAV User Notifications March 2012
+
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:collection-changes>
+ <D:href>http://example.com/cyrus/calendar/</D:href>
+ <CS:child-created>1</CS:child-created>
+ <CS:child-updated>2</CS:child-updated>
+ <CS:child-deleted>1</CS:child-deleted>
+ </CS:collection-changes>
+ </CS:resource-change>
+ </CS:notification>
+
+A.9. Collection Deleted
+
+ This is an example of the body of a notification resource where a
+ calendar collection has been deleted.
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <CS:notification xmlns:D="DAV:"
+ xmlns:CS="http://calendarserver.org/ns/">
+ <CS:dtstamp>2011-12-09T11:51:14-05:00</CS:dtstamp>
+ <CS:resource-change>
+ <CS:deleted>
+ <D:href>http://example.com/cyrus/old-calendar/</D:href>
+ <CS:changed-by>
+ <CS:first-name>Cyrus</CS:first-name>
+ <CS:last-name>Daboo</CS:last-name>
+ <D:href>/principals/cyrusdaboo</D:href>
+ </CS:changed-by>
+ <CS:deleted-details>
+ <CS:deleted-displayname>Holidays</CS:deleted-displayname>
+ </CS:deleted-details>
+ </CS:deleted>
+ </CS:resource-change>
+ </CS:notification>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 27]
+\f
+ CalDAV User Notifications March 2012
+
+
+Author's Address
+
+ Cyrus Daboo
+ Apple Inc.
+ 1 Infinite Loop
+ Cupertino, CA 95014
+ USA
+
+ Email: cyrus@daboo.name
+ URI: http://www.apple.com/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Daboo [Page 28]
+\f
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+
+
+
+
+
+
+
+ <head>
+ <title>
+ /CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt – Calendar and Contacts Server
+ </title>
+ <link rel="search" href="/search" />
+ <link rel="help" href="/wiki/TracGuide" />
+ <link rel="alternate" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?format=txt" type="text/plain" title="Plain Text" /><link rel="alternate" href="/export/9403/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" type="text/plain; charset=iso-8859-15" title="Original Format" />
+ <link rel="up" href="/browser/CalendarServer/trunk/doc/Extensions" title="Parent directory" />
+ <link rel="start" href="/wiki" />
+ <link rel="stylesheet" href="/chrome/common/css/trac.css" type="text/css" /><link rel="stylesheet" href="/chrome/common/css/code.css" type="text/css" /><link rel="stylesheet" href="/pygments/colorful.css" type="text/css" /><link rel="stylesheet" href="/chrome/common/css/browser.css" type="text/css" />
+ <link rel="shortcut icon" href="/chrome/common/trac.ico" type="image/x-icon" />
+ <link rel="icon" href="/chrome/common/trac.ico" type="image/x-icon" />
+ <link type="application/opensearchdescription+xml" rel="search" href="/search/opensearch" title="Search Calendar and Contacts Server" />
+ <script type="text/javascript" src="/chrome/common/js/jquery.js"></script><script type="text/javascript" src="/chrome/common/js/trac.js"></script><script type="text/javascript" src="/chrome/common/js/search.js"></script>
+ <!--[if lt IE 7]>
+ <script type="text/javascript" src="/chrome/common/js/ie_pre7_hacks.js"></script>
+ <![endif]-->
+ <script type="text/javascript">
+ jQuery(document).ready(function($) {
+ $(".trac-toggledeleted").show().click(function() {
+ $(this).siblings().find(".trac-deleted").toggle();
+ return false;
+ }).click();
+ $("#jumploc input").hide();
+ $("#jumploc select").change(function () {
+ this.parentNode.parentNode.submit();
+ });
+ });
+ </script>
+ <link rel="stylesheet" type="text/css" href="/static/css/style_v4.css" />
+ </head>
+ <body>
+ <div id="forge-body">
+ <div id="forge-header">
+ <div id="forge-logo">
+ <a href="http://www.macosforge.org/"><img alt="macosforge logo" src="https://static2.macosforge.org/static/images/logo_v2.png" /></a>
+ </div>
+ <div id="forge-project">
+ <a id="forge-project-logo" href="http://calendarserver.org/">
+ <img alt="project logo" src="http://static2.macosforge.org/files/logos/CalendarServer.png" />
+ </a>
+ <a id="forge-project-name" href="http://calendarserver.org/">
+ Calendar and Contacts Server
+ </a>
+ </div>
+ <div id="auth-nav">
+ <a href="/auth/register/">Register</a>
+ <a href="/auth/password/lost/">Lost Password</a>
+ <a href="/auth/login/">Login</a>
+ </div>
+ </div>
+ <div id="forge-outter">
+ <div id="left-nav">
+ <div class="heading" id="projects-list-heading">Projects</div>
+<div id="project-list" class="project-list">
+ <ul>
+ <li>
+ <a class="navlink-item" href="http://alac.macosforge.org/">Apple Lossless Audio Codec</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://calendarserver.org/">Calendar and Contacts Server</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://www.dcerpc.org/">DCERPC</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://dss.macosforge.org/">Darwin Streaming Server</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://darwinbuild.macosforge.org/">DarwinBuild</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://fstools.macosforge.org/">FS Tools</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://www.macports.org/">MacPorts</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://smartcardservices.macosforge.org/">SmartCard Services</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://www.webkit.org/">WebKit</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://xquartz.macosforge.org/">XQuartz</a>
+ </li>
+ <li>
+ <a class="navlink-item" href="http://libdispatch.macosforge.org/">libdispatch</a>
+ </li>
+ </ul>
+</div>
+ <div id="forge-footer">
+ <div id="footerlinks">
+ <a href="http://www.macosforge.org/contact/">Contact</a><br />
+ <a href="http://www.macosforge.org/terms/">Terms of Use</a><br />
+ <a href="http://www.apple.com/legal/privacy/">Privacy Policy</a><br />
+ </div>
+ <div id="footertext">
+ <br />
+ All user-submitted text and content on this website is licensed under a
+ <a href="http://creativecommons.org/licenses/by/2.5/">
+ Creative Commons Attribution 2.5 License
+ </a>
+ unless otherwise noted.
+ Copyright © 2010 Apple Inc. All rights reserved.
+ </div>
+ </div>
+ </div>
+ <div id="forge-inner">
+ <div id="top-nav">
+ <a href="/wiki">Wiki</a>
+
+ <a href="/timeline">Timeline</a>
+
+ <a href="/roadmap">Roadmap</a>
+
+ <a href="/browser">Browse Source</a>
+
+ <a href="/report">View Tickets</a>
+
+ <a href="/newticket">New Ticket</a>
+
+ <a href="/search">Search</a>
+ </div>
+ <div id="forge-content">
+ <div id="banner">
+ <div id="header">
+ <a id="logo" href="/"><img src="/chrome/site/icon.png" alt="Calendar Server" height="100" width="100" /></a>
+ </div>
+ <form id="search" action="/search" method="get">
+ <div>
+ <label for="proj-search">Search:</label>
+ <input type="text" id="proj-search" name="q" size="18" value="" />
+ <input type="submit" value="Search" />
+ </div>
+ </form>
+ <div id="metanav" class="nav">
+ </div>
+ </div>
+ <div id="main">
+ <div id="ctxtnav" class="nav">
+ <h2>Context Navigation</h2>
+ <ul>
+ <li class="first"><a href="/changeset/6401/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Last Change</a></li><li><a href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?annotate=blame&rev=6401" title="Annotate each line with the last changed revision (this can be time consuming...)">Annotate</a></li><li class="last"><a href="/log/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Revision Log</a></li>
+ </ul>
+ <hr />
+ </div>
+ <div id="content" class="browser">
+ <h1>
+ <a class="pathentry first" title="Go to root directory" href="/browser">root</a><span class="pathentry sep">/</span><a class="pathentry" title="View CalendarServer" href="/browser/CalendarServer">CalendarServer</a><span class="pathentry sep">/</span><a class="pathentry" title="View trunk" href="/browser/CalendarServer/trunk">trunk</a><span class="pathentry sep">/</span><a class="pathentry" title="View doc" href="/browser/CalendarServer/trunk/doc">doc</a><span class="pathentry sep">/</span><a class="pathentry" title="View Extensions" href="/browser/CalendarServer/trunk/doc/Extensions">Extensions</a><span class="pathentry sep">/</span><a class="pathentry" title="View caldav-sharing-02.txt" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">caldav-sharing-02.txt</a>
+ <br style="clear: both" />
+ </h1>
+ <div id="jumprev">
+ <form action="" method="get">
+ <div>
+ <label for="rev">
+ View revision:</label>
+ <input type="text" id="rev" name="rev" size="6" />
+ </div>
+ </form>
+ </div>
+ <table id="info" summary="Revision info">
+ <tr>
+ <th scope="col">
+ Revision <a href="/changeset/6401">6401</a>, <span title="54302 bytes">53.0 KB</span>
+ (checked in by cdaboo@…, <a class="timeline" href="/timeline?from=2010-10-06T07%3A54%3A17-0700&precision=second" title="2010-10-06T07:54:17-0700 in Timeline">21 months</a> ago)
+ </th>
+ </tr>
+ <tr>
+ <td class="message searchable">
+ <p>
+Sharing extension specification.<br />
+</p>
+ </td>
+ </tr>
+ </table>
+ <div id="preview" class="searchable">
+ <table class="code"><thead><tr><th class="lineno" title="Line numbers">Line</th><th class="content"> </th></tr></thead><tbody><tr><th id="L1"><a href="#L1">1</a></th><td></td></tr><tr><th id="L2"><a href="#L2">2</a></th><td></td></tr><tr><th id="L3"><a href="#L3">3</a></th><td></td></tr><tr><th id="L4"><a href="#L4">4</a></th><td>Calendar Server Extension C. Daboo</td></tr><tr><th id="L5"><a href="#L5">5</a></th><td> E. York</td></tr><tr><th id="L6"><a href="#L6">6</a></th><td> Apple Inc.</td></tr><tr><th id="L7"><a href="#L7">7</a></th><td> October 6, 2010</td></tr><tr><th id="L8"><a href="#L8">8</a></th><td></td></tr><tr><th id="L9"><a href="#L9">9</a></th><td></td></tr><tr><th id="L10"><a href="#L10">10</a></th><td> Shared and Published Calendars in CalDAV</td></tr><tr><th id="L11"><a href="#L11">11</a></th><td></td></tr><tr><th id="L12"><a href="#L12">12</a></th><td>Abstract</td></tr><tr><th id="L13"><a href="#L13">13</a></th><td></td></tr><tr><th id="L14"><a href="#L14">14</a></th><td> This specification defines an extension to CalDAV that enables the</td></tr><tr><th id="L15"><a href="#L15">15</a></th><td> sharing of calendars between users on a CalDAV server.</td></tr><tr><th id="L16"><a href="#L16">16</a></th><td></td></tr><tr><th id="L17"><a href="#L17">17</a></th><td></td></tr><tr><th id="L18"><a href="#L18">18</a></th><td>Table of Contents</td></tr><tr><th id="L19"><a href="#L19">19</a></th><td></td></tr><tr><th id="L20"><a href="#L20">20</a></th><td> 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3</td></tr><tr><th id="L21"><a href="#L21">21</a></th><td> 2. Conventions Used in This Document . . . . . . . . . . . . . . 3</td></tr><tr><th id="L22"><a href="#L22">22</a></th><td> 3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 4</td></tr><tr><th id="L23"><a href="#L23">23</a></th><td> 4. Notifications . . . . . . . . . . . . . . . . . . . . . . . . 5</td></tr><tr><th id="L24"><a href="#L24">24</a></th><td> 4.1. Additional Principal Properties . . . . . . . . . . . . . 5</td></tr><tr><th id="L25"><a href="#L25">25</a></th><td> 4.1.1. CS:notification-URL Property . . . . . . . . . . . . . 6</td></tr><tr><th id="L26"><a href="#L26">26</a></th><td> 4.2. Properties on Notification Resources . . . . . . . . . . . 6</td></tr><tr><th id="L27"><a href="#L27">27</a></th><td> 4.2.1. CS:notificationtype Property . . . . . . . . . . . . . 6</td></tr><tr><th id="L28"><a href="#L28">28</a></th><td> 5. Shared Calendaring . . . . . . . . . . . . . . . . . . . . . . 7</td></tr><tr><th id="L29"><a href="#L29">29</a></th><td> 5.1. Feature Discovery . . . . . . . . . . . . . . . . . . . . 7</td></tr><tr><th id="L30"><a href="#L30">30</a></th><td> 5.2. Additional Properties for Calendars . . . . . . . . . . . 7</td></tr><tr><th id="L31"><a href="#L31">31</a></th><td> 5.2.1. DAV:resourcetype Property . . . . . . . . . . . . . . 7</td></tr><tr><th id="L32"><a href="#L32">32</a></th><td> 5.2.2. CS:invite Property . . . . . . . . . . . . . . . . . . 8</td></tr><tr><th id="L33"><a href="#L33">33</a></th><td> 5.2.3. CS:allowed-sharing-modes Property . . . . . . . . . . 8</td></tr><tr><th id="L34"><a href="#L34">34</a></th><td> 5.2.4. CS:shared-url Property . . . . . . . . . . . . . . . . 9</td></tr><tr><th id="L35"><a href="#L35">35</a></th><td> 5.3. Sharer Actions on Shared Calendars . . . . . . . . . . . . 9</td></tr><tr><th id="L36"><a href="#L36">36</a></th><td> 5.3.1. Creating a Shared Calendar . . . . . . . . . . . . . . 9</td></tr><tr><th id="L37"><a href="#L37">37</a></th><td> 5.3.1.1. Example: Successful MKCALENDAR Request . . . . . . 10</td></tr><tr><th id="L38"><a href="#L38">38</a></th><td> 5.3.1.2. Example: Successful Extended MKCOL Request . . . . 10</td></tr><tr><th id="L39"><a href="#L39">39</a></th><td> 5.3.2. Sharing an Existing Calendar . . . . . . . . . . . . . 11</td></tr><tr><th id="L40"><a href="#L40">40</a></th><td> 5.3.2.1. Example: Successful PROPPATCH Request . . . . . . 11</td></tr><tr><th id="L41"><a href="#L41">41</a></th><td> 5.3.3. Manipulating Sharees of a Shared Calendar . . . . . . 12</td></tr><tr><th id="L42"><a href="#L42">42</a></th><td> 5.3.3.1. Example: Successful Sharee Add Request . . . . . . 13</td></tr><tr><th id="L43"><a href="#L43">43</a></th><td> 5.3.3.2. Example: Successful Multiple Sharee Change</td></tr><tr><th id="L44"><a href="#L44">44</a></th><td> Request . . . . . . . . . . . . . . . . . . . . . 14</td></tr><tr><th id="L45"><a href="#L45">45</a></th><td> 5.4. Sharee Actions on Shared Calendars . . . . . . . . . . . . 15</td></tr><tr><th id="L46"><a href="#L46">46</a></th><td> 5.4.1. Replying to a Sharing Invite . . . . . . . . . . . . . 15</td></tr><tr><th id="L47"><a href="#L47">47</a></th><td> 5.4.2. Removing a Shared Calendar . . . . . . . . . . . . . . 16</td></tr><tr><th id="L48"><a href="#L48">48</a></th><td> 5.5. General Considerations . . . . . . . . . . . . . . . . . . 16</td></tr><tr><th id="L49"><a href="#L49">49</a></th><td> 5.5.1. Access Levels . . . . . . . . . . . . . . . . . . . . 16</td></tr><tr><th id="L50"><a href="#L50">50</a></th><td> 5.5.2. Allowing or Disallowing Sharing . . . . . . . . . . . 16</td></tr><tr><th id="L51"><a href="#L51">51</a></th><td> 5.5.3. Per-user WebDAV Properties . . . . . . . . . . . . . . 17</td></tr><tr><th id="L52"><a href="#L52">52</a></th><td></td></tr><tr><th id="L53"><a href="#L53">53</a></th><td></td></tr><tr><th id="L54"><a href="#L54">54</a></th><td></td></tr><tr><th id="L55"><a href="#L55">55</a></th><td>Daboo & York [Page 1]</td></tr><tr><th id="L56"><a href="#L56">56</a></th><td></td></tr><tr><th id="L57"><a href="#L57">57</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L58"><a href="#L58">58</a></th><td></td></tr><tr><th id="L59"><a href="#L59">59</a></th><td></td></tr><tr><th id="L60"><a href="#L60">60</a></th><td> 5.5.4. Per-user Calendar Data . . . . . . . . . . . . . . . . 17</td></tr><tr><th id="L61"><a href="#L61">61</a></th><td> 5.5.5. Scheduling . . . . . . . . . . . . . . . . . . . . . . 18</td></tr><tr><th id="L62"><a href="#L62">62</a></th><td> 6. XML Element Definitions . . . . . . . . . . . . . . . . . . . 19</td></tr><tr><th id="L63"><a href="#L63">63</a></th><td> 6.1. CS:shared-owner . . . . . . . . . . . . . . . . . . . . . 19</td></tr><tr><th id="L64"><a href="#L64">64</a></th><td> 6.2. CS:shared . . . . . . . . . . . . . . . . . . . . . . . . 20</td></tr><tr><th id="L65"><a href="#L65">65</a></th><td> 6.3. CS:can-be-shared . . . . . . . . . . . . . . . . . . . . . 20</td></tr><tr><th id="L66"><a href="#L66">66</a></th><td> 6.4. CS:can-be-published . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L67"><a href="#L67">67</a></th><td> 6.5. CS:user . . . . . . . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L68"><a href="#L68">68</a></th><td> 6.6. CS:invite-noresponse . . . . . . . . . . . . . . . . . . . 21</td></tr><tr><th id="L69"><a href="#L69">69</a></th><td> 6.7. CS:invite-deleted . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L70"><a href="#L70">70</a></th><td> 6.8. CS:invite-accepted . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L71"><a href="#L71">71</a></th><td> 6.9. CS:invite-declined . . . . . . . . . . . . . . . . . . . . 22</td></tr><tr><th id="L72"><a href="#L72">72</a></th><td> 6.10. CS:invite-invalid . . . . . . . . . . . . . . . . . . . . 23</td></tr><tr><th id="L73"><a href="#L73">73</a></th><td> 6.11. CS:access . . . . . . . . . . . . . . . . . . . . . . . . 23</td></tr><tr><th id="L74"><a href="#L74">74</a></th><td> 6.12. CS:read . . . . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L75"><a href="#L75">75</a></th><td> 6.13. CS:read-write . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L76"><a href="#L76">76</a></th><td> 6.14. CS:summary . . . . . . . . . . . . . . . . . . . . . . . . 24</td></tr><tr><th id="L77"><a href="#L77">77</a></th><td> 6.15. CS:invite-notification . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L78"><a href="#L78">78</a></th><td> 6.16. CS:uid . . . . . . . . . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L79"><a href="#L79">79</a></th><td> 6.17. CS:hosturl . . . . . . . . . . . . . . . . . . . . . . . . 25</td></tr><tr><th id="L80"><a href="#L80">80</a></th><td> 6.18. CS:organizer . . . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L81"><a href="#L81">81</a></th><td> 6.19. CS:common-name . . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L82"><a href="#L82">82</a></th><td> 6.20. CS:invite-reply . . . . . . . . . . . . . . . . . . . . . 26</td></tr><tr><th id="L83"><a href="#L83">83</a></th><td> 6.21. CS:in-reply-to . . . . . . . . . . . . . . . . . . . . . . 27</td></tr><tr><th id="L84"><a href="#L84">84</a></th><td> 6.22. CS:notification . . . . . . . . . . . . . . . . . . . . . 27</td></tr><tr><th id="L85"><a href="#L85">85</a></th><td> 6.23. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L86"><a href="#L86">86</a></th><td> 6.24. CS:share . . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L87"><a href="#L87">87</a></th><td> 6.25. CS:set . . . . . . . . . . . . . . . . . . . . . . . . . . 28</td></tr><tr><th id="L88"><a href="#L88">88</a></th><td> 6.26. CS:remove . . . . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L89"><a href="#L89">89</a></th><td> 6.27. CS:shared-as . . . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L90"><a href="#L90">90</a></th><td> 7. Security Considerations . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L91"><a href="#L91">91</a></th><td> 8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 29</td></tr><tr><th id="L92"><a href="#L92">92</a></th><td> 9. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L93"><a href="#L93">93</a></th><td> 10. Normative References . . . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L94"><a href="#L94">94</a></th><td> Appendix A. Change History . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L95"><a href="#L95">95</a></th><td> Appendix B. Change History . . . . . . . . . . . . . . . . . . . 30</td></tr><tr><th id="L96"><a href="#L96">96</a></th><td> Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 31</td></tr><tr><th id="L97"><a href="#L97">97</a></th><td></td></tr><tr><th id="L98"><a href="#L98">98</a></th><td></td></tr><tr><th id="L99"><a href="#L99">99</a></th><td></td></tr><tr><th id="L100"><a href="#L100">100</a></th><td></td></tr><tr><th id="L101"><a href="#L101">101</a></th><td></td></tr><tr><th id="L102"><a href="#L102">102</a></th><td></td></tr><tr><th id="L103"><a href="#L103">103</a></th><td></td></tr><tr><th id="L104"><a href="#L104">104</a></th><td></td></tr><tr><th id="L105"><a href="#L105">105</a></th><td></td></tr><tr><th id="L106"><a href="#L106">106</a></th><td></td></tr><tr><th id="L107"><a href="#L107">107</a></th><td></td></tr><tr><th id="L108"><a href="#L108">108</a></th><td></td></tr><tr><th id="L109"><a href="#L109">109</a></th><td></td></tr><tr><th id="L110"><a href="#L110">110</a></th><td></td></tr><tr><th id="L111"><a href="#L111">111</a></th><td>Daboo & York [Page 2]</td></tr><tr><th id="L112"><a href="#L112">112</a></th><td></td></tr><tr><th id="L113"><a href="#L113">113</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L114"><a href="#L114">114</a></th><td></td></tr><tr><th id="L115"><a href="#L115">115</a></th><td></td></tr><tr><th id="L116"><a href="#L116">116</a></th><td>1. Introduction</td></tr><tr><th id="L117"><a href="#L117">117</a></th><td></td></tr><tr><th id="L118"><a href="#L118">118</a></th><td> CalDAV [RFC4791] provides a way for calendar users to store calendar</td></tr><tr><th id="L119"><a href="#L119">119</a></th><td> data and exchange this data via scheduling operations. Based on the</td></tr><tr><th id="L120"><a href="#L120">120</a></th><td> WebDAV [RFC4918] protocol, it also includes the ability to manage</td></tr><tr><th id="L121"><a href="#L121">121</a></th><td> access to calendar data via the WebDAV ACL [RFC3744] extension.</td></tr><tr><th id="L122"><a href="#L122">122</a></th><td></td></tr><tr><th id="L123"><a href="#L123">123</a></th><td> WebDAV ACL [RFC3744] provides a way to manage fine-grained access</td></tr><tr><th id="L124"><a href="#L124">124</a></th><td> controls on WebDAV resources. Whilst this could be used directly to</td></tr><tr><th id="L125"><a href="#L125">125</a></th><td> manage sharing of calendars, experience has shown that client</td></tr><tr><th id="L126"><a href="#L126">126</a></th><td> developers are averse to using it due to its complexity. Instead a</td></tr><tr><th id="L127"><a href="#L127">127</a></th><td> simpler process for sharing calendars is preferred.</td></tr><tr><th id="L128"><a href="#L128">128</a></th><td></td></tr><tr><th id="L129"><a href="#L129">129</a></th><td> This extension defines a way for individual calendar users to share</td></tr><tr><th id="L130"><a href="#L130">130</a></th><td> calendars with other users. This is done via an "opt-in" process in</td></tr><tr><th id="L131"><a href="#L131">131</a></th><td> which a sharing invite is sent from the sharer to a sharee, allowing</td></tr><tr><th id="L132"><a href="#L132">132</a></th><td> the sharee to accept or decline. If the sharee accepts the sharing</td></tr><tr><th id="L133"><a href="#L133">133</a></th><td> invite, the shared calendar is made available to them in their own</td></tr><tr><th id="L134"><a href="#L134">134</a></th><td> calendar home collection (i.e., alongside their own personal</td></tr><tr><th id="L135"><a href="#L135">135</a></th><td> calendars). HTTP POST operations are used to manage the sharing</td></tr><tr><th id="L136"><a href="#L136">136</a></th><td> invitations and replies, and WebDAV properties are used to expose the</td></tr><tr><th id="L137"><a href="#L137">137</a></th><td> state of shared calendars.</td></tr><tr><th id="L138"><a href="#L138">138</a></th><td></td></tr><tr><th id="L139"><a href="#L139">139</a></th><td></td></tr><tr><th id="L140"><a href="#L140">140</a></th><td>2. Conventions Used in This Document</td></tr><tr><th id="L141"><a href="#L141">141</a></th><td></td></tr><tr><th id="L142"><a href="#L142">142</a></th><td> The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",</td></tr><tr><th id="L143"><a href="#L143">143</a></th><td> "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this</td></tr><tr><th id="L144"><a href="#L144">144</a></th><td> document are to be interpreted as described in [RFC2119].</td></tr><tr><th id="L145"><a href="#L145">145</a></th><td></td></tr><tr><th id="L146"><a href="#L146">146</a></th><td> When XML element types in the namespaces "DAV:" and</td></tr><tr><th id="L147"><a href="#L147">147</a></th><td> "urn:ietf:params:xml:ns:caldav" are referenced in this document</td></tr><tr><th id="L148"><a href="#L148">148</a></th><td> outside of the context of an XML fragment, the string "DAV:" and</td></tr><tr><th id="L149"><a href="#L149">149</a></th><td> "CALDAV:" will be prefixed to the element type names respectively.</td></tr><tr><th id="L150"><a href="#L150">150</a></th><td></td></tr><tr><th id="L151"><a href="#L151">151</a></th><td> The namespace "http://calendarserver.org/ns/" is used for XML</td></tr><tr><th id="L152"><a href="#L152">152</a></th><td> elements defined in this specification. When XML element types in</td></tr><tr><th id="L153"><a href="#L153">153</a></th><td> that namespace are referenced in this document outside of the context</td></tr><tr><th id="L154"><a href="#L154">154</a></th><td> of an XML fragment, the string "CS:" will be prefixed to the element</td></tr><tr><th id="L155"><a href="#L155">155</a></th><td> type names.</td></tr><tr><th id="L156"><a href="#L156">156</a></th><td></td></tr><tr><th id="L157"><a href="#L157">157</a></th><td> Terms Used:</td></tr><tr><th id="L158"><a href="#L158">158</a></th><td></td></tr><tr><th id="L159"><a href="#L159">159</a></th><td> Sharer A calendar user who is sharing a calendar with other calendar</td></tr><tr><th id="L160"><a href="#L160">160</a></th><td> users.</td></tr><tr><th id="L161"><a href="#L161">161</a></th><td></td></tr><tr><th id="L162"><a href="#L162">162</a></th><td></td></tr><tr><th id="L163"><a href="#L163">163</a></th><td></td></tr><tr><th id="L164"><a href="#L164">164</a></th><td></td></tr><tr><th id="L165"><a href="#L165">165</a></th><td></td></tr><tr><th id="L166"><a href="#L166">166</a></th><td></td></tr><tr><th id="L167"><a href="#L167">167</a></th><td>Daboo & York [Page 3]</td></tr><tr><th id="L168"><a href="#L168">168</a></th><td></td></tr><tr><th id="L169"><a href="#L169">169</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L170"><a href="#L170">170</a></th><td></td></tr><tr><th id="L171"><a href="#L171">171</a></th><td></td></tr><tr><th id="L172"><a href="#L172">172</a></th><td> Sharee A calendar user to whom a calendar has been shared.</td></tr><tr><th id="L173"><a href="#L173">173</a></th><td></td></tr><tr><th id="L174"><a href="#L174">174</a></th><td> Sharing Invite A message sent by a sharer to a sharee to indicate</td></tr><tr><th id="L175"><a href="#L175">175</a></th><td> the status of a shared calendar.</td></tr><tr><th id="L176"><a href="#L176">176</a></th><td></td></tr><tr><th id="L177"><a href="#L177">177</a></th><td> Sharing Reply A message sent by a sharee to a sharer to indicate the</td></tr><tr><th id="L178"><a href="#L178">178</a></th><td> status of a shared calendar.</td></tr><tr><th id="L179"><a href="#L179">179</a></th><td></td></tr><tr><th id="L180"><a href="#L180">180</a></th><td></td></tr><tr><th id="L181"><a href="#L181">181</a></th><td>3. Overview</td></tr><tr><th id="L182"><a href="#L182">182</a></th><td></td></tr><tr><th id="L183"><a href="#L183">183</a></th><td> This section provides a basic overview of this protocol by way of a</td></tr><tr><th id="L184"><a href="#L184">184</a></th><td> simple use case of a sharer sharing a calendar with a single sharee.</td></tr><tr><th id="L185"><a href="#L185">185</a></th><td></td></tr><tr><th id="L186"><a href="#L186">186</a></th><td> To share a calendar with another user, the sharer's client executes</td></tr><tr><th id="L187"><a href="#L187">187</a></th><td> an HTTP POST request against the calendar collection resource for the</td></tr><tr><th id="L188"><a href="#L188">188</a></th><td> calendar to be shared. The POST request body will contain details of</td></tr><tr><th id="L189"><a href="#L189">189</a></th><td> the calendar user to whom the calendar is to be shared as well as the</td></tr><tr><th id="L190"><a href="#L190">190</a></th><td> access right to be granted to them. If the request succeeds, a</td></tr><tr><th id="L191"><a href="#L191">191</a></th><td> notification is sent to the sharee with details of the calendar being</td></tr><tr><th id="L192"><a href="#L192">192</a></th><td> shared to them.</td></tr><tr><th id="L193"><a href="#L193">193</a></th><td></td></tr><tr><th id="L194"><a href="#L194">194</a></th><td> The sharer's client will show the notification to the sharee and</td></tr><tr><th id="L195"><a href="#L195">195</a></th><td> present them with the choice to accept or decline the invitation to</td></tr><tr><th id="L196"><a href="#L196">196</a></th><td> the shared calendar. If the sharee chooses to decline, then nothing</td></tr><tr><th id="L197"><a href="#L197">197</a></th><td> changes for that sharee. If the sharee chooses to accept, then the</td></tr><tr><th id="L198"><a href="#L198">198</a></th><td> server automatically creates a new calendar collection resource in</td></tr><tr><th id="L199"><a href="#L199">199</a></th><td> the sharee's calendar home collection, and ensures that calendar</td></tr><tr><th id="L200"><a href="#L200">200</a></th><td> provides a mapping to the actual shared calendar of the sharer. Thus</td></tr><tr><th id="L201"><a href="#L201">201</a></th><td> the shared calendar is available to the sharee as just another</td></tr><tr><th id="L202"><a href="#L202">202</a></th><td> calendar in their calendar home. The server enforces the appropriare</td></tr><tr><th id="L203"><a href="#L203">203</a></th><td> access privileges for the sharee.</td></tr><tr><th id="L204"><a href="#L204">204</a></th><td></td></tr><tr><th id="L205"><a href="#L205">205</a></th><td> At any time, the sharer can inspect properties on the calendar</td></tr><tr><th id="L206"><a href="#L206">206</a></th><td> collection being shared, and determine the accept/decline status of</td></tr><tr><th id="L207"><a href="#L207">207</a></th><td> each sharee. Additional sharees can be added and existing ones</td></tr><tr><th id="L208"><a href="#L208">208</a></th><td> removed. The access privileges for existing sharees can also be</td></tr><tr><th id="L209"><a href="#L209">209</a></th><td> changed.</td></tr><tr><th id="L210"><a href="#L210">210</a></th><td></td></tr><tr><th id="L211"><a href="#L211">211</a></th><td> Once a sharee has a shared calendar set to appear in their calendar</td></tr><tr><th id="L212"><a href="#L212">212</a></th><td> home collection, they can remove it and decline the sharing invite by</td></tr><tr><th id="L213"><a href="#L213">213</a></th><td> simply having their client issue an HTTP DELETE request on the shared</td></tr><tr><th id="L214"><a href="#L214">214</a></th><td> calendar collection. That does not delete any calendar data, but</td></tr><tr><th id="L215"><a href="#L215">215</a></th><td> rather simply removes the "link" to the sharer's calendar collection</td></tr><tr><th id="L216"><a href="#L216">216</a></th><td> and sets the sharee's inviate status to declined.</td></tr><tr><th id="L217"><a href="#L217">217</a></th><td></td></tr><tr><th id="L218"><a href="#L218">218</a></th><td></td></tr><tr><th id="L219"><a href="#L219">219</a></th><td></td></tr><tr><th id="L220"><a href="#L220">220</a></th><td></td></tr><tr><th id="L221"><a href="#L221">221</a></th><td></td></tr><tr><th id="L222"><a href="#L222">222</a></th><td></td></tr><tr><th id="L223"><a href="#L223">223</a></th><td>Daboo & York [Page 4]</td></tr><tr><th id="L224"><a href="#L224">224</a></th><td></td></tr><tr><th id="L225"><a href="#L225">225</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L226"><a href="#L226">226</a></th><td></td></tr><tr><th id="L227"><a href="#L227">227</a></th><td></td></tr><tr><th id="L228"><a href="#L228">228</a></th><td>4. Notifications</td></tr><tr><th id="L229"><a href="#L229">229</a></th><td></td></tr><tr><th id="L230"><a href="#L230">230</a></th><td> In order to facilitate the process of sharing invitations, this</td></tr><tr><th id="L231"><a href="#L231">231</a></th><td> specification defines a new generic notification mechanism for CalDAV</td></tr><tr><th id="L232"><a href="#L232">232</a></th><td> servers. When this feature is available, a CS:notification-URL</td></tr><tr><th id="L233"><a href="#L233">233</a></th><td> (Section 4.1.1) property appears on principal resources for those</td></tr><tr><th id="L234"><a href="#L234">234</a></th><td> principals who are able to receive notifications. That property</td></tr><tr><th id="L235"><a href="#L235">235</a></th><td> specifies a single DAV:href element whose content refers to a WebDAV</td></tr><tr><th id="L236"><a href="#L236">236</a></th><td> collection resource. Notification "messages" are deposited into this</td></tr><tr><th id="L237"><a href="#L237">237</a></th><td> collection and can be retrieved by clients and acted on accordingly.</td></tr><tr><th id="L238"><a href="#L238">238</a></th><td></td></tr><tr><th id="L239"><a href="#L239">239</a></th><td> The notification collection referenced by the CS:notification-URL</td></tr><tr><th id="L240"><a href="#L240">240</a></th><td> (Section 4.1.1) property MUST have a DAV:resourcetype property with</td></tr><tr><th id="L241"><a href="#L241">241</a></th><td> DAV:collection and CS:notification (Section 6.22) child elements.</td></tr><tr><th id="L242"><a href="#L242">242</a></th><td></td></tr><tr><th id="L243"><a href="#L243">243</a></th><td> Notification "messages" are XML documents stored as resources in the</td></tr><tr><th id="L244"><a href="#L244">244</a></th><td> notification collection. Each XML document contains a CS:</td></tr><tr><th id="L245"><a href="#L245">245</a></th><td> notification (Section 6.22) element as its root. The root element</td></tr><tr><th id="L246"><a href="#L246">246</a></th><td> contains a CS:dtstamp (Section 6.23) element, and one additional</td></tr><tr><th id="L247"><a href="#L247">247</a></th><td> element which represents the type of notification being conveyed in</td></tr><tr><th id="L248"><a href="#L248">248</a></th><td> the message. That child element will typically contain additional</td></tr><tr><th id="L249"><a href="#L249">249</a></th><td> content that describes the notification.</td></tr><tr><th id="L250"><a href="#L250">250</a></th><td></td></tr><tr><th id="L251"><a href="#L251">251</a></th><td> Each notification resource has a CS:notificationtype (Section 4.2.1)</td></tr><tr><th id="L252"><a href="#L252">252</a></th><td> property which contains as its single child element an empty element</td></tr><tr><th id="L253"><a href="#L253">253</a></th><td> that matches the child element of the notification resource XML</td></tr><tr><th id="L254"><a href="#L254">254</a></th><td> document root. Any attributes on the child element in the XML</td></tr><tr><th id="L255"><a href="#L255">255</a></th><td> document are also present in the property child element.</td></tr><tr><th id="L256"><a href="#L256">256</a></th><td></td></tr><tr><th id="L257"><a href="#L257">257</a></th><td> Notifications are automatically generated by the server (perhaps in</td></tr><tr><th id="L258"><a href="#L258">258</a></th><td> response to a client action) with an appropriate resource stored in</td></tr><tr><th id="L259"><a href="#L259">259</a></th><td> the notifications collection of the user to whom the notification is</td></tr><tr><th id="L260"><a href="#L260">260</a></th><td> targeted. Clients SHOULD monitor the notification collection looking</td></tr><tr><th id="L261"><a href="#L261">261</a></th><td> for new notification resources. When doing so, clients SHOULD look</td></tr><tr><th id="L262"><a href="#L262">262</a></th><td> at the CS:notificationtype (Section 4.2.1) property to ensure that</td></tr><tr><th id="L263"><a href="#L263">263</a></th><td> the notification is of a type that the client can handle. Once a</td></tr><tr><th id="L264"><a href="#L264">264</a></th><td> client has handled the notification in whatever way is appropriate it</td></tr><tr><th id="L265"><a href="#L265">265</a></th><td> SHOULD delete the notification resource. Servers MAY delete</td></tr><tr><th id="L266"><a href="#L266">266</a></th><td> notification resources on their own if they determine that the</td></tr><tr><th id="L267"><a href="#L267">267</a></th><td> notifications are no longer relevant or valid. Servers MAY coalesce</td></tr><tr><th id="L268"><a href="#L268">268</a></th><td> notifications as appropriate.</td></tr><tr><th id="L269"><a href="#L269">269</a></th><td></td></tr><tr><th id="L270"><a href="#L270">270</a></th><td>4.1. Additional Principal Properties</td></tr><tr><th id="L271"><a href="#L271">271</a></th><td></td></tr><tr><th id="L272"><a href="#L272">272</a></th><td> This section defines new properties for WebDAV principal resources as</td></tr><tr><th id="L273"><a href="#L273">273</a></th><td> defined in RFC3744 [RFC3744]. These properties are likely to be</td></tr><tr><th id="L274"><a href="#L274">274</a></th><td> protected but the server MAY allow them to be written by appropriate</td></tr><tr><th id="L275"><a href="#L275">275</a></th><td> users.</td></tr><tr><th id="L276"><a href="#L276">276</a></th><td></td></tr><tr><th id="L277"><a href="#L277">277</a></th><td></td></tr><tr><th id="L278"><a href="#L278">278</a></th><td></td></tr><tr><th id="L279"><a href="#L279">279</a></th><td>Daboo & York [Page 5]</td></tr><tr><th id="L280"><a href="#L280">280</a></th><td></td></tr><tr><th id="L281"><a href="#L281">281</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L282"><a href="#L282">282</a></th><td></td></tr><tr><th id="L283"><a href="#L283">283</a></th><td></td></tr><tr><th id="L284"><a href="#L284">284</a></th><td>4.1.1. CS:notification-URL Property</td></tr><tr><th id="L285"><a href="#L285">285</a></th><td></td></tr><tr><th id="L286"><a href="#L286">286</a></th><td> Name: notification-URL</td></tr><tr><th id="L287"><a href="#L287">287</a></th><td></td></tr><tr><th id="L288"><a href="#L288">288</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L289"><a href="#L289">289</a></th><td></td></tr><tr><th id="L290"><a href="#L290">290</a></th><td> Purpose: Identify the URL of the notification collection owned by</td></tr><tr><th id="L291"><a href="#L291">291</a></th><td> the associated principal resource.</td></tr><tr><th id="L292"><a href="#L292">292</a></th><td></td></tr><tr><th id="L293"><a href="#L293">293</a></th><td> Protected: This property SHOULD be protected.</td></tr><tr><th id="L294"><a href="#L294">294</a></th><td></td></tr><tr><th id="L295"><a href="#L295">295</a></th><td> PROPFIND behavior: This property SHOULD NOT be returned by a</td></tr><tr><th id="L296"><a href="#L296">296</a></th><td> PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L297"><a href="#L297">297</a></th><td> [RFC4918]).</td></tr><tr><th id="L298"><a href="#L298">298</a></th><td></td></tr><tr><th id="L299"><a href="#L299">299</a></th><td> COPY/MOVE behavior: This property value SHOULD be preserved in COPY</td></tr><tr><th id="L300"><a href="#L300">300</a></th><td> and MOVE operations.</td></tr><tr><th id="L301"><a href="#L301">301</a></th><td></td></tr><tr><th id="L302"><a href="#L302">302</a></th><td> Description: This property is needed for a client to determine where</td></tr><tr><th id="L303"><a href="#L303">303</a></th><td> the notification collection of the current user is located so that</td></tr><tr><th id="L304"><a href="#L304">304</a></th><td> processing of notification messages can occur. If not present,</td></tr><tr><th id="L305"><a href="#L305">305</a></th><td> then the associated calendar user is not enabled for notification</td></tr><tr><th id="L306"><a href="#L306">306</a></th><td> messages on the server.</td></tr><tr><th id="L307"><a href="#L307">307</a></th><td></td></tr><tr><th id="L308"><a href="#L308">308</a></th><td> Definition:</td></tr><tr><th id="L309"><a href="#L309">309</a></th><td></td></tr><tr><th id="L310"><a href="#L310">310</a></th><td> <!ELEMENT notification-URL (DAV:href)></td></tr><tr><th id="L311"><a href="#L311">311</a></th><td></td></tr><tr><th id="L312"><a href="#L312">312</a></th><td>4.2. Properties on Notification Resources</td></tr><tr><th id="L313"><a href="#L313">313</a></th><td></td></tr><tr><th id="L314"><a href="#L314">314</a></th><td> The following new WebDAV properties are defined for notification</td></tr><tr><th id="L315"><a href="#L315">315</a></th><td> resources.</td></tr><tr><th id="L316"><a href="#L316">316</a></th><td></td></tr><tr><th id="L317"><a href="#L317">317</a></th><td>4.2.1. CS:notificationtype Property</td></tr><tr><th id="L318"><a href="#L318">318</a></th><td></td></tr><tr><th id="L319"><a href="#L319">319</a></th><td> Name: notificationtype</td></tr><tr><th id="L320"><a href="#L320">320</a></th><td></td></tr><tr><th id="L321"><a href="#L321">321</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L322"><a href="#L322">322</a></th><td></td></tr><tr><th id="L323"><a href="#L323">323</a></th><td> Purpose: Identify the type of notification of the corresponding</td></tr><tr><th id="L324"><a href="#L324">324</a></th><td> resource.</td></tr><tr><th id="L325"><a href="#L325">325</a></th><td></td></tr><tr><th id="L326"><a href="#L326">326</a></th><td> Protected: This property MUST be protected.</td></tr><tr><th id="L327"><a href="#L327">327</a></th><td></td></tr><tr><th id="L328"><a href="#L328">328</a></th><td> PROPFIND behavior: This property SHOULD NOT be returned by a</td></tr><tr><th id="L329"><a href="#L329">329</a></th><td> PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L330"><a href="#L330">330</a></th><td> [RFC4918]).</td></tr><tr><th id="L331"><a href="#L331">331</a></th><td></td></tr><tr><th id="L332"><a href="#L332">332</a></th><td></td></tr><tr><th id="L333"><a href="#L333">333</a></th><td></td></tr><tr><th id="L334"><a href="#L334">334</a></th><td></td></tr><tr><th id="L335"><a href="#L335">335</a></th><td>Daboo & York [Page 6]</td></tr><tr><th id="L336"><a href="#L336">336</a></th><td></td></tr><tr><th id="L337"><a href="#L337">337</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L338"><a href="#L338">338</a></th><td></td></tr><tr><th id="L339"><a href="#L339">339</a></th><td></td></tr><tr><th id="L340"><a href="#L340">340</a></th><td> COPY/MOVE behavior: This property value MUST be preserved in COPY</td></tr><tr><th id="L341"><a href="#L341">341</a></th><td> and MOVE operations.</td></tr><tr><th id="L342"><a href="#L342">342</a></th><td></td></tr><tr><th id="L343"><a href="#L343">343</a></th><td> Description: This property allows a client, via a PROPFIND Depth:1</td></tr><tr><th id="L344"><a href="#L344">344</a></th><td> request, to quickly find notification messages that the client can</td></tr><tr><th id="L345"><a href="#L345">345</a></th><td> handle in a notification collection. The single child element is</td></tr><tr><th id="L346"><a href="#L346">346</a></th><td> the notification resource root element's child defining the</td></tr><tr><th id="L347"><a href="#L347">347</a></th><td> notification itself. This element MUST be empty, though any</td></tr><tr><th id="L348"><a href="#L348">348</a></th><td> attributes on the element in the notification resource MUST be</td></tr><tr><th id="L349"><a href="#L349">349</a></th><td> present in the property element.</td></tr><tr><th id="L350"><a href="#L350">350</a></th><td></td></tr><tr><th id="L351"><a href="#L351">351</a></th><td> Definition:</td></tr><tr><th id="L352"><a href="#L352">352</a></th><td></td></tr><tr><th id="L353"><a href="#L353">353</a></th><td> <!ELEMENT notificationtype (invite-notification | invite-reply)></td></tr><tr><th id="L354"><a href="#L354">354</a></th><td> <!-- Child elements are empty but will have appropriate attributes.</td></tr><tr><th id="L355"><a href="#L355">355</a></th><td> Any valid notification message child element can appear.--></td></tr><tr><th id="L356"><a href="#L356">356</a></th><td></td></tr><tr><th id="L357"><a href="#L357">357</a></th><td></td></tr><tr><th id="L358"><a href="#L358">358</a></th><td>5. Shared Calendaring</td></tr><tr><th id="L359"><a href="#L359">359</a></th><td></td></tr><tr><th id="L360"><a href="#L360">360</a></th><td>5.1. Feature Discovery</td></tr><tr><th id="L361"><a href="#L361">361</a></th><td></td></tr><tr><th id="L362"><a href="#L362">362</a></th><td> A server that supports the features described in this document MUST</td></tr><tr><th id="L363"><a href="#L363">363</a></th><td> include "calendarserver-sharing" as a field in the DAV response</td></tr><tr><th id="L364"><a href="#L364">364</a></th><td> header from an OPTIONS request on any resource that supports these</td></tr><tr><th id="L365"><a href="#L365">365</a></th><td> features.</td></tr><tr><th id="L366"><a href="#L366">366</a></th><td></td></tr><tr><th id="L367"><a href="#L367">367</a></th><td>5.2. Additional Properties for Calendars</td></tr><tr><th id="L368"><a href="#L368">368</a></th><td></td></tr><tr><th id="L369"><a href="#L369">369</a></th><td> The following new or modified WebDAV properties are defined for</td></tr><tr><th id="L370"><a href="#L370">370</a></th><td> calendar collections and used to view or manipulate shared calendar</td></tr><tr><th id="L371"><a href="#L371">371</a></th><td> features.</td></tr><tr><th id="L372"><a href="#L372">372</a></th><td></td></tr><tr><th id="L373"><a href="#L373">373</a></th><td>5.2.1. DAV:resourcetype Property</td></tr><tr><th id="L374"><a href="#L374">374</a></th><td></td></tr><tr><th id="L375"><a href="#L375">375</a></th><td> Calendar collections that are shared have addition elements listed in</td></tr><tr><th id="L376"><a href="#L376">376</a></th><td> their DAV:resourcetype property in addition to DAV:collection and</td></tr><tr><th id="L377"><a href="#L377">377</a></th><td> CALDAV:calendar.</td></tr><tr><th id="L378"><a href="#L378">378</a></th><td></td></tr><tr><th id="L379"><a href="#L379">379</a></th><td> o CS:shared-owner (Section 6.1): used to indicate that the calendar</td></tr><tr><th id="L380"><a href="#L380">380</a></th><td> is owned by the current user and is being shared by them.</td></tr><tr><th id="L381"><a href="#L381">381</a></th><td></td></tr><tr><th id="L382"><a href="#L382">382</a></th><td> o CS:shared (Section 6.2): used to indicate that the calendar is</td></tr><tr><th id="L383"><a href="#L383">383</a></th><td> owned by another user and is being shared to the current user.</td></tr><tr><th id="L384"><a href="#L384">384</a></th><td></td></tr><tr><th id="L385"><a href="#L385">385</a></th><td></td></tr><tr><th id="L386"><a href="#L386">386</a></th><td></td></tr><tr><th id="L387"><a href="#L387">387</a></th><td></td></tr><tr><th id="L388"><a href="#L388">388</a></th><td></td></tr><tr><th id="L389"><a href="#L389">389</a></th><td></td></tr><tr><th id="L390"><a href="#L390">390</a></th><td></td></tr><tr><th id="L391"><a href="#L391">391</a></th><td>Daboo & York [Page 7]</td></tr><tr><th id="L392"><a href="#L392">392</a></th><td></td></tr><tr><th id="L393"><a href="#L393">393</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L394"><a href="#L394">394</a></th><td></td></tr><tr><th id="L395"><a href="#L395">395</a></th><td></td></tr><tr><th id="L396"><a href="#L396">396</a></th><td>5.2.2. CS:invite Property</td></tr><tr><th id="L397"><a href="#L397">397</a></th><td></td></tr><tr><th id="L398"><a href="#L398">398</a></th><td> Name: invite</td></tr><tr><th id="L399"><a href="#L399">399</a></th><td></td></tr><tr><th id="L400"><a href="#L400">400</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L401"><a href="#L401">401</a></th><td></td></tr><tr><th id="L402"><a href="#L402">402</a></th><td> Purpose: Used to show to whom a calendar has been shared.</td></tr><tr><th id="L403"><a href="#L403">403</a></th><td></td></tr><tr><th id="L404"><a href="#L404">404</a></th><td> Protected: This property MUST be protected.</td></tr><tr><th id="L405"><a href="#L405">405</a></th><td></td></tr><tr><th id="L406"><a href="#L406">406</a></th><td> PROPFIND behavior: This property SHOULD NOT be returned by a</td></tr><tr><th id="L407"><a href="#L407">407</a></th><td> PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L408"><a href="#L408">408</a></th><td> [RFC4918]).</td></tr><tr><th id="L409"><a href="#L409">409</a></th><td></td></tr><tr><th id="L410"><a href="#L410">410</a></th><td> COPY/MOVE behavior: This property value MUST be preserved in COPY</td></tr><tr><th id="L411"><a href="#L411">411</a></th><td> and MOVE operations.</td></tr><tr><th id="L412"><a href="#L412">412</a></th><td></td></tr><tr><th id="L413"><a href="#L413">413</a></th><td> Description: This WebDAV property is present on a calendar</td></tr><tr><th id="L414"><a href="#L414">414</a></th><td> collection resource that has been shared by the owner. It MUST</td></tr><tr><th id="L415"><a href="#L415">415</a></th><td> NOT appear on the calendar collection resources of the sharees of</td></tr><tr><th id="L416"><a href="#L416">416</a></th><td> the calendar. It provides a list of users to whom the calendar</td></tr><tr><th id="L417"><a href="#L417">417</a></th><td> has been shared, along with the "status" of the sharing invites</td></tr><tr><th id="L418"><a href="#L418">418</a></th><td> sent to each user.</td></tr><tr><th id="L419"><a href="#L419">419</a></th><td></td></tr><tr><th id="L420"><a href="#L420">420</a></th><td> Definition:</td></tr><tr><th id="L421"><a href="#L421">421</a></th><td></td></tr><tr><th id="L422"><a href="#L422">422</a></th><td> <!ELEMENT invite (user*)></td></tr><tr><th id="L423"><a href="#L423">423</a></th><td></td></tr><tr><th id="L424"><a href="#L424">424</a></th><td>5.2.3. CS:allowed-sharing-modes Property</td></tr><tr><th id="L425"><a href="#L425">425</a></th><td></td></tr><tr><th id="L426"><a href="#L426">426</a></th><td> Name: allowed-sharing-modes</td></tr><tr><th id="L427"><a href="#L427">427</a></th><td></td></tr><tr><th id="L428"><a href="#L428">428</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L429"><a href="#L429">429</a></th><td></td></tr><tr><th id="L430"><a href="#L430">430</a></th><td> Purpose: Used to show which modes of sharing are supported on a</td></tr><tr><th id="L431"><a href="#L431">431</a></th><td> calendar collection.</td></tr><tr><th id="L432"><a href="#L432">432</a></th><td></td></tr><tr><th id="L433"><a href="#L433">433</a></th><td> Protected: This property MUST be protected.</td></tr><tr><th id="L434"><a href="#L434">434</a></th><td></td></tr><tr><th id="L435"><a href="#L435">435</a></th><td> PROPFIND behavior: This property SHOULD NOT be returned by a</td></tr><tr><th id="L436"><a href="#L436">436</a></th><td> PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L437"><a href="#L437">437</a></th><td> [RFC4918]).</td></tr><tr><th id="L438"><a href="#L438">438</a></th><td></td></tr><tr><th id="L439"><a href="#L439">439</a></th><td> COPY/MOVE behavior: This property value MUST be preserved in COPY</td></tr><tr><th id="L440"><a href="#L440">440</a></th><td> and MOVE operations.</td></tr><tr><th id="L441"><a href="#L441">441</a></th><td></td></tr><tr><th id="L442"><a href="#L442">442</a></th><td></td></tr><tr><th id="L443"><a href="#L443">443</a></th><td></td></tr><tr><th id="L444"><a href="#L444">444</a></th><td></td></tr><tr><th id="L445"><a href="#L445">445</a></th><td></td></tr><tr><th id="L446"><a href="#L446">446</a></th><td></td></tr><tr><th id="L447"><a href="#L447">447</a></th><td>Daboo & York [Page 8]</td></tr><tr><th id="L448"><a href="#L448">448</a></th><td></td></tr><tr><th id="L449"><a href="#L449">449</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L450"><a href="#L450">450</a></th><td></td></tr><tr><th id="L451"><a href="#L451">451</a></th><td></td></tr><tr><th id="L452"><a href="#L452">452</a></th><td> Description: This WebDAV property is present on a calendar</td></tr><tr><th id="L453"><a href="#L453">453</a></th><td> collection resource that can been shared or published. It</td></tr><tr><th id="L454"><a href="#L454">454</a></th><td> provides a list of options indicating what sharing modes are</td></tr><tr><th id="L455"><a href="#L455">455</a></th><td> allowed as per Section 5.5.2.</td></tr><tr><th id="L456"><a href="#L456">456</a></th><td></td></tr><tr><th id="L457"><a href="#L457">457</a></th><td> Definition:</td></tr><tr><th id="L458"><a href="#L458">458</a></th><td></td></tr><tr><th id="L459"><a href="#L459">459</a></th><td> <!ELEMENT allowed-sharing-modes</td></tr><tr><th id="L460"><a href="#L460">460</a></th><td> (can-be-shared?, can-be-published?)></td></tr><tr><th id="L461"><a href="#L461">461</a></th><td></td></tr><tr><th id="L462"><a href="#L462">462</a></th><td>5.2.4. CS:shared-url Property</td></tr><tr><th id="L463"><a href="#L463">463</a></th><td></td></tr><tr><th id="L464"><a href="#L464">464</a></th><td> Name: shared-url</td></tr><tr><th id="L465"><a href="#L465">465</a></th><td></td></tr><tr><th id="L466"><a href="#L466">466</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L467"><a href="#L467">467</a></th><td></td></tr><tr><th id="L468"><a href="#L468">468</a></th><td> Purpose: Indicates the URL of the owner's copy of a shared calendar.</td></tr><tr><th id="L469"><a href="#L469">469</a></th><td></td></tr><tr><th id="L470"><a href="#L470">470</a></th><td> Protected: This property MUST be protected.</td></tr><tr><th id="L471"><a href="#L471">471</a></th><td></td></tr><tr><th id="L472"><a href="#L472">472</a></th><td> PROPFIND behavior: This property SHOULD NOT be returned by a</td></tr><tr><th id="L473"><a href="#L473">473</a></th><td> PROPFIND allprop request (as defined in Section 14.2 of</td></tr><tr><th id="L474"><a href="#L474">474</a></th><td> [RFC4918]).</td></tr><tr><th id="L475"><a href="#L475">475</a></th><td></td></tr><tr><th id="L476"><a href="#L476">476</a></th><td> COPY/MOVE behavior: This property value MUST be preserved in COPY</td></tr><tr><th id="L477"><a href="#L477">477</a></th><td> and MOVE operations.</td></tr><tr><th id="L478"><a href="#L478">478</a></th><td></td></tr><tr><th id="L479"><a href="#L479">479</a></th><td> Description: This WebDAV property is present on a shared calendar</td></tr><tr><th id="L480"><a href="#L480">480</a></th><td> collection resource that appears in a sharee's calendar home</td></tr><tr><th id="L481"><a href="#L481">481</a></th><td> collection. Its content is a single DAV:href element whose value</td></tr><tr><th id="L482"><a href="#L482">482</a></th><td> is the URL of the sharer's calendar being shared.</td></tr><tr><th id="L483"><a href="#L483">483</a></th><td></td></tr><tr><th id="L484"><a href="#L484">484</a></th><td> Definition:</td></tr><tr><th id="L485"><a href="#L485">485</a></th><td></td></tr><tr><th id="L486"><a href="#L486">486</a></th><td> <!ELEMENT shared-url (DAV:href)></td></tr><tr><th id="L487"><a href="#L487">487</a></th><td></td></tr><tr><th id="L488"><a href="#L488">488</a></th><td>5.3. Sharer Actions on Shared Calendars</td></tr><tr><th id="L489"><a href="#L489">489</a></th><td></td></tr><tr><th id="L490"><a href="#L490">490</a></th><td>5.3.1. Creating a Shared Calendar</td></tr><tr><th id="L491"><a href="#L491">491</a></th><td></td></tr><tr><th id="L492"><a href="#L492">492</a></th><td> To create a shared calendar, clients use the MKCALENDAR [RFC4791] or</td></tr><tr><th id="L493"><a href="#L493">493</a></th><td> extended MKCOL [RFC5689] requests, and include a DAV:resourcetype</td></tr><tr><th id="L494"><a href="#L494">494</a></th><td> property to be set upon creation. That property MUST contain DAV:</td></tr><tr><th id="L495"><a href="#L495">495</a></th><td> collection, CALDAV:calendar and CS:shared-owner child elements to</td></tr><tr><th id="L496"><a href="#L496">496</a></th><td> enable sharing.</td></tr><tr><th id="L497"><a href="#L497">497</a></th><td></td></tr><tr><th id="L498"><a href="#L498">498</a></th><td></td></tr><tr><th id="L499"><a href="#L499">499</a></th><td></td></tr><tr><th id="L500"><a href="#L500">500</a></th><td></td></tr><tr><th id="L501"><a href="#L501">501</a></th><td></td></tr><tr><th id="L502"><a href="#L502">502</a></th><td></td></tr><tr><th id="L503"><a href="#L503">503</a></th><td>Daboo & York [Page 9]</td></tr><tr><th id="L504"><a href="#L504">504</a></th><td></td></tr><tr><th id="L505"><a href="#L505">505</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L506"><a href="#L506">506</a></th><td></td></tr><tr><th id="L507"><a href="#L507">507</a></th><td></td></tr><tr><th id="L508"><a href="#L508">508</a></th><td>5.3.1.1. Example: Successful MKCALENDAR Request</td></tr><tr><th id="L509"><a href="#L509">509</a></th><td></td></tr><tr><th id="L510"><a href="#L510">510</a></th><td> This example shows how the MKCALENDAR request is used to create a</td></tr><tr><th id="L511"><a href="#L511">511</a></th><td> shared calendar collection. The response body is empty as the</td></tr><tr><th id="L512"><a href="#L512">512</a></th><td> request completed successfully.</td></tr><tr><th id="L513"><a href="#L513">513</a></th><td></td></tr><tr><th id="L514"><a href="#L514">514</a></th><td> >> Request <<</td></tr><tr><th id="L515"><a href="#L515">515</a></th><td></td></tr><tr><th id="L516"><a href="#L516">516</a></th><td> MKCALENDAR /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L517"><a href="#L517">517</a></th><td> Host: calendar.example.com</td></tr><tr><th id="L518"><a href="#L518">518</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L519"><a href="#L519">519</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L520"><a href="#L520">520</a></th><td></td></tr><tr><th id="L521"><a href="#L521">521</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L522"><a href="#L522">522</a></th><td> <C:mkcalendar xmlns:D="DAV:"</td></tr><tr><th id="L523"><a href="#L523">523</a></th><td> xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L524"><a href="#L524">524</a></th><td> xmlns:CS="http://calendarserver.org/ns/"></td></tr><tr><th id="L525"><a href="#L525">525</a></th><td> <D:set></td></tr><tr><th id="L526"><a href="#L526">526</a></th><td> <D:prop></td></tr><tr><th id="L527"><a href="#L527">527</a></th><td> <D:resourcetype></td></tr><tr><th id="L528"><a href="#L528">528</a></th><td> <D:collection/></td></tr><tr><th id="L529"><a href="#L529">529</a></th><td> <C:calendar/></td></tr><tr><th id="L530"><a href="#L530">530</a></th><td> <CS:shared-owner/></td></tr><tr><th id="L531"><a href="#L531">531</a></th><td> </D:resourcetype></td></tr><tr><th id="L532"><a href="#L532">532</a></th><td> </D:prop></td></tr><tr><th id="L533"><a href="#L533">533</a></th><td> </D:set></td></tr><tr><th id="L534"><a href="#L534">534</a></th><td> </C:mkcalendar></td></tr><tr><th id="L535"><a href="#L535">535</a></th><td></td></tr><tr><th id="L536"><a href="#L536">536</a></th><td> >> Response <<</td></tr><tr><th id="L537"><a href="#L537">537</a></th><td></td></tr><tr><th id="L538"><a href="#L538">538</a></th><td> HTTP/1.1 201 Created</td></tr><tr><th id="L539"><a href="#L539">539</a></th><td> Cache-Control: no-cache</td></tr><tr><th id="L540"><a href="#L540">540</a></th><td> Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L541"><a href="#L541">541</a></th><td></td></tr><tr><th id="L542"><a href="#L542">542</a></th><td>5.3.1.2. Example: Successful Extended MKCOL Request</td></tr><tr><th id="L543"><a href="#L543">543</a></th><td></td></tr><tr><th id="L544"><a href="#L544">544</a></th><td> This example shows how the extended MKCOL request is used to create a</td></tr><tr><th id="L545"><a href="#L545">545</a></th><td> shared calendar collection. The response body is empty as the</td></tr><tr><th id="L546"><a href="#L546">546</a></th><td> request completed successfully.</td></tr><tr><th id="L547"><a href="#L547">547</a></th><td></td></tr><tr><th id="L548"><a href="#L548">548</a></th><td></td></tr><tr><th id="L549"><a href="#L549">549</a></th><td></td></tr><tr><th id="L550"><a href="#L550">550</a></th><td></td></tr><tr><th id="L551"><a href="#L551">551</a></th><td></td></tr><tr><th id="L552"><a href="#L552">552</a></th><td></td></tr><tr><th id="L553"><a href="#L553">553</a></th><td></td></tr><tr><th id="L554"><a href="#L554">554</a></th><td></td></tr><tr><th id="L555"><a href="#L555">555</a></th><td></td></tr><tr><th id="L556"><a href="#L556">556</a></th><td></td></tr><tr><th id="L557"><a href="#L557">557</a></th><td></td></tr><tr><th id="L558"><a href="#L558">558</a></th><td></td></tr><tr><th id="L559"><a href="#L559">559</a></th><td>Daboo & York [Page 10]</td></tr><tr><th id="L560"><a href="#L560">560</a></th><td></td></tr><tr><th id="L561"><a href="#L561">561</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L562"><a href="#L562">562</a></th><td></td></tr><tr><th id="L563"><a href="#L563">563</a></th><td></td></tr><tr><th id="L564"><a href="#L564">564</a></th><td> >> Request <<</td></tr><tr><th id="L565"><a href="#L565">565</a></th><td></td></tr><tr><th id="L566"><a href="#L566">566</a></th><td> MKCOL /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L567"><a href="#L567">567</a></th><td> Host: calendar.example.com</td></tr><tr><th id="L568"><a href="#L568">568</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L569"><a href="#L569">569</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L570"><a href="#L570">570</a></th><td></td></tr><tr><th id="L571"><a href="#L571">571</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L572"><a href="#L572">572</a></th><td> <D:mkcol xmlns:D="DAV:"</td></tr><tr><th id="L573"><a href="#L573">573</a></th><td> xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L574"><a href="#L574">574</a></th><td> xmlns:CS="http://calendarserver.org/ns/"></td></tr><tr><th id="L575"><a href="#L575">575</a></th><td> <D:set></td></tr><tr><th id="L576"><a href="#L576">576</a></th><td> <D:prop></td></tr><tr><th id="L577"><a href="#L577">577</a></th><td> <D:resourcetype></td></tr><tr><th id="L578"><a href="#L578">578</a></th><td> <D:collection/></td></tr><tr><th id="L579"><a href="#L579">579</a></th><td> <C:calendar/></td></tr><tr><th id="L580"><a href="#L580">580</a></th><td> <CS:shared-owner/></td></tr><tr><th id="L581"><a href="#L581">581</a></th><td> </D:resourcetype></td></tr><tr><th id="L582"><a href="#L582">582</a></th><td> </D:prop></td></tr><tr><th id="L583"><a href="#L583">583</a></th><td> </D:set></td></tr><tr><th id="L584"><a href="#L584">584</a></th><td> </D:mkcol></td></tr><tr><th id="L585"><a href="#L585">585</a></th><td></td></tr><tr><th id="L586"><a href="#L586">586</a></th><td> >> Response <<</td></tr><tr><th id="L587"><a href="#L587">587</a></th><td></td></tr><tr><th id="L588"><a href="#L588">588</a></th><td> HTTP/1.1 201 Created</td></tr><tr><th id="L589"><a href="#L589">589</a></th><td> Cache-Control: no-cache</td></tr><tr><th id="L590"><a href="#L590">590</a></th><td> Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L591"><a href="#L591">591</a></th><td></td></tr><tr><th id="L592"><a href="#L592">592</a></th><td>5.3.2. Sharing an Existing Calendar</td></tr><tr><th id="L593"><a href="#L593">593</a></th><td></td></tr><tr><th id="L594"><a href="#L594">594</a></th><td> Sharing an existing calendar can be accomplished in two ways. One</td></tr><tr><th id="L595"><a href="#L595">595</a></th><td> option is to use a PROPPATCH request to set the DAV:resourcetype</td></tr><tr><th id="L596"><a href="#L596">596</a></th><td> property to include CS:shared-owner as a child element. Another</td></tr><tr><th id="L597"><a href="#L597">597</a></th><td> option is to add sharee's directly to the calendar collection (as</td></tr><tr><th id="L598"><a href="#L598">598</a></th><td> described in Section 5.3.3) - that action MUST upgrade a non-shared</td></tr><tr><th id="L599"><a href="#L599">599</a></th><td> calendar to a shared calendar when it completes successfully,</td></tr><tr><th id="L600"><a href="#L600">600</a></th><td> assuming that such an upgrade is allowed as per Section 5.5.2.</td></tr><tr><th id="L601"><a href="#L601">601</a></th><td></td></tr><tr><th id="L602"><a href="#L602">602</a></th><td>5.3.2.1. Example: Successful PROPPATCH Request</td></tr><tr><th id="L603"><a href="#L603">603</a></th><td></td></tr><tr><th id="L604"><a href="#L604">604</a></th><td> This example shows how the PROPPATCH request is used to upgrade to a</td></tr><tr><th id="L605"><a href="#L605">605</a></th><td> shared calendar collection.</td></tr><tr><th id="L606"><a href="#L606">606</a></th><td></td></tr><tr><th id="L607"><a href="#L607">607</a></th><td></td></tr><tr><th id="L608"><a href="#L608">608</a></th><td></td></tr><tr><th id="L609"><a href="#L609">609</a></th><td></td></tr><tr><th id="L610"><a href="#L610">610</a></th><td></td></tr><tr><th id="L611"><a href="#L611">611</a></th><td></td></tr><tr><th id="L612"><a href="#L612">612</a></th><td></td></tr><tr><th id="L613"><a href="#L613">613</a></th><td></td></tr><tr><th id="L614"><a href="#L614">614</a></th><td></td></tr><tr><th id="L615"><a href="#L615">615</a></th><td>Daboo & York [Page 11]</td></tr><tr><th id="L616"><a href="#L616">616</a></th><td></td></tr><tr><th id="L617"><a href="#L617">617</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L618"><a href="#L618">618</a></th><td></td></tr><tr><th id="L619"><a href="#L619">619</a></th><td></td></tr><tr><th id="L620"><a href="#L620">620</a></th><td> >> Request <<</td></tr><tr><th id="L621"><a href="#L621">621</a></th><td></td></tr><tr><th id="L622"><a href="#L622">622</a></th><td> PROPPATCH /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L623"><a href="#L623">623</a></th><td> Host: calendar.example.com</td></tr><tr><th id="L624"><a href="#L624">624</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L625"><a href="#L625">625</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L626"><a href="#L626">626</a></th><td></td></tr><tr><th id="L627"><a href="#L627">627</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L628"><a href="#L628">628</a></th><td> <D:propertyupdate xmlns:D="DAV:"</td></tr><tr><th id="L629"><a href="#L629">629</a></th><td> xmlns:C="urn:ietf:params:xml:ns:caldav"</td></tr><tr><th id="L630"><a href="#L630">630</a></th><td> xmlns:CS="http://calendarserver.org/ns/"></td></tr><tr><th id="L631"><a href="#L631">631</a></th><td> <D:set></td></tr><tr><th id="L632"><a href="#L632">632</a></th><td> <D:prop></td></tr><tr><th id="L633"><a href="#L633">633</a></th><td> <D:resourcetype></td></tr><tr><th id="L634"><a href="#L634">634</a></th><td> <D:collection/></td></tr><tr><th id="L635"><a href="#L635">635</a></th><td> <C:calendar/></td></tr><tr><th id="L636"><a href="#L636">636</a></th><td> <CS:shared-owner/></td></tr><tr><th id="L637"><a href="#L637">637</a></th><td> </D:resourcetype></td></tr><tr><th id="L638"><a href="#L638">638</a></th><td> </D:prop></td></tr><tr><th id="L639"><a href="#L639">639</a></th><td> </D:set></td></tr><tr><th id="L640"><a href="#L640">640</a></th><td> </D:propertyupdate></td></tr><tr><th id="L641"><a href="#L641">641</a></th><td></td></tr><tr><th id="L642"><a href="#L642">642</a></th><td> >> Response <<</td></tr><tr><th id="L643"><a href="#L643">643</a></th><td></td></tr><tr><th id="L644"><a href="#L644">644</a></th><td> HTTP/1.1 207 Multi-Status</td></tr><tr><th id="L645"><a href="#L645">645</a></th><td> Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L646"><a href="#L646">646</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L647"><a href="#L647">647</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L648"><a href="#L648">648</a></th><td></td></tr><tr><th id="L649"><a href="#L649">649</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L650"><a href="#L650">650</a></th><td> <D:multistatus xmlns:D="DAV:"></td></tr><tr><th id="L651"><a href="#L651">651</a></th><td> <D:response></td></tr><tr><th id="L652"><a href="#L652">652</a></th><td> <D:href>/calendars/users/cyrus/shared/</D:href></td></tr><tr><th id="L653"><a href="#L653">653</a></th><td> <D:propstat></td></tr><tr><th id="L654"><a href="#L654">654</a></th><td> <D:prop></td></tr><tr><th id="L655"><a href="#L655">655</a></th><td> <D:resourcetype/></td></tr><tr><th id="L656"><a href="#L656">656</a></th><td> </D:prop></td></tr><tr><th id="L657"><a href="#L657">657</a></th><td> <D:status>HTTP/1.1 200 OK</D:status></td></tr><tr><th id="L658"><a href="#L658">658</a></th><td> </D:propstat></td></tr><tr><th id="L659"><a href="#L659">659</a></th><td> </D:response></td></tr><tr><th id="L660"><a href="#L660">660</a></th><td> </D:multistatus></td></tr><tr><th id="L661"><a href="#L661">661</a></th><td></td></tr><tr><th id="L662"><a href="#L662">662</a></th><td>5.3.3. Manipulating Sharees of a Shared Calendar</td></tr><tr><th id="L663"><a href="#L663">663</a></th><td></td></tr><tr><th id="L664"><a href="#L664">664</a></th><td> The sharer of a shared calendar is able to manipulate the sharee list</td></tr><tr><th id="L665"><a href="#L665">665</a></th><td> by issuing a POST request targeted at the shared calendar collection</td></tr><tr><th id="L666"><a href="#L666">666</a></th><td> resource. The POST request MUST contain an XML document as its body</td></tr><tr><th id="L667"><a href="#L667">667</a></th><td> with the root element being CS:share (Section 6.24).</td></tr><tr><th id="L668"><a href="#L668">668</a></th><td></td></tr><tr><th id="L669"><a href="#L669">669</a></th><td></td></tr><tr><th id="L670"><a href="#L670">670</a></th><td></td></tr><tr><th id="L671"><a href="#L671">671</a></th><td>Daboo & York [Page 12]</td></tr><tr><th id="L672"><a href="#L672">672</a></th><td></td></tr><tr><th id="L673"><a href="#L673">673</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L674"><a href="#L674">674</a></th><td></td></tr><tr><th id="L675"><a href="#L675">675</a></th><td></td></tr><tr><th id="L676"><a href="#L676">676</a></th><td> The CS:share (Section 6.24) element in the POST requests MUST contain</td></tr><tr><th id="L677"><a href="#L677">677</a></th><td> one or more CS:set (Section 6.25) or CS:remove (Section 6.26)</td></tr><tr><th id="L678"><a href="#L678">678</a></th><td> elements. For each CS:set (Section 6.25) element, the server MUST</td></tr><tr><th id="L679"><a href="#L679">679</a></th><td> add the specified sharee access to the shared calendar. For each CS:</td></tr><tr><th id="L680"><a href="#L680">680</a></th><td> remove (Section 6.26) element the server MUST remove the specified</td></tr><tr><th id="L681"><a href="#L681">681</a></th><td> sharee access from the shared calendar. In each case the server MUST</td></tr><tr><th id="L682"><a href="#L682">682</a></th><td> send a notification message to any sharees whose status is changed</td></tr><tr><th id="L683"><a href="#L683">683</a></th><td> (added, modified or removed), indicating to them a change in status</td></tr><tr><th id="L684"><a href="#L684">684</a></th><td> for the shared calendar. The server SHOULD NOT send notification</td></tr><tr><th id="L685"><a href="#L685">685</a></th><td> messages to sharees whose status is unchanged.</td></tr><tr><th id="L686"><a href="#L686">686</a></th><td></td></tr><tr><th id="L687"><a href="#L687">687</a></th><td> Sharee's are identified via a DAV:href element whose value is either</td></tr><tr><th id="L688"><a href="#L688">688</a></th><td> a principal-URL for a sharee hosted on the same server, a calendar</td></tr><tr><th id="L689"><a href="#L689">689</a></th><td> user address or email address. In the case of the later two, the</td></tr><tr><th id="L690"><a href="#L690">690</a></th><td> sharee might not be a user on the same server - though in that case</td></tr><tr><th id="L691"><a href="#L691">691</a></th><td> how invitations are sent or access enabled is out of scope for this</td></tr><tr><th id="L692"><a href="#L692">692</a></th><td> specification. A server MAY change the sharee's "address" to any</td></tr><tr><th id="L693"><a href="#L693">693</a></th><td> suitable alternative that it might prefer when returning the list of</td></tr><tr><th id="L694"><a href="#L694">694</a></th><td> sharees via the CS:invite property (Section 5.2.2).</td></tr><tr><th id="L695"><a href="#L695">695</a></th><td></td></tr><tr><th id="L696"><a href="#L696">696</a></th><td> The client MAY include a CS:common-name (Section 6.19) element in the</td></tr><tr><th id="L697"><a href="#L697">697</a></th><td> CS:set (Section 6.25) element. When provided, the value represents</td></tr><tr><th id="L698"><a href="#L698">698</a></th><td> the common name for the sharee, and is returned in the list of</td></tr><tr><th id="L699"><a href="#L699">699</a></th><td> sharees via the CS:invite property (Section 5.2.2). The server MAY</td></tr><tr><th id="L700"><a href="#L700">700</a></th><td> change this to a suitable alternative when it is able to match the</td></tr><tr><th id="L701"><a href="#L701">701</a></th><td> sharee to a known user. If absent from the client request, the</td></tr><tr><th id="L702"><a href="#L702">702</a></th><td> server SHOULD add a CS:common-name when it is able to match the</td></tr><tr><th id="L703"><a href="#L703">703</a></th><td> sharee with a known user, and a common name for that user can be</td></tr><tr><th id="L704"><a href="#L704">704</a></th><td> determined.</td></tr><tr><th id="L705"><a href="#L705">705</a></th><td></td></tr><tr><th id="L706"><a href="#L706">706</a></th><td> When the sharee list on a shared calendar is changed, the server MUST</td></tr><tr><th id="L707"><a href="#L707">707</a></th><td> send notifications to each sharee to update them on their current</td></tr><tr><th id="L708"><a href="#L708">708</a></th><td> sharing status. This is accomplished by sending a CS:invite-</td></tr><tr><th id="L709"><a href="#L709">709</a></th><td> notification (Section 6.15) notification to each sharee.</td></tr><tr><th id="L710"><a href="#L710">710</a></th><td></td></tr><tr><th id="L711"><a href="#L711">711</a></th><td>5.3.3.1. Example: Successful Sharee Add Request</td></tr><tr><th id="L712"><a href="#L712">712</a></th><td></td></tr><tr><th id="L713"><a href="#L713">713</a></th><td> This example shows how to add a single sharee (with calendar user</td></tr><tr><th id="L714"><a href="#L714">714</a></th><td> address "mailto:eric@example.com") to a shared calendar with CS:read-</td></tr><tr><th id="L715"><a href="#L715">715</a></th><td> write access.</td></tr><tr><th id="L716"><a href="#L716">716</a></th><td></td></tr><tr><th id="L717"><a href="#L717">717</a></th><td></td></tr><tr><th id="L718"><a href="#L718">718</a></th><td></td></tr><tr><th id="L719"><a href="#L719">719</a></th><td></td></tr><tr><th id="L720"><a href="#L720">720</a></th><td></td></tr><tr><th id="L721"><a href="#L721">721</a></th><td></td></tr><tr><th id="L722"><a href="#L722">722</a></th><td></td></tr><tr><th id="L723"><a href="#L723">723</a></th><td></td></tr><tr><th id="L724"><a href="#L724">724</a></th><td></td></tr><tr><th id="L725"><a href="#L725">725</a></th><td></td></tr><tr><th id="L726"><a href="#L726">726</a></th><td></td></tr><tr><th id="L727"><a href="#L727">727</a></th><td>Daboo & York [Page 13]</td></tr><tr><th id="L728"><a href="#L728">728</a></th><td></td></tr><tr><th id="L729"><a href="#L729">729</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L730"><a href="#L730">730</a></th><td></td></tr><tr><th id="L731"><a href="#L731">731</a></th><td></td></tr><tr><th id="L732"><a href="#L732">732</a></th><td> >> Request <<</td></tr><tr><th id="L733"><a href="#L733">733</a></th><td></td></tr><tr><th id="L734"><a href="#L734">734</a></th><td> POST /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L735"><a href="#L735">735</a></th><td> Host: calendar.example.com</td></tr><tr><th id="L736"><a href="#L736">736</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L737"><a href="#L737">737</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L738"><a href="#L738">738</a></th><td></td></tr><tr><th id="L739"><a href="#L739">739</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L740"><a href="#L740">740</a></th><td> <CS:share xmlns:D="DAV:"</td></tr><tr><th id="L741"><a href="#L741">741</a></th><td> xmlns:CS="http://calendarserver.org/ns/"></td></tr><tr><th id="L742"><a href="#L742">742</a></th><td> <CS:set></td></tr><tr><th id="L743"><a href="#L743">743</a></th><td> <D:href>mailto:eric@example.com</D:href></td></tr><tr><th id="L744"><a href="#L744">744</a></th><td> <CS:common-name>Eric York</CS:common-name></td></tr><tr><th id="L745"><a href="#L745">745</a></th><td> <CS:summary>Shared workspace</CS:summary></td></tr><tr><th id="L746"><a href="#L746">746</a></th><td> <CS:read-write /></td></tr><tr><th id="L747"><a href="#L747">747</a></th><td> </CS:set></td></tr><tr><th id="L748"><a href="#L748">748</a></th><td> </CS:share></td></tr><tr><th id="L749"><a href="#L749">749</a></th><td></td></tr><tr><th id="L750"><a href="#L750">750</a></th><td> >> Response <<</td></tr><tr><th id="L751"><a href="#L751">751</a></th><td></td></tr><tr><th id="L752"><a href="#L752">752</a></th><td> HTTP/1.1 200 OK</td></tr><tr><th id="L753"><a href="#L753">753</a></th><td> Cache-Control: no-cache</td></tr><tr><th id="L754"><a href="#L754">754</a></th><td> Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L755"><a href="#L755">755</a></th><td></td></tr><tr><th id="L756"><a href="#L756">756</a></th><td>5.3.3.2. Example: Successful Multiple Sharee Change Request</td></tr><tr><th id="L757"><a href="#L757">757</a></th><td></td></tr><tr><th id="L758"><a href="#L758">758</a></th><td> This example shows how multiple sharee's can be manipulated in a</td></tr><tr><th id="L759"><a href="#L759">759</a></th><td> single request. The sharee with calendar user address</td></tr><tr><th id="L760"><a href="#L760">760</a></th><td> "mailto:eric@example.com" has their access downgraded to CS:read,</td></tr><tr><th id="L761"><a href="#L761">761</a></th><td> whilst another sharee is removed from the access list entirely.</td></tr><tr><th id="L762"><a href="#L762">762</a></th><td></td></tr><tr><th id="L763"><a href="#L763">763</a></th><td></td></tr><tr><th id="L764"><a href="#L764">764</a></th><td></td></tr><tr><th id="L765"><a href="#L765">765</a></th><td></td></tr><tr><th id="L766"><a href="#L766">766</a></th><td></td></tr><tr><th id="L767"><a href="#L767">767</a></th><td></td></tr><tr><th id="L768"><a href="#L768">768</a></th><td></td></tr><tr><th id="L769"><a href="#L769">769</a></th><td></td></tr><tr><th id="L770"><a href="#L770">770</a></th><td></td></tr><tr><th id="L771"><a href="#L771">771</a></th><td></td></tr><tr><th id="L772"><a href="#L772">772</a></th><td></td></tr><tr><th id="L773"><a href="#L773">773</a></th><td></td></tr><tr><th id="L774"><a href="#L774">774</a></th><td></td></tr><tr><th id="L775"><a href="#L775">775</a></th><td></td></tr><tr><th id="L776"><a href="#L776">776</a></th><td></td></tr><tr><th id="L777"><a href="#L777">777</a></th><td></td></tr><tr><th id="L778"><a href="#L778">778</a></th><td></td></tr><tr><th id="L779"><a href="#L779">779</a></th><td></td></tr><tr><th id="L780"><a href="#L780">780</a></th><td></td></tr><tr><th id="L781"><a href="#L781">781</a></th><td></td></tr><tr><th id="L782"><a href="#L782">782</a></th><td></td></tr><tr><th id="L783"><a href="#L783">783</a></th><td>Daboo & York [Page 14]</td></tr><tr><th id="L784"><a href="#L784">784</a></th><td></td></tr><tr><th id="L785"><a href="#L785">785</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L786"><a href="#L786">786</a></th><td></td></tr><tr><th id="L787"><a href="#L787">787</a></th><td></td></tr><tr><th id="L788"><a href="#L788">788</a></th><td> >> Request <<</td></tr><tr><th id="L789"><a href="#L789">789</a></th><td></td></tr><tr><th id="L790"><a href="#L790">790</a></th><td> POST /calendars/users/cyrus/shared/ HTTP/1.1</td></tr><tr><th id="L791"><a href="#L791">791</a></th><td> Host: calendar.example.com</td></tr><tr><th id="L792"><a href="#L792">792</a></th><td> Content-Type: application/xml; charset="utf-8"</td></tr><tr><th id="L793"><a href="#L793">793</a></th><td> Content-Length: xxxx</td></tr><tr><th id="L794"><a href="#L794">794</a></th><td></td></tr><tr><th id="L795"><a href="#L795">795</a></th><td> <?xml version="1.0" encoding="utf-8" ?></td></tr><tr><th id="L796"><a href="#L796">796</a></th><td> <CS:share xmlns:D="DAV:"</td></tr><tr><th id="L797"><a href="#L797">797</a></th><td> xmlns:CS="http://calendarserver.org/ns/"></td></tr><tr><th id="L798"><a href="#L798">798</a></th><td> <CS:set></td></tr><tr><th id="L799"><a href="#L799">799</a></th><td> <D:href>mailto:eric@example.com</D:href></td></tr><tr><th id="L800"><a href="#L800">800</a></th><td> <CS:summary>Shared workspace</CS:summary></td></tr><tr><th id="L801"><a href="#L801">801</a></th><td> <CS:read-write /></td></tr><tr><th id="L802"><a href="#L802">802</a></th><td> </CS:set></td></tr><tr><th id="L803"><a href="#L803">803</a></th><td> <CS:remove></td></tr><tr><th id="L804"><a href="#L804">804</a></th><td> <D:href>mailto:wilfredo@example.com</D:href></td></tr><tr><th id="L805"><a href="#L805">805</a></th><td> </CS:remove></td></tr><tr><th id="L806"><a href="#L806">806</a></th><td> </CS:share></td></tr><tr><th id="L807"><a href="#L807">807</a></th><td></td></tr><tr><th id="L808"><a href="#L808">808</a></th><td> >> Response <<</td></tr><tr><th id="L809"><a href="#L809">809</a></th><td></td></tr><tr><th id="L810"><a href="#L810">810</a></th><td> HTTP/1.1 200 OK</td></tr><tr><th id="L811"><a href="#L811">811</a></th><td> Cache-Control: no-cache</td></tr><tr><th id="L812"><a href="#L812">812</a></th><td> Date: Sat, 11 Nov 2006 09:32:12 GMT</td></tr><tr><th id="L813"><a href="#L813">813</a></th><td></td></tr><tr><th id="L814"><a href="#L814">814</a></th><td>5.4. Sharee Actions on Shared Calendars</td></tr><tr><th id="L815"><a href="#L815">815</a></th><td></td></tr><tr><th id="L816"><a href="#L816">816</a></th><td>5.4.1. Replying to a Sharing Invite</td></tr><tr><th id="L817"><a href="#L817">817</a></th><td></td></tr><tr><th id="L818"><a href="#L818">818</a></th><td> When a sharee is invited to a shared calendar they can accept or</td></tr><tr><th id="L819"><a href="#L819">819</a></th><td> decline the invite by issuing a POST request to the sharee's calendar</td></tr><tr><th id="L820"><a href="#L820">820</a></th><td> home collection resource. The POST request MUST contain an XML</td></tr><tr><th id="L821"><a href="#L821">821</a></th><td> document as its body with the root element being CS:invite-reply</td></tr><tr><th id="L822"><a href="#L822">822</a></th><td> (Section 6.20).</td></tr><tr><th id="L823"><a href="#L823">823</a></th><td></td></tr><tr><th id="L824"><a href="#L824">824</a></th><td> The CS:invite-reply (Section 6.20) element in the POST request</td></tr><tr><th id="L825"><a href="#L825">825</a></th><td> specifies the sharee who is replying in the DAV:href element, the</td></tr><tr><th id="L826"><a href="#L826">826</a></th><td> accept or decline action via the CS:invite-accepted or CS:invite-</td></tr><tr><th id="L827"><a href="#L827">827</a></th><td> declined elements, the URL of the shared calendar in the CS:hosturl</td></tr><tr><th id="L828"><a href="#L828">828</a></th><td> element, the unique identifier of the invite to which it is a reply</td></tr><tr><th id="L829"><a href="#L829">829</a></th><td> in the CS:in-reply-to element, and an optional CS:summary element.</td></tr><tr><th id="L830"><a href="#L830">830</a></th><td></td></tr><tr><th id="L831"><a href="#L831">831</a></th><td> The response to a POST request that accepts a shared calendar invite</td></tr><tr><th id="L832"><a href="#L832">832</a></th><td> MUST be an XML document containing CS:shared-as (Section 6.27) as its</td></tr><tr><th id="L833"><a href="#L833">833</a></th><td> root element. That root element contains a single DAV:href element</td></tr><tr><th id="L834"><a href="#L834">834</a></th><td> whose content is the URI of the shared calendar in the sharee's</td></tr><tr><th id="L835"><a href="#L835">835</a></th><td> calendar home created by the invite acceptance.</td></tr><tr><th id="L836"><a href="#L836">836</a></th><td></td></tr><tr><th id="L837"><a href="#L837">837</a></th><td></td></tr><tr><th id="L838"><a href="#L838">838</a></th><td></td></tr><tr><th id="L839"><a href="#L839">839</a></th><td>Daboo & York [Page 15]</td></tr><tr><th id="L840"><a href="#L840">840</a></th><td></td></tr><tr><th id="L841"><a href="#L841">841</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L842"><a href="#L842">842</a></th><td></td></tr><tr><th id="L843"><a href="#L843">843</a></th><td></td></tr><tr><th id="L844"><a href="#L844">844</a></th><td> When the sharee replies to an invite, the server SHOULD send a</td></tr><tr><th id="L845"><a href="#L845">845</a></th><td> notification to the sharer to update them on the change in the sharee</td></tr><tr><th id="L846"><a href="#L846">846</a></th><td> state. This is accomplished by sending a CS:invite-reply</td></tr><tr><th id="L847"><a href="#L847">847</a></th><td> (Section 6.20) notification to the sharer.</td></tr><tr><th id="L848"><a href="#L848">848</a></th><td></td></tr><tr><th id="L849"><a href="#L849">849</a></th><td>5.4.2. Removing a Shared Calendar</td></tr><tr><th id="L850"><a href="#L850">850</a></th><td></td></tr><tr><th id="L851"><a href="#L851">851</a></th><td> To remove a shared calendar from a sharee's calendar home collection</td></tr><tr><th id="L852"><a href="#L852">852</a></th><td> a DELETE request is targeted at the shared calendar URI. When such a</td></tr><tr><th id="L853"><a href="#L853">853</a></th><td> request is received the server MUST remove the shared calendar from</td></tr><tr><th id="L854"><a href="#L854">854</a></th><td> the sharee's calendar home and automatically update the sharee's</td></tr><tr><th id="L855"><a href="#L855">855</a></th><td> status in the sharer's calendar's CS:invite property.</td></tr><tr><th id="L856"><a href="#L856">856</a></th><td></td></tr><tr><th id="L857"><a href="#L857">857</a></th><td>5.5. General Considerations</td></tr><tr><th id="L858"><a href="#L858">858</a></th><td></td></tr><tr><th id="L859"><a href="#L859">859</a></th><td>5.5.1. Access Levels</td></tr><tr><th id="L860"><a href="#L860">860</a></th><td></td></tr><tr><th id="L861"><a href="#L861">861</a></th><td> Two levels of access ca be granted by a sharer to any sharee. These</td></tr><tr><th id="L862"><a href="#L862">862</a></th><td> are governed by the CS:access element used in the CS:invite/CS:user</td></tr><tr><th id="L863"><a href="#L863">863</a></th><td> element that specifies a shared user invite. CS:access contains a</td></tr><tr><th id="L864"><a href="#L864">864</a></th><td> single empty element that defines the type of access granted:</td></tr><tr><th id="L865"><a href="#L865">865</a></th><td></td></tr><tr><th id="L866"><a href="#L866">866</a></th><td> CS:read When present this indicates that sharees can read calendar</td></tr><tr><th id="L867"><a href="#L867">867</a></th><td> data but cannot change it.</td></tr><tr><th id="L868"><a href="#L868">868</a></th><td></td></tr><tr><th id="L869"><a href="#L869">869</a></th><td> CS:read-write When present this indicates that sharees can read and</td></tr><tr><th id="L870"><a href="#L870">870</a></th><td> write calendar data.</td></tr><tr><th id="L871"><a href="#L871">871</a></th><td></td></tr><tr><th id="L872"><a href="#L872">872</a></th><td>5.5.2. Allowing or Disallowing Sharing</td></tr><tr><th id="L873"><a href="#L873">873</a></th><td></td></tr><tr><th id="L874"><a href="#L874">874</a></th><td> Servers MAY support calendar sharing on a per-calendar basis - e.g.,</td></tr><tr><th id="L875"><a href="#L875">875</a></th><td> they could treat some calendars as always private (cannot be shared)</td></tr><tr><th id="L876"><a href="#L876">876</a></th><td> or always public (always shared). As a result clients need a way to</td></tr><tr><th id="L877"><a href="#L877">877</a></th><td> determine which calendar could be shared so they can enable or</td></tr><tr><th id="L878"><a href="#L878">878</a></th><td> disable sharing options on a per-calendar basis.</td></tr><tr><th id="L879"><a href="#L879">879</a></th><td></td></tr><tr><th id="L880"><a href="#L880">880</a></th><td> This specification adds a CS:allowed-sharing-modes (Section 5.2.3)</td></tr><tr><th id="L881"><a href="#L881">881</a></th><td> WebDAV property which servers can return on calendar collection</td></tr><tr><th id="L882"><a href="#L882">882</a></th><td> resources. This property contains XML elements that describe which</td></tr><tr><th id="L883"><a href="#L883">883</a></th><td> sharing or publishing capabilities can be supported by the</td></tr><tr><th id="L884"><a href="#L884">884</a></th><td> corresponding calendar collection:</td></tr><tr><th id="L885"><a href="#L885">885</a></th><td></td></tr><tr><th id="L886"><a href="#L886">886</a></th><td> CS:can-be-shared (Section 6.3): when present indicates that the</td></tr><tr><th id="L887"><a href="#L887">887</a></th><td> calendar collection can be shared. When not present, the calendar</td></tr><tr><th id="L888"><a href="#L888">888</a></th><td> collection cannot be shared.</td></tr><tr><th id="L889"><a href="#L889">889</a></th><td></td></tr><tr><th id="L890"><a href="#L890">890</a></th><td> CS:can-be-published (Section 6.4): when present indicates that the</td></tr><tr><th id="L891"><a href="#L891">891</a></th><td> calendar collection can be published. When not present, the</td></tr><tr><th id="L892"><a href="#L892">892</a></th><td></td></tr><tr><th id="L893"><a href="#L893">893</a></th><td></td></tr><tr><th id="L894"><a href="#L894">894</a></th><td></td></tr><tr><th id="L895"><a href="#L895">895</a></th><td>Daboo & York [Page 16]</td></tr><tr><th id="L896"><a href="#L896">896</a></th><td></td></tr><tr><th id="L897"><a href="#L897">897</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L898"><a href="#L898">898</a></th><td></td></tr><tr><th id="L899"><a href="#L899">899</a></th><td></td></tr><tr><th id="L900"><a href="#L900">900</a></th><td> calendar collection cannot be published.</td></tr><tr><th id="L901"><a href="#L901">901</a></th><td></td></tr><tr><th id="L902"><a href="#L902">902</a></th><td> When not present on a calendar collection, sharing or publishing of</td></tr><tr><th id="L903"><a href="#L903">903</a></th><td> that calendar is not allowed. Clients SHOULD NOT attempt to use</td></tr><tr><th id="L904"><a href="#L904">904</a></th><td> requests to enable sharing or publishing targeted at those calendar</td></tr><tr><th id="L905"><a href="#L905">905</a></th><td> collections.</td></tr><tr><th id="L906"><a href="#L906">906</a></th><td></td></tr><tr><th id="L907"><a href="#L907">907</a></th><td>5.5.3. Per-user WebDAV Properties</td></tr><tr><th id="L908"><a href="#L908">908</a></th><td></td></tr><tr><th id="L909"><a href="#L909">909</a></th><td> Servers MUST support "per-user" WebDAV properties on shared calendar</td></tr><tr><th id="L910"><a href="#L910">910</a></th><td> collections and MAY support them on calendar object resources within</td></tr><tr><th id="L911"><a href="#L911">911</a></th><td> shared calendar collections. A "per-user" WebDAV property is one</td></tr><tr><th id="L912"><a href="#L912">912</a></th><td> whose value can be set and retrieved independently by each user with</td></tr><tr><th id="L913"><a href="#L913">913</a></th><td> appropriate access rights. e.g., user "A" changes the DAV:displayname</td></tr><tr><th id="L914"><a href="#L914">914</a></th><td> property on a shared calendar in their calendar home to "My</td></tr><tr><th id="L915"><a href="#L915">915</a></th><td> calendar", and user "B" changes the same property to "Shared" on the</td></tr><tr><th id="L916"><a href="#L916">916</a></th><td> same shared calendar in their calendar home. When each user</td></tr><tr><th id="L917"><a href="#L917">917</a></th><td> retrieves the property value they will see their own last stored</td></tr><tr><th id="L918"><a href="#L918">918</a></th><td> value and not the value of the other user.</td></tr><tr><th id="L919"><a href="#L919">919</a></th><td></td></tr><tr><th id="L920"><a href="#L920">920</a></th><td> For shared calendars, the server MUST allow all users to write "per-</td></tr><tr><th id="L921"><a href="#L921">921</a></th><td> user" WebDAV properties on the shared calendar collection and MAY</td></tr><tr><th id="L922"><a href="#L922">922</a></th><td> allow property writes on calendar object resources within the shared</td></tr><tr><th id="L923"><a href="#L923">923</a></th><td> calendar collection. This is required even in the case where the</td></tr><tr><th id="L924"><a href="#L924">924</a></th><td> sharee has been granted read access only (i.e., the ability to change</td></tr><tr><th id="L925"><a href="#L925">925</a></th><td> calendar data is disallowed). This requirement ensures that sharees</td></tr><tr><th id="L926"><a href="#L926">926</a></th><td> can always change "personal" properties such as calendar colors and</td></tr><tr><th id="L927"><a href="#L927">927</a></th><td> display names.</td></tr><tr><th id="L928"><a href="#L928">928</a></th><td></td></tr><tr><th id="L929"><a href="#L929">929</a></th><td> Servers MUST treat the following properties as "per-user":</td></tr><tr><th id="L930"><a href="#L930">930</a></th><td></td></tr><tr><th id="L931"><a href="#L931">931</a></th><td> DAV:displayname</td></tr><tr><th id="L932"><a href="#L932">932</a></th><td></td></tr><tr><th id="L933"><a href="#L933">933</a></th><td> CALDAV:calendar-description</td></tr><tr><th id="L934"><a href="#L934">934</a></th><td></td></tr><tr><th id="L935"><a href="#L935">935</a></th><td> CALDAV:schedule-calendar-transp</td></tr><tr><th id="L936"><a href="#L936">936</a></th><td></td></tr><tr><th id="L937"><a href="#L937">937</a></th><td> ICAL:calendar-color</td></tr><tr><th id="L938"><a href="#L938">938</a></th><td></td></tr><tr><th id="L939"><a href="#L939">939</a></th><td> Servers MAY treat any dead property as per-user.</td></tr><tr><th id="L940"><a href="#L940">940</a></th><td></td></tr><tr><th id="L941"><a href="#L941">941</a></th><td> Servers MUST NOT treat live properties as per-user.</td></tr><tr><th id="L942"><a href="#L942">942</a></th><td></td></tr><tr><th id="L943"><a href="#L943">943</a></th><td>5.5.4. Per-user Calendar Data</td></tr><tr><th id="L944"><a href="#L944">944</a></th><td></td></tr><tr><th id="L945"><a href="#L945">945</a></th><td> Servers MUST support "per-user" calendar data in calendar object</td></tr><tr><th id="L946"><a href="#L946">946</a></th><td> resources stored in shared calendars. This allows each sharee and</td></tr><tr><th id="L947"><a href="#L947">947</a></th><td> the sharer to store their own alarms and free busy transparency</td></tr><tr><th id="L948"><a href="#L948">948</a></th><td></td></tr><tr><th id="L949"><a href="#L949">949</a></th><td></td></tr><tr><th id="L950"><a href="#L950">950</a></th><td></td></tr><tr><th id="L951"><a href="#L951">951</a></th><td>Daboo & York [Page 17]</td></tr><tr><th id="L952"><a href="#L952">952</a></th><td></td></tr><tr><th id="L953"><a href="#L953">953</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L954"><a href="#L954">954</a></th><td></td></tr><tr><th id="L955"><a href="#L955">955</a></th><td></td></tr><tr><th id="L956"><a href="#L956">956</a></th><td> status without "interfering" with other users who also have access to</td></tr><tr><th id="L957"><a href="#L957">957</a></th><td> the same calendar object resources.</td></tr><tr><th id="L958"><a href="#L958">958</a></th><td></td></tr><tr><th id="L959"><a href="#L959">959</a></th><td> For calendaring object resources in shared calendar collections, the</td></tr><tr><th id="L960"><a href="#L960">960</a></th><td> server MUST treat the following iCalendar data objects as per-user:</td></tr><tr><th id="L961"><a href="#L961">961</a></th><td></td></tr><tr><th id="L962"><a href="#L962">962</a></th><td> TRANSP property</td></tr><tr><th id="L963"><a href="#L963">963</a></th><td></td></tr><tr><th id="L964"><a href="#L964">964</a></th><td> VALARM component</td></tr><tr><th id="L965"><a href="#L965">965</a></th><td></td></tr><tr><th id="L966"><a href="#L966">966</a></th><td> Servers MAY treat any non-standard X- iCalendar properties as per-</td></tr><tr><th id="L967"><a href="#L967">967</a></th><td> user.</td></tr><tr><th id="L968"><a href="#L968">968</a></th><td></td></tr><tr><th id="L969"><a href="#L969">969</a></th><td> When handling per-user data in recurring components, servers SHOULD</td></tr><tr><th id="L970"><a href="#L970">970</a></th><td> eliminate overridden instances when returning iCalendar data to</td></tr><tr><th id="L971"><a href="#L971">971</a></th><td> clients in the case where there are no differences between the</td></tr><tr><th id="L972"><a href="#L972">972</a></th><td> overridden component and the instance that could be derived from the</td></tr><tr><th id="L973"><a href="#L973">973</a></th><td> "master" recurrence component. For example, consider a daily</td></tr><tr><th id="L974"><a href="#L974">974</a></th><td> recurring event, Monday through Friday, initially defined without any</td></tr><tr><th id="L975"><a href="#L975">975</a></th><td> overridden instances, that is in a shared calendar. If user "A"</td></tr><tr><th id="L976"><a href="#L976">976</a></th><td> overrides the Tuesday instance and adds their own "VALARM" component</td></tr><tr><th id="L977"><a href="#L977">977</a></th><td> only, then when user "A" later retrieves the data again they would</td></tr><tr><th id="L978"><a href="#L978">978</a></th><td> see that overridden instance, but when user "B" does so, they would</td></tr><tr><th id="L979"><a href="#L979">979</a></th><td> not. This ensures that each user sees the most "compact"</td></tr><tr><th id="L980"><a href="#L980">980</a></th><td> representation of the calendar data.</td></tr><tr><th id="L981"><a href="#L981">981</a></th><td></td></tr><tr><th id="L982"><a href="#L982">982</a></th><td>5.5.5. Scheduling</td></tr><tr><th id="L983"><a href="#L983">983</a></th><td></td></tr><tr><th id="L984"><a href="#L984">984</a></th><td> CalDAV Scheduling [I-D.desruisseaux-caldav-sched] defines how a</td></tr><tr><th id="L985"><a href="#L985">985</a></th><td> CalDAV server carries out scheduling operations when calendar object</td></tr><tr><th id="L986"><a href="#L986">986</a></th><td> resources are created, modified or deleted and include "ORGANIZER"</td></tr><tr><th id="L987"><a href="#L987">987</a></th><td> and "ATTENDEE" iCalendar properties.</td></tr><tr><th id="L988"><a href="#L988">988</a></th><td></td></tr><tr><th id="L989"><a href="#L989">989</a></th><td> When calendar object resources are created, modified or deleted in</td></tr><tr><th id="L990"><a href="#L990">990</a></th><td> shared calendars by sharees, the following restrictions apply:</td></tr><tr><th id="L991"><a href="#L991">991</a></th><td></td></tr><tr><th id="L992"><a href="#L992">992</a></th><td> 1. The "ORGANIZER" iCalendar property value in the iCalendar data</td></tr><tr><th id="L993"><a href="#L993">993</a></th><td> MUST match a calendar user address of the sharer (owner) of the</td></tr><tr><th id="L994"><a href="#L994">994</a></th><td> shared calendar. The DAV:owner WebDAV property MUST be present</td></tr><tr><th id="L995"><a href="#L995">995</a></th><td> on a shared calendar and MUST provide a reference to a principal-</td></tr><tr><th id="L996"><a href="#L996">996</a></th><td> URL of the sharer (owner) of the shared calendar. Clients can</td></tr><tr><th id="L997"><a href="#L997">997</a></th><td> use this value to determine what the allowed "ORGANIZER"</td></tr><tr><th id="L998"><a href="#L998">998</a></th><td> iCalendar property values are. The server MUST reject any</td></tr><tr><th id="L999"><a href="#L999">999</a></th><td> attempt by a sharee to create an iCalendar component with an</td></tr><tr><th id="L1000"><a href="#L1000">1000</a></th><td> "ORGANIZER" property value other than the sharer (owner) of the</td></tr><tr><th id="L1001"><a href="#L1001">1001</a></th><td> shared calendar.</td></tr><tr><th id="L1002"><a href="#L1002">1002</a></th><td></td></tr><tr><th id="L1003"><a href="#L1003">1003</a></th><td></td></tr><tr><th id="L1004"><a href="#L1004">1004</a></th><td></td></tr><tr><th id="L1005"><a href="#L1005">1005</a></th><td></td></tr><tr><th id="L1006"><a href="#L1006">1006</a></th><td></td></tr><tr><th id="L1007"><a href="#L1007">1007</a></th><td>Daboo & York [Page 18]</td></tr><tr><th id="L1008"><a href="#L1008">1008</a></th><td></td></tr><tr><th id="L1009"><a href="#L1009">1009</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1010"><a href="#L1010">1010</a></th><td></td></tr><tr><th id="L1011"><a href="#L1011">1011</a></th><td></td></tr><tr><th id="L1012"><a href="#L1012">1012</a></th><td> 2. The server MUST reject any attempt by a sharee to MOVE a calendar</td></tr><tr><th id="L1013"><a href="#L1013">1013</a></th><td> object resource in a shared calendar to some other collection.</td></tr><tr><th id="L1014"><a href="#L1014">1014</a></th><td></td></tr><tr><th id="L1015"><a href="#L1015">1015</a></th><td> 3. When a sharee is listed as an Attendee in a calendar object</td></tr><tr><th id="L1016"><a href="#L1016">1016</a></th><td> resource in a shared calendar, and write access is granted, the</td></tr><tr><th id="L1017"><a href="#L1017">1017</a></th><td> sharee is allowed to change not only iCalendar data related to</td></tr><tr><th id="L1018"><a href="#L1018">1018</a></th><td> the Organizer, but also data related to the Attendee. i.e., a</td></tr><tr><th id="L1019"><a href="#L1019">1019</a></th><td> sharee can change their own participation status on the</td></tr><tr><th id="L1020"><a href="#L1020">1020</a></th><td> "ATTENDEE" iCalendar property referring to them. Additionally,</td></tr><tr><th id="L1021"><a href="#L1021">1021</a></th><td> if the sharee is not listed as an Attendee, and write access is</td></tr><tr><th id="L1022"><a href="#L1022">1022</a></th><td> granted, the sharee can add themselves as an Attendee.</td></tr><tr><th id="L1023"><a href="#L1023">1023</a></th><td></td></tr><tr><th id="L1024"><a href="#L1024">1024</a></th><td> 4. The default calendar collection defined in Section 6.3 of</td></tr><tr><th id="L1025"><a href="#L1025">1025</a></th><td> [I-D.desruisseaux-caldav-sched] MUST NOT be a calendar shared to</td></tr><tr><th id="L1026"><a href="#L1026">1026</a></th><td> the corresponding calendar user.</td></tr><tr><th id="L1027"><a href="#L1027">1027</a></th><td></td></tr><tr><th id="L1028"><a href="#L1028">1028</a></th><td> Following are additional considerations for scheduling with shared</td></tr><tr><th id="L1029"><a href="#L1029">1029</a></th><td> calendars:</td></tr><tr><th id="L1030"><a href="#L1030">1030</a></th><td></td></tr><tr><th id="L1031"><a href="#L1031">1031</a></th><td> 1. A scheduled iCalendar component could appear in more than one</td></tr><tr><th id="L1032"><a href="#L1032">1032</a></th><td> calendar collection within a sharee's calendar home if the sharee</td></tr><tr><th id="L1033"><a href="#L1033">1033</a></th><td> is an Attendee and the Organizer or other Attendees have shared a</td></tr><tr><th id="L1034"><a href="#L1034">1034</a></th><td> calendar with the sharee that includes their copies of the</td></tr><tr><th id="L1035"><a href="#L1035">1035</a></th><td> iCalendar component. It is important to note that the scheduled</td></tr><tr><th id="L1036"><a href="#L1036">1036</a></th><td> component in the shared calendar could have different access</td></tr><tr><th id="L1037"><a href="#L1037">1037</a></th><td> rights than the one in the sharee's owned calendar.</td></tr><tr><th id="L1038"><a href="#L1038">1038</a></th><td></td></tr><tr><th id="L1039"><a href="#L1039">1039</a></th><td> 2. A scheduled iCalendar component appearing in a sharee's shared</td></tr><tr><th id="L1040"><a href="#L1040">1040</a></th><td> calendar could include the sharee as an Attendee. For recurring</td></tr><tr><th id="L1041"><a href="#L1041">1041</a></th><td> events, it is possible for the sharee to only be listed as an</td></tr><tr><th id="L1042"><a href="#L1042">1042</a></th><td> Attendee in some instances, as opposed to all. Clients will need</td></tr><tr><th id="L1043"><a href="#L1043">1043</a></th><td> to be aware of this when allowing sharee's to set their own</td></tr><tr><th id="L1044"><a href="#L1044">1044</a></th><td> participation status.</td></tr><tr><th id="L1045"><a href="#L1045">1045</a></th><td></td></tr><tr><th id="L1046"><a href="#L1046">1046</a></th><td> In addition, when a shared calendar is first accepted by a sharee,</td></tr><tr><th id="L1047"><a href="#L1047">1047</a></th><td> the server SHOULD set the CALDAV:schedule-calendar-transp property to</td></tr><tr><th id="L1048"><a href="#L1048">1048</a></th><td> the value CALDAV:transparent to ensure newly accepted shared</td></tr><tr><th id="L1049"><a href="#L1049">1049</a></th><td> calendars do not contribute to the sharee's freebusy time until the</td></tr><tr><th id="L1050"><a href="#L1050">1050</a></th><td> sharee explicitly requests it.</td></tr><tr><th id="L1051"><a href="#L1051">1051</a></th><td></td></tr><tr><th id="L1052"><a href="#L1052">1052</a></th><td></td></tr><tr><th id="L1053"><a href="#L1053">1053</a></th><td>6. XML Element Definitions</td></tr><tr><th id="L1054"><a href="#L1054">1054</a></th><td></td></tr><tr><th id="L1055"><a href="#L1055">1055</a></th><td>6.1. CS:shared-owner</td></tr><tr><th id="L1056"><a href="#L1056">1056</a></th><td></td></tr><tr><th id="L1057"><a href="#L1057">1057</a></th><td></td></tr><tr><th id="L1058"><a href="#L1058">1058</a></th><td></td></tr><tr><th id="L1059"><a href="#L1059">1059</a></th><td></td></tr><tr><th id="L1060"><a href="#L1060">1060</a></th><td></td></tr><tr><th id="L1061"><a href="#L1061">1061</a></th><td></td></tr><tr><th id="L1062"><a href="#L1062">1062</a></th><td></td></tr><tr><th id="L1063"><a href="#L1063">1063</a></th><td>Daboo & York [Page 19]</td></tr><tr><th id="L1064"><a href="#L1064">1064</a></th><td></td></tr><tr><th id="L1065"><a href="#L1065">1065</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1066"><a href="#L1066">1066</a></th><td></td></tr><tr><th id="L1067"><a href="#L1067">1067</a></th><td></td></tr><tr><th id="L1068"><a href="#L1068">1068</a></th><td> Name: shared-owner</td></tr><tr><th id="L1069"><a href="#L1069">1069</a></th><td></td></tr><tr><th id="L1070"><a href="#L1070">1070</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1071"><a href="#L1071">1071</a></th><td></td></tr><tr><th id="L1072"><a href="#L1072">1072</a></th><td> Purpose: Used to indicate that a calendar is being shared by the</td></tr><tr><th id="L1073"><a href="#L1073">1073</a></th><td> owner.</td></tr><tr><th id="L1074"><a href="#L1074">1074</a></th><td></td></tr><tr><th id="L1075"><a href="#L1075">1075</a></th><td> Description: This property appears in the DAV:resourcetype property</td></tr><tr><th id="L1076"><a href="#L1076">1076</a></th><td> on the calendar collection resource shared by a sharer. See</td></tr><tr><th id="L1077"><a href="#L1077">1077</a></th><td> Section 5.2.</td></tr><tr><th id="L1078"><a href="#L1078">1078</a></th><td></td></tr><tr><th id="L1079"><a href="#L1079">1079</a></th><td> Definition:</td></tr><tr><th id="L1080"><a href="#L1080">1080</a></th><td></td></tr><tr><th id="L1081"><a href="#L1081">1081</a></th><td> <!ELEMENT shared-owner EMPTY></td></tr><tr><th id="L1082"><a href="#L1082">1082</a></th><td></td></tr><tr><th id="L1083"><a href="#L1083">1083</a></th><td>6.2. CS:shared</td></tr><tr><th id="L1084"><a href="#L1084">1084</a></th><td></td></tr><tr><th id="L1085"><a href="#L1085">1085</a></th><td> Name: shared</td></tr><tr><th id="L1086"><a href="#L1086">1086</a></th><td></td></tr><tr><th id="L1087"><a href="#L1087">1087</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1088"><a href="#L1088">1088</a></th><td></td></tr><tr><th id="L1089"><a href="#L1089">1089</a></th><td> Purpose: Used to indicate that a calendar is being shared to a</td></tr><tr><th id="L1090"><a href="#L1090">1090</a></th><td> sharee.</td></tr><tr><th id="L1091"><a href="#L1091">1091</a></th><td></td></tr><tr><th id="L1092"><a href="#L1092">1092</a></th><td> Description: This property appears in the DAV:resourcetype property</td></tr><tr><th id="L1093"><a href="#L1093">1093</a></th><td> on a calendar collection resource that is shared to a sharee and</td></tr><tr><th id="L1094"><a href="#L1094">1094</a></th><td> appears in the sharee's calendar home collection. See</td></tr><tr><th id="L1095"><a href="#L1095">1095</a></th><td> Section 5.2.</td></tr><tr><th id="L1096"><a href="#L1096">1096</a></th><td></td></tr><tr><th id="L1097"><a href="#L1097">1097</a></th><td> Definition:</td></tr><tr><th id="L1098"><a href="#L1098">1098</a></th><td></td></tr><tr><th id="L1099"><a href="#L1099">1099</a></th><td> <!ELEMENT shared EMPTY></td></tr><tr><th id="L1100"><a href="#L1100">1100</a></th><td></td></tr><tr><th id="L1101"><a href="#L1101">1101</a></th><td>6.3. CS:can-be-shared</td></tr><tr><th id="L1102"><a href="#L1102">1102</a></th><td></td></tr><tr><th id="L1103"><a href="#L1103">1103</a></th><td> Name: can-be-shared</td></tr><tr><th id="L1104"><a href="#L1104">1104</a></th><td></td></tr><tr><th id="L1105"><a href="#L1105">1105</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1106"><a href="#L1106">1106</a></th><td></td></tr><tr><th id="L1107"><a href="#L1107">1107</a></th><td> Purpose: Used to indicate that a calendar can be shared.</td></tr><tr><th id="L1108"><a href="#L1108">1108</a></th><td></td></tr><tr><th id="L1109"><a href="#L1109">1109</a></th><td> Description: This element indicates that a calendar can be shared</td></tr><tr><th id="L1110"><a href="#L1110">1110</a></th><td> with other users. See Section 5.2.3</td></tr><tr><th id="L1111"><a href="#L1111">1111</a></th><td></td></tr><tr><th id="L1112"><a href="#L1112">1112</a></th><td> Definition:</td></tr><tr><th id="L1113"><a href="#L1113">1113</a></th><td></td></tr><tr><th id="L1114"><a href="#L1114">1114</a></th><td> <!ELEMENT can-be-shared EMPTY></td></tr><tr><th id="L1115"><a href="#L1115">1115</a></th><td></td></tr><tr><th id="L1116"><a href="#L1116">1116</a></th><td></td></tr><tr><th id="L1117"><a href="#L1117">1117</a></th><td></td></tr><tr><th id="L1118"><a href="#L1118">1118</a></th><td></td></tr><tr><th id="L1119"><a href="#L1119">1119</a></th><td>Daboo & York [Page 20]</td></tr><tr><th id="L1120"><a href="#L1120">1120</a></th><td></td></tr><tr><th id="L1121"><a href="#L1121">1121</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1122"><a href="#L1122">1122</a></th><td></td></tr><tr><th id="L1123"><a href="#L1123">1123</a></th><td></td></tr><tr><th id="L1124"><a href="#L1124">1124</a></th><td>6.4. CS:can-be-published</td></tr><tr><th id="L1125"><a href="#L1125">1125</a></th><td></td></tr><tr><th id="L1126"><a href="#L1126">1126</a></th><td> Name: can-be-published</td></tr><tr><th id="L1127"><a href="#L1127">1127</a></th><td></td></tr><tr><th id="L1128"><a href="#L1128">1128</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1129"><a href="#L1129">1129</a></th><td></td></tr><tr><th id="L1130"><a href="#L1130">1130</a></th><td> Purpose: Used to indicate that a calendar can be published.</td></tr><tr><th id="L1131"><a href="#L1131">1131</a></th><td></td></tr><tr><th id="L1132"><a href="#L1132">1132</a></th><td> Description: This element indicates that a calendar can be published</td></tr><tr><th id="L1133"><a href="#L1133">1133</a></th><td> to anyone. See Section 5.2.3</td></tr><tr><th id="L1134"><a href="#L1134">1134</a></th><td></td></tr><tr><th id="L1135"><a href="#L1135">1135</a></th><td> Definition:</td></tr><tr><th id="L1136"><a href="#L1136">1136</a></th><td></td></tr><tr><th id="L1137"><a href="#L1137">1137</a></th><td> <!ELEMENT can-be-published EMPTY></td></tr><tr><th id="L1138"><a href="#L1138">1138</a></th><td></td></tr><tr><th id="L1139"><a href="#L1139">1139</a></th><td>6.5. CS:user</td></tr><tr><th id="L1140"><a href="#L1140">1140</a></th><td></td></tr><tr><th id="L1141"><a href="#L1141">1141</a></th><td> Name: user</td></tr><tr><th id="L1142"><a href="#L1142">1142</a></th><td></td></tr><tr><th id="L1143"><a href="#L1143">1143</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1144"><a href="#L1144">1144</a></th><td></td></tr><tr><th id="L1145"><a href="#L1145">1145</a></th><td> Purpose: Used to show status of sharing invites sent to sharees.</td></tr><tr><th id="L1146"><a href="#L1146">1146</a></th><td></td></tr><tr><th id="L1147"><a href="#L1147">1147</a></th><td> Description: This element provides the "status" of a sharing invite</td></tr><tr><th id="L1148"><a href="#L1148">1148</a></th><td> sent to a particular user. See Section 5.2.2.</td></tr><tr><th id="L1149"><a href="#L1149">1149</a></th><td></td></tr><tr><th id="L1150"><a href="#L1150">1150</a></th><td> Definition:</td></tr><tr><th id="L1151"><a href="#L1151">1151</a></th><td></td></tr><tr><th id="L1152"><a href="#L1152">1152</a></th><td> <!ELEMENT user (DAV:href, common-name?, (invite-noresponse |</td></tr><tr><th id="L1153"><a href="#L1153">1153</a></th><td> invite-accepted | invite-declined | invite-invalid),</td></tr><tr><th id="L1154"><a href="#L1154">1154</a></th><td> access, summary?)></td></tr><tr><th id="L1155"><a href="#L1155">1155</a></th><td></td></tr><tr><th id="L1156"><a href="#L1156">1156</a></th><td>6.6. CS:invite-noresponse</td></tr><tr><th id="L1157"><a href="#L1157">1157</a></th><td></td></tr><tr><th id="L1158"><a href="#L1158">1158</a></th><td> Name: invite-noresponse</td></tr><tr><th id="L1159"><a href="#L1159">1159</a></th><td></td></tr><tr><th id="L1160"><a href="#L1160">1160</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1161"><a href="#L1161">1161</a></th><td></td></tr><tr><th id="L1162"><a href="#L1162">1162</a></th><td> Purpose: Sharing invite status.</td></tr><tr><th id="L1163"><a href="#L1163">1163</a></th><td></td></tr><tr><th id="L1164"><a href="#L1164">1164</a></th><td> Description: When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1165"><a href="#L1165">1165</a></th><td> element is used to indicate that the sharee has never replied to</td></tr><tr><th id="L1166"><a href="#L1166">1166</a></th><td> the corresponding sharing invite. When used in a CS:invite-</td></tr><tr><th id="L1167"><a href="#L1167">1167</a></th><td> notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1168"><a href="#L1168">1168</a></th><td> indicate to the sharee that a sharing reply is needed.</td></tr><tr><th id="L1169"><a href="#L1169">1169</a></th><td></td></tr><tr><th id="L1170"><a href="#L1170">1170</a></th><td></td></tr><tr><th id="L1171"><a href="#L1171">1171</a></th><td></td></tr><tr><th id="L1172"><a href="#L1172">1172</a></th><td></td></tr><tr><th id="L1173"><a href="#L1173">1173</a></th><td></td></tr><tr><th id="L1174"><a href="#L1174">1174</a></th><td></td></tr><tr><th id="L1175"><a href="#L1175">1175</a></th><td>Daboo & York [Page 21]</td></tr><tr><th id="L1176"><a href="#L1176">1176</a></th><td></td></tr><tr><th id="L1177"><a href="#L1177">1177</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1178"><a href="#L1178">1178</a></th><td></td></tr><tr><th id="L1179"><a href="#L1179">1179</a></th><td></td></tr><tr><th id="L1180"><a href="#L1180">1180</a></th><td> Definition:</td></tr><tr><th id="L1181"><a href="#L1181">1181</a></th><td></td></tr><tr><th id="L1182"><a href="#L1182">1182</a></th><td> <!ELEMENT invite-noresponse EMPTY></td></tr><tr><th id="L1183"><a href="#L1183">1183</a></th><td></td></tr><tr><th id="L1184"><a href="#L1184">1184</a></th><td>6.7. CS:invite-deleted</td></tr><tr><th id="L1185"><a href="#L1185">1185</a></th><td></td></tr><tr><th id="L1186"><a href="#L1186">1186</a></th><td> Name: invite-deleted</td></tr><tr><th id="L1187"><a href="#L1187">1187</a></th><td></td></tr><tr><th id="L1188"><a href="#L1188">1188</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1189"><a href="#L1189">1189</a></th><td></td></tr><tr><th id="L1190"><a href="#L1190">1190</a></th><td> Purpose: Sharing invite status.</td></tr><tr><th id="L1191"><a href="#L1191">1191</a></th><td></td></tr><tr><th id="L1192"><a href="#L1192">1192</a></th><td> Description: When used in a CS:invite-notification (Section 6.15)</td></tr><tr><th id="L1193"><a href="#L1193">1193</a></th><td> element, this element is used to indicate to the sharee that a</td></tr><tr><th id="L1194"><a href="#L1194">1194</a></th><td> shared calendar has been unshared by the sharer.</td></tr><tr><th id="L1195"><a href="#L1195">1195</a></th><td></td></tr><tr><th id="L1196"><a href="#L1196">1196</a></th><td> Definition:</td></tr><tr><th id="L1197"><a href="#L1197">1197</a></th><td></td></tr><tr><th id="L1198"><a href="#L1198">1198</a></th><td> <!ELEMENT invite-deleted EMPTY></td></tr><tr><th id="L1199"><a href="#L1199">1199</a></th><td></td></tr><tr><th id="L1200"><a href="#L1200">1200</a></th><td>6.8. CS:invite-accepted</td></tr><tr><th id="L1201"><a href="#L1201">1201</a></th><td></td></tr><tr><th id="L1202"><a href="#L1202">1202</a></th><td> Name: invite-accepted</td></tr><tr><th id="L1203"><a href="#L1203">1203</a></th><td></td></tr><tr><th id="L1204"><a href="#L1204">1204</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1205"><a href="#L1205">1205</a></th><td></td></tr><tr><th id="L1206"><a href="#L1206">1206</a></th><td> Purpose: Sharing invite status.</td></tr><tr><th id="L1207"><a href="#L1207">1207</a></th><td></td></tr><tr><th id="L1208"><a href="#L1208">1208</a></th><td> Description: When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1209"><a href="#L1209">1209</a></th><td> element is used to indicate that the sharee has accepted the</td></tr><tr><th id="L1210"><a href="#L1210">1210</a></th><td> corresponding sharing invite. When used in a CS:invite-</td></tr><tr><th id="L1211"><a href="#L1211">1211</a></th><td> notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1212"><a href="#L1212">1212</a></th><td> indicate to the sharee that the sharing invite is an update for</td></tr><tr><th id="L1213"><a href="#L1213">1213</a></th><td> one they previously accepted.</td></tr><tr><th id="L1214"><a href="#L1214">1214</a></th><td></td></tr><tr><th id="L1215"><a href="#L1215">1215</a></th><td> Definition:</td></tr><tr><th id="L1216"><a href="#L1216">1216</a></th><td></td></tr><tr><th id="L1217"><a href="#L1217">1217</a></th><td> <!ELEMENT invite-accepted EMPTY></td></tr><tr><th id="L1218"><a href="#L1218">1218</a></th><td></td></tr><tr><th id="L1219"><a href="#L1219">1219</a></th><td>6.9. CS:invite-declined</td></tr><tr><th id="L1220"><a href="#L1220">1220</a></th><td></td></tr><tr><th id="L1221"><a href="#L1221">1221</a></th><td> Name: invite-declined</td></tr><tr><th id="L1222"><a href="#L1222">1222</a></th><td></td></tr><tr><th id="L1223"><a href="#L1223">1223</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1224"><a href="#L1224">1224</a></th><td></td></tr><tr><th id="L1225"><a href="#L1225">1225</a></th><td></td></tr><tr><th id="L1226"><a href="#L1226">1226</a></th><td></td></tr><tr><th id="L1227"><a href="#L1227">1227</a></th><td></td></tr><tr><th id="L1228"><a href="#L1228">1228</a></th><td></td></tr><tr><th id="L1229"><a href="#L1229">1229</a></th><td></td></tr><tr><th id="L1230"><a href="#L1230">1230</a></th><td></td></tr><tr><th id="L1231"><a href="#L1231">1231</a></th><td>Daboo & York [Page 22]</td></tr><tr><th id="L1232"><a href="#L1232">1232</a></th><td></td></tr><tr><th id="L1233"><a href="#L1233">1233</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1234"><a href="#L1234">1234</a></th><td></td></tr><tr><th id="L1235"><a href="#L1235">1235</a></th><td></td></tr><tr><th id="L1236"><a href="#L1236">1236</a></th><td> Purpose: Sharing invite status.</td></tr><tr><th id="L1237"><a href="#L1237">1237</a></th><td></td></tr><tr><th id="L1238"><a href="#L1238">1238</a></th><td> Description: When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1239"><a href="#L1239">1239</a></th><td> element is used to indicate that the sharee has declined the</td></tr><tr><th id="L1240"><a href="#L1240">1240</a></th><td> corresponding sharing invite. When used in a CS:invite-</td></tr><tr><th id="L1241"><a href="#L1241">1241</a></th><td> notification (Section 6.15) element, this element is used to</td></tr><tr><th id="L1242"><a href="#L1242">1242</a></th><td> indicate to the sharee that the sharing invite is an update for</td></tr><tr><th id="L1243"><a href="#L1243">1243</a></th><td> one they previously declined.</td></tr><tr><th id="L1244"><a href="#L1244">1244</a></th><td></td></tr><tr><th id="L1245"><a href="#L1245">1245</a></th><td> Definition:</td></tr><tr><th id="L1246"><a href="#L1246">1246</a></th><td></td></tr><tr><th id="L1247"><a href="#L1247">1247</a></th><td> <!ELEMENT invite-declined EMPTY></td></tr><tr><th id="L1248"><a href="#L1248">1248</a></th><td></td></tr><tr><th id="L1249"><a href="#L1249">1249</a></th><td>6.10. CS:invite-invalid</td></tr><tr><th id="L1250"><a href="#L1250">1250</a></th><td></td></tr><tr><th id="L1251"><a href="#L1251">1251</a></th><td> Name: invite-invalid</td></tr><tr><th id="L1252"><a href="#L1252">1252</a></th><td></td></tr><tr><th id="L1253"><a href="#L1253">1253</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1254"><a href="#L1254">1254</a></th><td></td></tr><tr><th id="L1255"><a href="#L1255">1255</a></th><td> Purpose: Sharing invite status.</td></tr><tr><th id="L1256"><a href="#L1256">1256</a></th><td></td></tr><tr><th id="L1257"><a href="#L1257">1257</a></th><td> Description: When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1258"><a href="#L1258">1258</a></th><td> element is used to indicate that the corresponding sharee is not a</td></tr><tr><th id="L1259"><a href="#L1259">1259</a></th><td> valid calendar user known to the server.</td></tr><tr><th id="L1260"><a href="#L1260">1260</a></th><td></td></tr><tr><th id="L1261"><a href="#L1261">1261</a></th><td> Definition:</td></tr><tr><th id="L1262"><a href="#L1262">1262</a></th><td></td></tr><tr><th id="L1263"><a href="#L1263">1263</a></th><td> <!ELEMENT invite-invalid EMPTY></td></tr><tr><th id="L1264"><a href="#L1264">1264</a></th><td></td></tr><tr><th id="L1265"><a href="#L1265">1265</a></th><td>6.11. CS:access</td></tr><tr><th id="L1266"><a href="#L1266">1266</a></th><td></td></tr><tr><th id="L1267"><a href="#L1267">1267</a></th><td> Name: access</td></tr><tr><th id="L1268"><a href="#L1268">1268</a></th><td></td></tr><tr><th id="L1269"><a href="#L1269">1269</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1270"><a href="#L1270">1270</a></th><td></td></tr><tr><th id="L1271"><a href="#L1271">1271</a></th><td> Purpose: Shared calendar access level.</td></tr><tr><th id="L1272"><a href="#L1272">1272</a></th><td></td></tr><tr><th id="L1273"><a href="#L1273">1273</a></th><td> Description: When used in a CS:user (Section 6.5) element, this</td></tr><tr><th id="L1274"><a href="#L1274">1274</a></th><td> element is used to indicate the sharing access level granted to</td></tr><tr><th id="L1275"><a href="#L1275">1275</a></th><td> the corresponding sharee.</td></tr><tr><th id="L1276"><a href="#L1276">1276</a></th><td></td></tr><tr><th id="L1277"><a href="#L1277">1277</a></th><td> Definition:</td></tr><tr><th id="L1278"><a href="#L1278">1278</a></th><td></td></tr><tr><th id="L1279"><a href="#L1279">1279</a></th><td> <!ELEMENT invite-invalid (read | read-write)></td></tr><tr><th id="L1280"><a href="#L1280">1280</a></th><td></td></tr><tr><th id="L1281"><a href="#L1281">1281</a></th><td></td></tr><tr><th id="L1282"><a href="#L1282">1282</a></th><td></td></tr><tr><th id="L1283"><a href="#L1283">1283</a></th><td></td></tr><tr><th id="L1284"><a href="#L1284">1284</a></th><td></td></tr><tr><th id="L1285"><a href="#L1285">1285</a></th><td></td></tr><tr><th id="L1286"><a href="#L1286">1286</a></th><td></td></tr><tr><th id="L1287"><a href="#L1287">1287</a></th><td>Daboo & York [Page 23]</td></tr><tr><th id="L1288"><a href="#L1288">1288</a></th><td></td></tr><tr><th id="L1289"><a href="#L1289">1289</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1290"><a href="#L1290">1290</a></th><td></td></tr><tr><th id="L1291"><a href="#L1291">1291</a></th><td></td></tr><tr><th id="L1292"><a href="#L1292">1292</a></th><td>6.12. CS:read</td></tr><tr><th id="L1293"><a href="#L1293">1293</a></th><td></td></tr><tr><th id="L1294"><a href="#L1294">1294</a></th><td> Name: read</td></tr><tr><th id="L1295"><a href="#L1295">1295</a></th><td></td></tr><tr><th id="L1296"><a href="#L1296">1296</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1297"><a href="#L1297">1297</a></th><td></td></tr><tr><th id="L1298"><a href="#L1298">1298</a></th><td> Purpose: Shared calendar access level privilege.</td></tr><tr><th id="L1299"><a href="#L1299">1299</a></th><td></td></tr><tr><th id="L1300"><a href="#L1300">1300</a></th><td> Description: Indicates that the access level granted only allows</td></tr><tr><th id="L1301"><a href="#L1301">1301</a></th><td> sharees to read data in the shared calendar (though they can write</td></tr><tr><th id="L1302"><a href="#L1302">1302</a></th><td> per-user data (Section 5.5.4)).</td></tr><tr><th id="L1303"><a href="#L1303">1303</a></th><td></td></tr><tr><th id="L1304"><a href="#L1304">1304</a></th><td> Definition:</td></tr><tr><th id="L1305"><a href="#L1305">1305</a></th><td></td></tr><tr><th id="L1306"><a href="#L1306">1306</a></th><td> <!ELEMENT read EMPTY></td></tr><tr><th id="L1307"><a href="#L1307">1307</a></th><td></td></tr><tr><th id="L1308"><a href="#L1308">1308</a></th><td>6.13. CS:read-write</td></tr><tr><th id="L1309"><a href="#L1309">1309</a></th><td></td></tr><tr><th id="L1310"><a href="#L1310">1310</a></th><td> Name: read-write</td></tr><tr><th id="L1311"><a href="#L1311">1311</a></th><td></td></tr><tr><th id="L1312"><a href="#L1312">1312</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1313"><a href="#L1313">1313</a></th><td></td></tr><tr><th id="L1314"><a href="#L1314">1314</a></th><td> Purpose: Shared calendar access level privilege.</td></tr><tr><th id="L1315"><a href="#L1315">1315</a></th><td></td></tr><tr><th id="L1316"><a href="#L1316">1316</a></th><td> Description: Indicates that the access level granted allows sharees</td></tr><tr><th id="L1317"><a href="#L1317">1317</a></th><td> to read and write all data in the shared calendar, with the</td></tr><tr><th id="L1318"><a href="#L1318">1318</a></th><td> exception of components that would trigger scheduling.</td></tr><tr><th id="L1319"><a href="#L1319">1319</a></th><td></td></tr><tr><th id="L1320"><a href="#L1320">1320</a></th><td> Definition:</td></tr><tr><th id="L1321"><a href="#L1321">1321</a></th><td></td></tr><tr><th id="L1322"><a href="#L1322">1322</a></th><td> <!ELEMENT read-write EMPTY></td></tr><tr><th id="L1323"><a href="#L1323">1323</a></th><td></td></tr><tr><th id="L1324"><a href="#L1324">1324</a></th><td>6.14. CS:summary</td></tr><tr><th id="L1325"><a href="#L1325">1325</a></th><td></td></tr><tr><th id="L1326"><a href="#L1326">1326</a></th><td> Name: summary</td></tr><tr><th id="L1327"><a href="#L1327">1327</a></th><td></td></tr><tr><th id="L1328"><a href="#L1328">1328</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1329"><a href="#L1329">1329</a></th><td></td></tr><tr><th id="L1330"><a href="#L1330">1330</a></th><td> Purpose: Summary or title of shared calendar.</td></tr><tr><th id="L1331"><a href="#L1331">1331</a></th><td></td></tr><tr><th id="L1332"><a href="#L1332">1332</a></th><td> Description: A brief description of a shared calendar. This can be</td></tr><tr><th id="L1333"><a href="#L1333">1333</a></th><td> used by sharers to communicate the nature of a shared calendar to</td></tr><tr><th id="L1334"><a href="#L1334">1334</a></th><td> sharees, as well as used by sharees to indicate back to the sharer</td></tr><tr><th id="L1335"><a href="#L1335">1335</a></th><td> how each sharee is refering to the shared calendar.</td></tr><tr><th id="L1336"><a href="#L1336">1336</a></th><td></td></tr><tr><th id="L1337"><a href="#L1337">1337</a></th><td></td></tr><tr><th id="L1338"><a href="#L1338">1338</a></th><td></td></tr><tr><th id="L1339"><a href="#L1339">1339</a></th><td></td></tr><tr><th id="L1340"><a href="#L1340">1340</a></th><td></td></tr><tr><th id="L1341"><a href="#L1341">1341</a></th><td></td></tr><tr><th id="L1342"><a href="#L1342">1342</a></th><td></td></tr><tr><th id="L1343"><a href="#L1343">1343</a></th><td>Daboo & York [Page 24]</td></tr><tr><th id="L1344"><a href="#L1344">1344</a></th><td></td></tr><tr><th id="L1345"><a href="#L1345">1345</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1346"><a href="#L1346">1346</a></th><td></td></tr><tr><th id="L1347"><a href="#L1347">1347</a></th><td></td></tr><tr><th id="L1348"><a href="#L1348">1348</a></th><td> Definition:</td></tr><tr><th id="L1349"><a href="#L1349">1349</a></th><td></td></tr><tr><th id="L1350"><a href="#L1350">1350</a></th><td> <!ELEMENT summary (#PCDATA)></td></tr><tr><th id="L1351"><a href="#L1351">1351</a></th><td></td></tr><tr><th id="L1352"><a href="#L1352">1352</a></th><td>6.15. CS:invite-notification</td></tr><tr><th id="L1353"><a href="#L1353">1353</a></th><td></td></tr><tr><th id="L1354"><a href="#L1354">1354</a></th><td> Name: invite-notification</td></tr><tr><th id="L1355"><a href="#L1355">1355</a></th><td></td></tr><tr><th id="L1356"><a href="#L1356">1356</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1357"><a href="#L1357">1357</a></th><td></td></tr><tr><th id="L1358"><a href="#L1358">1358</a></th><td> Purpose: A notification used as a shared calendar invite.</td></tr><tr><th id="L1359"><a href="#L1359">1359</a></th><td></td></tr><tr><th id="L1360"><a href="#L1360">1360</a></th><td> Description: Defines a notification message sent automatically by</td></tr><tr><th id="L1361"><a href="#L1361">1361</a></th><td> the server when a sharer adds, changes or removes a sharee from a</td></tr><tr><th id="L1362"><a href="#L1362">1362</a></th><td> shared calendar. The DAV:href element specifies the calendar user</td></tr><tr><th id="L1363"><a href="#L1363">1363</a></th><td> address of the sharee to whom the message was sent.</td></tr><tr><th id="L1364"><a href="#L1364">1364</a></th><td></td></tr><tr><th id="L1365"><a href="#L1365">1365</a></th><td> Definition:</td></tr><tr><th id="L1366"><a href="#L1366">1366</a></th><td></td></tr><tr><th id="L1367"><a href="#L1367">1367</a></th><td> <!ELEMENT invite-notification (uid, DAV:href,</td></tr><tr><th id="L1368"><a href="#L1368">1368</a></th><td> (invite-noresponse | invite-deleted |</td></tr><tr><th id="L1369"><a href="#L1369">1369</a></th><td> invite-accepted | invite-declined),</td></tr><tr><th id="L1370"><a href="#L1370">1370</a></th><td> access, hosturl, organizer, summary?></td></tr><tr><th id="L1371"><a href="#L1371">1371</a></th><td></td></tr><tr><th id="L1372"><a href="#L1372">1372</a></th><td>6.16. CS:uid</td></tr><tr><th id="L1373"><a href="#L1373">1373</a></th><td></td></tr><tr><th id="L1374"><a href="#L1374">1374</a></th><td> Name: uid</td></tr><tr><th id="L1375"><a href="#L1375">1375</a></th><td></td></tr><tr><th id="L1376"><a href="#L1376">1376</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1377"><a href="#L1377">1377</a></th><td></td></tr><tr><th id="L1378"><a href="#L1378">1378</a></th><td> Purpose: Unique identifier.</td></tr><tr><th id="L1379"><a href="#L1379">1379</a></th><td></td></tr><tr><th id="L1380"><a href="#L1380">1380</a></th><td> Description: A unique identifier for an invitation to a shared</td></tr><tr><th id="L1381"><a href="#L1381">1381</a></th><td> calendar.</td></tr><tr><th id="L1382"><a href="#L1382">1382</a></th><td></td></tr><tr><th id="L1383"><a href="#L1383">1383</a></th><td> Definition:</td></tr><tr><th id="L1384"><a href="#L1384">1384</a></th><td></td></tr><tr><th id="L1385"><a href="#L1385">1385</a></th><td> <!ELEMENT uid (#PCDATA)></td></tr><tr><th id="L1386"><a href="#L1386">1386</a></th><td></td></tr><tr><th id="L1387"><a href="#L1387">1387</a></th><td>6.17. CS:hosturl</td></tr><tr><th id="L1388"><a href="#L1388">1388</a></th><td></td></tr><tr><th id="L1389"><a href="#L1389">1389</a></th><td> Name: hosturl</td></tr><tr><th id="L1390"><a href="#L1390">1390</a></th><td></td></tr><tr><th id="L1391"><a href="#L1391">1391</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1392"><a href="#L1392">1392</a></th><td></td></tr><tr><th id="L1393"><a href="#L1393">1393</a></th><td></td></tr><tr><th id="L1394"><a href="#L1394">1394</a></th><td></td></tr><tr><th id="L1395"><a href="#L1395">1395</a></th><td></td></tr><tr><th id="L1396"><a href="#L1396">1396</a></th><td></td></tr><tr><th id="L1397"><a href="#L1397">1397</a></th><td></td></tr><tr><th id="L1398"><a href="#L1398">1398</a></th><td></td></tr><tr><th id="L1399"><a href="#L1399">1399</a></th><td>Daboo & York [Page 25]</td></tr><tr><th id="L1400"><a href="#L1400">1400</a></th><td></td></tr><tr><th id="L1401"><a href="#L1401">1401</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1402"><a href="#L1402">1402</a></th><td></td></tr><tr><th id="L1403"><a href="#L1403">1403</a></th><td></td></tr><tr><th id="L1404"><a href="#L1404">1404</a></th><td> Purpose: Identifies the source URL of a shared calendar.</td></tr><tr><th id="L1405"><a href="#L1405">1405</a></th><td></td></tr><tr><th id="L1406"><a href="#L1406">1406</a></th><td> Description: Contains a single DAV:href element that refers to the</td></tr><tr><th id="L1407"><a href="#L1407">1407</a></th><td> source of a shared calendar - i.e., the URL of the calendar shared</td></tr><tr><th id="L1408"><a href="#L1408">1408</a></th><td> by the sharer.</td></tr><tr><th id="L1409"><a href="#L1409">1409</a></th><td></td></tr><tr><th id="L1410"><a href="#L1410">1410</a></th><td> Definition:</td></tr><tr><th id="L1411"><a href="#L1411">1411</a></th><td></td></tr><tr><th id="L1412"><a href="#L1412">1412</a></th><td> <!ELEMENT hosturl (DAV:href)></td></tr><tr><th id="L1413"><a href="#L1413">1413</a></th><td></td></tr><tr><th id="L1414"><a href="#L1414">1414</a></th><td>6.18. CS:organizer</td></tr><tr><th id="L1415"><a href="#L1415">1415</a></th><td></td></tr><tr><th id="L1416"><a href="#L1416">1416</a></th><td> Name: organizer</td></tr><tr><th id="L1417"><a href="#L1417">1417</a></th><td></td></tr><tr><th id="L1418"><a href="#L1418">1418</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1419"><a href="#L1419">1419</a></th><td></td></tr><tr><th id="L1420"><a href="#L1420">1420</a></th><td> Purpose: Identifies the sharer of a shared calendar.</td></tr><tr><th id="L1421"><a href="#L1421">1421</a></th><td></td></tr><tr><th id="L1422"><a href="#L1422">1422</a></th><td> Description: Contains a single DAV:href element that identifies the</td></tr><tr><th id="L1423"><a href="#L1423">1423</a></th><td> calendar user address of the sharer of a shared calendar, and an</td></tr><tr><th id="L1424"><a href="#L1424">1424</a></th><td> optional CS:common-name element that matches that user.</td></tr><tr><th id="L1425"><a href="#L1425">1425</a></th><td></td></tr><tr><th id="L1426"><a href="#L1426">1426</a></th><td> Definition:</td></tr><tr><th id="L1427"><a href="#L1427">1427</a></th><td></td></tr><tr><th id="L1428"><a href="#L1428">1428</a></th><td> <!ELEMENT organizer (DAV:href, CS:common-name?)></td></tr><tr><th id="L1429"><a href="#L1429">1429</a></th><td></td></tr><tr><th id="L1430"><a href="#L1430">1430</a></th><td>6.19. CS:common-name</td></tr><tr><th id="L1431"><a href="#L1431">1431</a></th><td></td></tr><tr><th id="L1432"><a href="#L1432">1432</a></th><td> Name: common-name</td></tr><tr><th id="L1433"><a href="#L1433">1433</a></th><td></td></tr><tr><th id="L1434"><a href="#L1434">1434</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1435"><a href="#L1435">1435</a></th><td></td></tr><tr><th id="L1436"><a href="#L1436">1436</a></th><td> Purpose: The common name of a sharer or sharee.</td></tr><tr><th id="L1437"><a href="#L1437">1437</a></th><td></td></tr><tr><th id="L1438"><a href="#L1438">1438</a></th><td> Description: The common name is optionally provided by a client when</td></tr><tr><th id="L1439"><a href="#L1439">1439</a></th><td> adding a sharee and optionally included (or modified) by the</td></tr><tr><th id="L1440"><a href="#L1440">1440</a></th><td> server when returning results for sharers or sharees and in</td></tr><tr><th id="L1441"><a href="#L1441">1441</a></th><td> notifications.</td></tr><tr><th id="L1442"><a href="#L1442">1442</a></th><td></td></tr><tr><th id="L1443"><a href="#L1443">1443</a></th><td> Definition:</td></tr><tr><th id="L1444"><a href="#L1444">1444</a></th><td></td></tr><tr><th id="L1445"><a href="#L1445">1445</a></th><td> <!ELEMENT common-name (#PCDATA)></td></tr><tr><th id="L1446"><a href="#L1446">1446</a></th><td></td></tr><tr><th id="L1447"><a href="#L1447">1447</a></th><td>6.20. CS:invite-reply</td></tr><tr><th id="L1448"><a href="#L1448">1448</a></th><td></td></tr><tr><th id="L1449"><a href="#L1449">1449</a></th><td></td></tr><tr><th id="L1450"><a href="#L1450">1450</a></th><td></td></tr><tr><th id="L1451"><a href="#L1451">1451</a></th><td></td></tr><tr><th id="L1452"><a href="#L1452">1452</a></th><td></td></tr><tr><th id="L1453"><a href="#L1453">1453</a></th><td></td></tr><tr><th id="L1454"><a href="#L1454">1454</a></th><td></td></tr><tr><th id="L1455"><a href="#L1455">1455</a></th><td>Daboo & York [Page 26]</td></tr><tr><th id="L1456"><a href="#L1456">1456</a></th><td></td></tr><tr><th id="L1457"><a href="#L1457">1457</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1458"><a href="#L1458">1458</a></th><td></td></tr><tr><th id="L1459"><a href="#L1459">1459</a></th><td></td></tr><tr><th id="L1460"><a href="#L1460">1460</a></th><td> Name: invite-reply</td></tr><tr><th id="L1461"><a href="#L1461">1461</a></th><td></td></tr><tr><th id="L1462"><a href="#L1462">1462</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1463"><a href="#L1463">1463</a></th><td></td></tr><tr><th id="L1464"><a href="#L1464">1464</a></th><td> Purpose: A notification used as a reply to a shared calendar invite.</td></tr><tr><th id="L1465"><a href="#L1465">1465</a></th><td></td></tr><tr><th id="L1466"><a href="#L1466">1466</a></th><td> Description: Defines a notification message sent automatically by</td></tr><tr><th id="L1467"><a href="#L1467">1467</a></th><td> the server when a sharee replies to a shared calendar invite. The</td></tr><tr><th id="L1468"><a href="#L1468">1468</a></th><td> DAV:href element specifies the calendar user address of the sharee</td></tr><tr><th id="L1469"><a href="#L1469">1469</a></th><td> to whom the original invite message was sent.</td></tr><tr><th id="L1470"><a href="#L1470">1470</a></th><td></td></tr><tr><th id="L1471"><a href="#L1471">1471</a></th><td> Definition:</td></tr><tr><th id="L1472"><a href="#L1472">1472</a></th><td></td></tr><tr><th id="L1473"><a href="#L1473">1473</a></th><td> <!ELEMENT invite-reply (DAV:href,</td></tr><tr><th id="L1474"><a href="#L1474">1474</a></th><td> (invite-accepted | invite-declined),</td></tr><tr><th id="L1475"><a href="#L1475">1475</a></th><td> hosturl, in-reply-to, summary?></td></tr><tr><th id="L1476"><a href="#L1476">1476</a></th><td></td></tr><tr><th id="L1477"><a href="#L1477">1477</a></th><td>6.21. CS:in-reply-to</td></tr><tr><th id="L1478"><a href="#L1478">1478</a></th><td></td></tr><tr><th id="L1479"><a href="#L1479">1479</a></th><td> Name: in-reply-to</td></tr><tr><th id="L1480"><a href="#L1480">1480</a></th><td></td></tr><tr><th id="L1481"><a href="#L1481">1481</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1482"><a href="#L1482">1482</a></th><td></td></tr><tr><th id="L1483"><a href="#L1483">1483</a></th><td> Purpose: Unique identifier.</td></tr><tr><th id="L1484"><a href="#L1484">1484</a></th><td></td></tr><tr><th id="L1485"><a href="#L1485">1485</a></th><td> Description: Specifies the unique identifier of the inviate message</td></tr><tr><th id="L1486"><a href="#L1486">1486</a></th><td> that this notification message is a reply to.</td></tr><tr><th id="L1487"><a href="#L1487">1487</a></th><td></td></tr><tr><th id="L1488"><a href="#L1488">1488</a></th><td> Definition:</td></tr><tr><th id="L1489"><a href="#L1489">1489</a></th><td></td></tr><tr><th id="L1490"><a href="#L1490">1490</a></th><td> <!ELEMENT in-reply-to (#PCDATA)></td></tr><tr><th id="L1491"><a href="#L1491">1491</a></th><td></td></tr><tr><th id="L1492"><a href="#L1492">1492</a></th><td>6.22. CS:notification</td></tr><tr><th id="L1493"><a href="#L1493">1493</a></th><td></td></tr><tr><th id="L1494"><a href="#L1494">1494</a></th><td> Name: notification</td></tr><tr><th id="L1495"><a href="#L1495">1495</a></th><td></td></tr><tr><th id="L1496"><a href="#L1496">1496</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1497"><a href="#L1497">1497</a></th><td></td></tr><tr><th id="L1498"><a href="#L1498">1498</a></th><td> Purpose: Notification message root element.</td></tr><tr><th id="L1499"><a href="#L1499">1499</a></th><td></td></tr><tr><th id="L1500"><a href="#L1500">1500</a></th><td> Description: The root element used in notification resources.</td></tr><tr><th id="L1501"><a href="#L1501">1501</a></th><td></td></tr><tr><th id="L1502"><a href="#L1502">1502</a></th><td> Definition:</td></tr><tr><th id="L1503"><a href="#L1503">1503</a></th><td></td></tr><tr><th id="L1504"><a href="#L1504">1504</a></th><td> <!ELEMENT notification (CS:dtstamp,</td></tr><tr><th id="L1505"><a href="#L1505">1505</a></th><td> (invite-notification | invite-reply)></td></tr><tr><th id="L1506"><a href="#L1506">1506</a></th><td> <!-- Any notification type element can appear after CS:dtstamp,</td></tr><tr><th id="L1507"><a href="#L1507">1507</a></th><td> this specification defines only the two listed above --></td></tr><tr><th id="L1508"><a href="#L1508">1508</a></th><td></td></tr><tr><th id="L1509"><a href="#L1509">1509</a></th><td></td></tr><tr><th id="L1510"><a href="#L1510">1510</a></th><td></td></tr><tr><th id="L1511"><a href="#L1511">1511</a></th><td>Daboo & York [Page 27]</td></tr><tr><th id="L1512"><a href="#L1512">1512</a></th><td></td></tr><tr><th id="L1513"><a href="#L1513">1513</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1514"><a href="#L1514">1514</a></th><td></td></tr><tr><th id="L1515"><a href="#L1515">1515</a></th><td></td></tr><tr><th id="L1516"><a href="#L1516">1516</a></th><td>6.23. CS:dtstamp</td></tr><tr><th id="L1517"><a href="#L1517">1517</a></th><td></td></tr><tr><th id="L1518"><a href="#L1518">1518</a></th><td> Name: dtstamp</td></tr><tr><th id="L1519"><a href="#L1519">1519</a></th><td></td></tr><tr><th id="L1520"><a href="#L1520">1520</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1521"><a href="#L1521">1521</a></th><td></td></tr><tr><th id="L1522"><a href="#L1522">1522</a></th><td> Purpose: Date-time stamp.</td></tr><tr><th id="L1523"><a href="#L1523">1523</a></th><td></td></tr><tr><th id="L1524"><a href="#L1524">1524</a></th><td> Description: Contains the date-time stamp corresponding to the</td></tr><tr><th id="L1525"><a href="#L1525">1525</a></th><td> creation of a notification message.</td></tr><tr><th id="L1526"><a href="#L1526">1526</a></th><td></td></tr><tr><th id="L1527"><a href="#L1527">1527</a></th><td> Definition:</td></tr><tr><th id="L1528"><a href="#L1528">1528</a></th><td></td></tr><tr><th id="L1529"><a href="#L1529">1529</a></th><td> <!ELEMENT dtstamp (#PCDATA)></td></tr><tr><th id="L1530"><a href="#L1530">1530</a></th><td></td></tr><tr><th id="L1531"><a href="#L1531">1531</a></th><td>6.24. CS:share</td></tr><tr><th id="L1532"><a href="#L1532">1532</a></th><td></td></tr><tr><th id="L1533"><a href="#L1533">1533</a></th><td> Name: share</td></tr><tr><th id="L1534"><a href="#L1534">1534</a></th><td></td></tr><tr><th id="L1535"><a href="#L1535">1535</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1536"><a href="#L1536">1536</a></th><td></td></tr><tr><th id="L1537"><a href="#L1537">1537</a></th><td> Purpose: Describes changes to sharees.</td></tr><tr><th id="L1538"><a href="#L1538">1538</a></th><td></td></tr><tr><th id="L1539"><a href="#L1539">1539</a></th><td> Description: The root element used in POST requests on calendars by</td></tr><tr><th id="L1540"><a href="#L1540">1540</a></th><td> sharers to manipulate the sharee list of a shared calendar.</td></tr><tr><th id="L1541"><a href="#L1541">1541</a></th><td></td></tr><tr><th id="L1542"><a href="#L1542">1542</a></th><td> Definition:</td></tr><tr><th id="L1543"><a href="#L1543">1543</a></th><td></td></tr><tr><th id="L1544"><a href="#L1544">1544</a></th><td> <!ELEMENT share (set | remove)*></td></tr><tr><th id="L1545"><a href="#L1545">1545</a></th><td></td></tr><tr><th id="L1546"><a href="#L1546">1546</a></th><td>6.25. CS:set</td></tr><tr><th id="L1547"><a href="#L1547">1547</a></th><td></td></tr><tr><th id="L1548"><a href="#L1548">1548</a></th><td> Name: set</td></tr><tr><th id="L1549"><a href="#L1549">1549</a></th><td></td></tr><tr><th id="L1550"><a href="#L1550">1550</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1551"><a href="#L1551">1551</a></th><td></td></tr><tr><th id="L1552"><a href="#L1552">1552</a></th><td> Purpose: Sets access for a sharee.</td></tr><tr><th id="L1553"><a href="#L1553">1553</a></th><td></td></tr><tr><th id="L1554"><a href="#L1554">1554</a></th><td> Description: Used to add or modify sharee access to a shared</td></tr><tr><th id="L1555"><a href="#L1555">1555</a></th><td> calendar. The specified access to the shared calendar is given to</td></tr><tr><th id="L1556"><a href="#L1556">1556</a></th><td> the sharee.</td></tr><tr><th id="L1557"><a href="#L1557">1557</a></th><td></td></tr><tr><th id="L1558"><a href="#L1558">1558</a></th><td> Definition:</td></tr><tr><th id="L1559"><a href="#L1559">1559</a></th><td></td></tr><tr><th id="L1560"><a href="#L1560">1560</a></th><td> <!ELEMENT set (DAV:href, common-name?, summary?,</td></tr><tr><th id="L1561"><a href="#L1561">1561</a></th><td> (read | read-write)></td></tr><tr><th id="L1562"><a href="#L1562">1562</a></th><td></td></tr><tr><th id="L1563"><a href="#L1563">1563</a></th><td></td></tr><tr><th id="L1564"><a href="#L1564">1564</a></th><td></td></tr><tr><th id="L1565"><a href="#L1565">1565</a></th><td></td></tr><tr><th id="L1566"><a href="#L1566">1566</a></th><td></td></tr><tr><th id="L1567"><a href="#L1567">1567</a></th><td>Daboo & York [Page 28]</td></tr><tr><th id="L1568"><a href="#L1568">1568</a></th><td></td></tr><tr><th id="L1569"><a href="#L1569">1569</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1570"><a href="#L1570">1570</a></th><td></td></tr><tr><th id="L1571"><a href="#L1571">1571</a></th><td></td></tr><tr><th id="L1572"><a href="#L1572">1572</a></th><td>6.26. CS:remove</td></tr><tr><th id="L1573"><a href="#L1573">1573</a></th><td></td></tr><tr><th id="L1574"><a href="#L1574">1574</a></th><td> Name: remove</td></tr><tr><th id="L1575"><a href="#L1575">1575</a></th><td></td></tr><tr><th id="L1576"><a href="#L1576">1576</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1577"><a href="#L1577">1577</a></th><td></td></tr><tr><th id="L1578"><a href="#L1578">1578</a></th><td> Purpose: Removes access for a sharee.</td></tr><tr><th id="L1579"><a href="#L1579">1579</a></th><td></td></tr><tr><th id="L1580"><a href="#L1580">1580</a></th><td> Description: Used to remove sharee access to a shared calendar. All</td></tr><tr><th id="L1581"><a href="#L1581">1581</a></th><td> access to the shared calendar is removed for the sharee.</td></tr><tr><th id="L1582"><a href="#L1582">1582</a></th><td></td></tr><tr><th id="L1583"><a href="#L1583">1583</a></th><td> Definition:</td></tr><tr><th id="L1584"><a href="#L1584">1584</a></th><td></td></tr><tr><th id="L1585"><a href="#L1585">1585</a></th><td> <!ELEMENT remove (DAV:href)></td></tr><tr><th id="L1586"><a href="#L1586">1586</a></th><td></td></tr><tr><th id="L1587"><a href="#L1587">1587</a></th><td>6.27. CS:shared-as</td></tr><tr><th id="L1588"><a href="#L1588">1588</a></th><td></td></tr><tr><th id="L1589"><a href="#L1589">1589</a></th><td> Name: shared-as</td></tr><tr><th id="L1590"><a href="#L1590">1590</a></th><td></td></tr><tr><th id="L1591"><a href="#L1591">1591</a></th><td> Namespace: http://calendarserver.org/ns/</td></tr><tr><th id="L1592"><a href="#L1592">1592</a></th><td></td></tr><tr><th id="L1593"><a href="#L1593">1593</a></th><td> Purpose: Identifies a shared calendar.</td></tr><tr><th id="L1594"><a href="#L1594">1594</a></th><td></td></tr><tr><th id="L1595"><a href="#L1595">1595</a></th><td> Description: Returned by the server for a POST request by a sharee</td></tr><tr><th id="L1596"><a href="#L1596">1596</a></th><td> accepting a shared calendar invite. The DAV:href element</td></tr><tr><th id="L1597"><a href="#L1597">1597</a></th><td> specifies the URI of the calendar created by the acceptance.</td></tr><tr><th id="L1598"><a href="#L1598">1598</a></th><td></td></tr><tr><th id="L1599"><a href="#L1599">1599</a></th><td> Definition:</td></tr><tr><th id="L1600"><a href="#L1600">1600</a></th><td></td></tr><tr><th id="L1601"><a href="#L1601">1601</a></th><td> <!ELEMENT shared-as (DAV:href)></td></tr><tr><th id="L1602"><a href="#L1602">1602</a></th><td></td></tr><tr><th id="L1603"><a href="#L1603">1603</a></th><td></td></tr><tr><th id="L1604"><a href="#L1604">1604</a></th><td>7. Security Considerations</td></tr><tr><th id="L1605"><a href="#L1605">1605</a></th><td></td></tr><tr><th id="L1606"><a href="#L1606">1606</a></th><td> Per-user WebDAV properties and iCalendar data MUST only be accessible</td></tr><tr><th id="L1607"><a href="#L1607">1607</a></th><td> by the user that created them.</td></tr><tr><th id="L1608"><a href="#L1608">1608</a></th><td></td></tr><tr><th id="L1609"><a href="#L1609">1609</a></th><td> Alarms set by the sharer SHOULD NOT be propagated to sharees by</td></tr><tr><th id="L1610"><a href="#L1610">1610</a></th><td> default. Clients SHOULD NOT automatically enable triggering of</td></tr><tr><th id="L1611"><a href="#L1611">1611</a></th><td> alarms on shared calendars that have just been accepted without</td></tr><tr><th id="L1612"><a href="#L1612">1612</a></th><td> confirmation by the user.</td></tr><tr><th id="L1613"><a href="#L1613">1613</a></th><td></td></tr><tr><th id="L1614"><a href="#L1614">1614</a></th><td> TBD</td></tr><tr><th id="L1615"><a href="#L1615">1615</a></th><td></td></tr><tr><th id="L1616"><a href="#L1616">1616</a></th><td></td></tr><tr><th id="L1617"><a href="#L1617">1617</a></th><td>8. IANA Considerations</td></tr><tr><th id="L1618"><a href="#L1618">1618</a></th><td></td></tr><tr><th id="L1619"><a href="#L1619">1619</a></th><td> This document does not require any actions on the part of IANA.</td></tr><tr><th id="L1620"><a href="#L1620">1620</a></th><td></td></tr><tr><th id="L1621"><a href="#L1621">1621</a></th><td></td></tr><tr><th id="L1622"><a href="#L1622">1622</a></th><td></td></tr><tr><th id="L1623"><a href="#L1623">1623</a></th><td>Daboo & York [Page 29]</td></tr><tr><th id="L1624"><a href="#L1624">1624</a></th><td></td></tr><tr><th id="L1625"><a href="#L1625">1625</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1626"><a href="#L1626">1626</a></th><td></td></tr><tr><th id="L1627"><a href="#L1627">1627</a></th><td></td></tr><tr><th id="L1628"><a href="#L1628">1628</a></th><td>9. Acknowledgments</td></tr><tr><th id="L1629"><a href="#L1629">1629</a></th><td></td></tr><tr><th id="L1630"><a href="#L1630">1630</a></th><td> This specification is the result of discussions between the Apple</td></tr><tr><th id="L1631"><a href="#L1631">1631</a></th><td> calendar server and client teams.</td></tr><tr><th id="L1632"><a href="#L1632">1632</a></th><td></td></tr><tr><th id="L1633"><a href="#L1633">1633</a></th><td></td></tr><tr><th id="L1634"><a href="#L1634">1634</a></th><td>10. Normative References</td></tr><tr><th id="L1635"><a href="#L1635">1635</a></th><td></td></tr><tr><th id="L1636"><a href="#L1636">1636</a></th><td> [I-D.desruisseaux-caldav-sched]</td></tr><tr><th id="L1637"><a href="#L1637">1637</a></th><td> Daboo, C. and B. Desruisseaux, "CalDAV Scheduling</td></tr><tr><th id="L1638"><a href="#L1638">1638</a></th><td> Extensions to WebDAV", draft-desruisseaux-caldav-sched-08</td></tr><tr><th id="L1639"><a href="#L1639">1639</a></th><td> (work in progress), August 2009.</td></tr><tr><th id="L1640"><a href="#L1640">1640</a></th><td></td></tr><tr><th id="L1641"><a href="#L1641">1641</a></th><td> [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate</td></tr><tr><th id="L1642"><a href="#L1642">1642</a></th><td> Requirement Levels", BCP 14, RFC 2119, March 1997.</td></tr><tr><th id="L1643"><a href="#L1643">1643</a></th><td></td></tr><tr><th id="L1644"><a href="#L1644">1644</a></th><td> [RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web</td></tr><tr><th id="L1645"><a href="#L1645">1645</a></th><td> Distributed Authoring and Versioning (WebDAV)</td></tr><tr><th id="L1646"><a href="#L1646">1646</a></th><td> Access Control Protocol", RFC 3744, May 2004.</td></tr><tr><th id="L1647"><a href="#L1647">1647</a></th><td></td></tr><tr><th id="L1648"><a href="#L1648">1648</a></th><td> [RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,</td></tr><tr><th id="L1649"><a href="#L1649">1649</a></th><td> "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,</td></tr><tr><th id="L1650"><a href="#L1650">1650</a></th><td> March 2007.</td></tr><tr><th id="L1651"><a href="#L1651">1651</a></th><td></td></tr><tr><th id="L1652"><a href="#L1652">1652</a></th><td> [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed</td></tr><tr><th id="L1653"><a href="#L1653">1653</a></th><td> Authoring and Versioning (WebDAV)", RFC 4918, June 2007.</td></tr><tr><th id="L1654"><a href="#L1654">1654</a></th><td></td></tr><tr><th id="L1655"><a href="#L1655">1655</a></th><td> [RFC5689] Daboo, C., "Extended MKCOL for Web Distributed Authoring</td></tr><tr><th id="L1656"><a href="#L1656">1656</a></th><td> and Versioning (WebDAV)", RFC 5689, September 2009.</td></tr><tr><th id="L1657"><a href="#L1657">1657</a></th><td></td></tr><tr><th id="L1658"><a href="#L1658">1658</a></th><td></td></tr><tr><th id="L1659"><a href="#L1659">1659</a></th><td>Appendix A. Change History</td></tr><tr><th id="L1660"><a href="#L1660">1660</a></th><td></td></tr><tr><th id="L1661"><a href="#L1661">1661</a></th><td> Changes in -02:</td></tr><tr><th id="L1662"><a href="#L1662">1662</a></th><td></td></tr><tr><th id="L1663"><a href="#L1663">1663</a></th><td> 1. Removed read-write-shared access mode - now a server that does</td></tr><tr><th id="L1664"><a href="#L1664">1664</a></th><td> not support shared scheduling should advertise that via a DAV</td></tr><tr><th id="L1665"><a href="#L1665">1665</a></th><td> header</td></tr><tr><th id="L1666"><a href="#L1666">1666</a></th><td></td></tr><tr><th id="L1667"><a href="#L1667">1667</a></th><td></td></tr><tr><th id="L1668"><a href="#L1668">1668</a></th><td>Appendix B. Change History</td></tr><tr><th id="L1669"><a href="#L1669">1669</a></th><td></td></tr><tr><th id="L1670"><a href="#L1670">1670</a></th><td> Changes in -01:</td></tr><tr><th id="L1671"><a href="#L1671">1671</a></th><td></td></tr><tr><th id="L1672"><a href="#L1672">1672</a></th><td> 1. Added CS:shared-url property</td></tr><tr><th id="L1673"><a href="#L1673">1673</a></th><td></td></tr><tr><th id="L1674"><a href="#L1674">1674</a></th><td> 2. Clarified that notifications are only required to be sent when</td></tr><tr><th id="L1675"><a href="#L1675">1675</a></th><td> sharee status is changed</td></tr><tr><th id="L1676"><a href="#L1676">1676</a></th><td></td></tr><tr><th id="L1677"><a href="#L1677">1677</a></th><td></td></tr><tr><th id="L1678"><a href="#L1678">1678</a></th><td></td></tr><tr><th id="L1679"><a href="#L1679">1679</a></th><td>Daboo & York [Page 30]</td></tr><tr><th id="L1680"><a href="#L1680">1680</a></th><td></td></tr><tr><th id="L1681"><a href="#L1681">1681</a></th><td> CalDAV Sharing and Publishing October 2010</td></tr><tr><th id="L1682"><a href="#L1682">1682</a></th><td></td></tr><tr><th id="L1683"><a href="#L1683">1683</a></th><td></td></tr><tr><th id="L1684"><a href="#L1684">1684</a></th><td>Authors' Addresses</td></tr><tr><th id="L1685"><a href="#L1685">1685</a></th><td></td></tr><tr><th id="L1686"><a href="#L1686">1686</a></th><td> Cyrus Daboo</td></tr><tr><th id="L1687"><a href="#L1687">1687</a></th><td> Apple Inc.</td></tr><tr><th id="L1688"><a href="#L1688">1688</a></th><td> 1 Infinite Loop</td></tr><tr><th id="L1689"><a href="#L1689">1689</a></th><td> Cupertino, CA 95014</td></tr><tr><th id="L1690"><a href="#L1690">1690</a></th><td> USA</td></tr><tr><th id="L1691"><a href="#L1691">1691</a></th><td></td></tr><tr><th id="L1692"><a href="#L1692">1692</a></th><td> Email: cyrus@daboo.name</td></tr><tr><th id="L1693"><a href="#L1693">1693</a></th><td> URI: http://www.apple.com/</td></tr><tr><th id="L1694"><a href="#L1694">1694</a></th><td></td></tr><tr><th id="L1695"><a href="#L1695">1695</a></th><td></td></tr><tr><th id="L1696"><a href="#L1696">1696</a></th><td> Eric York</td></tr><tr><th id="L1697"><a href="#L1697">1697</a></th><td> Apple Inc.</td></tr><tr><th id="L1698"><a href="#L1698">1698</a></th><td> 1 Infinite Loop</td></tr><tr><th id="L1699"><a href="#L1699">1699</a></th><td> Cupertino, CA 95014</td></tr><tr><th id="L1700"><a href="#L1700">1700</a></th><td> USA</td></tr><tr><th id="L1701"><a href="#L1701">1701</a></th><td></td></tr><tr><th id="L1702"><a href="#L1702">1702</a></th><td> Email:</td></tr><tr><th id="L1703"><a href="#L1703">1703</a></th><td> URI: http://www.apple.com/</td></tr><tr><th id="L1704"><a href="#L1704">1704</a></th><td></td></tr><tr><th id="L1705"><a href="#L1705">1705</a></th><td></td></tr><tr><th id="L1706"><a href="#L1706">1706</a></th><td></td></tr><tr><th id="L1707"><a href="#L1707">1707</a></th><td></td></tr><tr><th id="L1708"><a href="#L1708">1708</a></th><td></td></tr><tr><th id="L1709"><a href="#L1709">1709</a></th><td></td></tr><tr><th id="L1710"><a href="#L1710">1710</a></th><td></td></tr><tr><th id="L1711"><a href="#L1711">1711</a></th><td></td></tr><tr><th id="L1712"><a href="#L1712">1712</a></th><td></td></tr><tr><th id="L1713"><a href="#L1713">1713</a></th><td></td></tr><tr><th id="L1714"><a href="#L1714">1714</a></th><td></td></tr><tr><th id="L1715"><a href="#L1715">1715</a></th><td></td></tr><tr><th id="L1716"><a href="#L1716">1716</a></th><td></td></tr><tr><th id="L1717"><a href="#L1717">1717</a></th><td></td></tr><tr><th id="L1718"><a href="#L1718">1718</a></th><td></td></tr><tr><th id="L1719"><a href="#L1719">1719</a></th><td></td></tr><tr><th id="L1720"><a href="#L1720">1720</a></th><td></td></tr><tr><th id="L1721"><a href="#L1721">1721</a></th><td></td></tr><tr><th id="L1722"><a href="#L1722">1722</a></th><td></td></tr><tr><th id="L1723"><a href="#L1723">1723</a></th><td></td></tr><tr><th id="L1724"><a href="#L1724">1724</a></th><td></td></tr><tr><th id="L1725"><a href="#L1725">1725</a></th><td></td></tr><tr><th id="L1726"><a href="#L1726">1726</a></th><td></td></tr><tr><th id="L1727"><a href="#L1727">1727</a></th><td></td></tr><tr><th id="L1728"><a href="#L1728">1728</a></th><td></td></tr><tr><th id="L1729"><a href="#L1729">1729</a></th><td></td></tr><tr><th id="L1730"><a href="#L1730">1730</a></th><td></td></tr><tr><th id="L1731"><a href="#L1731">1731</a></th><td></td></tr><tr><th id="L1732"><a href="#L1732">1732</a></th><td></td></tr><tr><th id="L1733"><a href="#L1733">1733</a></th><td></td></tr><tr><th id="L1734"><a href="#L1734">1734</a></th><td></td></tr><tr><th id="L1735"><a href="#L1735">1735</a></th><td>Daboo & York [Page 31]</td></tr><tr><th id="L1736"><a href="#L1736">1736</a></th><td></td></tr></tbody></table>
+ </div>
+ <div id="help">
+ <strong>Note:</strong> See <a href="/wiki/TracBrowser">TracBrowser</a>
+ for help on using the browser.
+ </div>
+ <div id="anydiff">
+ <form action="/diff" method="get">
+ <div class="buttons">
+ <input type="hidden" name="new_path" value="/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" />
+ <input type="hidden" name="old_path" value="/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt" />
+ <input type="hidden" name="new_rev" />
+ <input type="hidden" name="old_rev" />
+ <input type="submit" value="View changes..." title="Select paths and revs for Diff" />
+ </div>
+ </form>
+ </div>
+ </div>
+ <div id="altlinks">
+ <h3>Download in other formats:</h3>
+ <ul>
+ <li class="first">
+ <a rel="nofollow" href="/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt?format=txt">Plain Text</a>
+ </li><li class="last">
+ <a rel="nofollow" href="/export/9403/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt">Original Format</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <script type="text/javascript" src="https://static2.macosforge.org/static/js/jquery.js"></script>
+ <script type="text/javascript" src="https://static2.macosforge.org/static/js/main.js"></script>
+ </div>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) M. Nottingham
+Request for Comments: 5785 E. Hammer-Lahav
+Updates: 2616, 2818 April 2010
+Category: Standards Track
+ISSN: 2070-1721
+
+
+ Defining Well-Known Uniform Resource Identifiers (URIs)
+
+Abstract
+
+ This memo defines a path prefix for "well-known locations",
+ "/.well-known/", in selected Uniform Resource Identifier (URI)
+ schemes.
+
+Status of This Memo
+
+ This is an Internet Standards Track document.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Further information on
+ Internet Standards is available in Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc5785.
+
+Copyright Notice
+
+ Copyright (c) 2010 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 1]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+Table of Contents
+
+ 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
+ 1.1. Appropriate Use of Well-Known URIs . . . . . . . . . . . . 3
+ 2. Notational Conventions . . . . . . . . . . . . . . . . . . . . 3
+ 3. Well-Known URIs . . . . . . . . . . . . . . . . . . . . . . . . 3
+ 4. Security Considerations . . . . . . . . . . . . . . . . . . . . 4
+ 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 4
+ 5.1. The Well-Known URI Registry . . . . . . . . . . . . . . . . 4
+ 5.1.1. Registration Template . . . . . . . . . . . . . . . . . 5
+ 6. References . . . . . . . . . . . . . . . . . . . . . . . . . . 5
+ 6.1. Normative References . . . . . . . . . . . . . . . . . . . 5
+ 6.2. Informative References . . . . . . . . . . . . . . . . . . 5
+ Appendix A. Acknowledgements . . . . . . . . . . . . . . . . . . . 7
+ Appendix B. Frequently Asked Questions . . . . . . . . . . . . . . 7
+
+1. Introduction
+
+ It is increasingly common for Web-based protocols to require the
+ discovery of policy or other information about a host ("site-wide
+ metadata") before making a request. For example, the Robots
+ Exclusion Protocol <http://www.robotstxt.org/> specifies a way for
+ automated processes to obtain permission to access resources;
+ likewise, the Platform for Privacy Preferences [W3C.REC-P3P-20020416]
+ tells user-agents how to discover privacy policy beforehand.
+
+ While there are several ways to access per-resource metadata (e.g.,
+ HTTP headers, WebDAV's PROPFIND [RFC4918]), the perceived overhead
+ (either in terms of client-perceived latency and/or deployment
+ difficulties) associated with them often precludes their use in these
+ scenarios.
+
+ When this happens, it is common to designate a "well-known location"
+ for such data, so that it can be easily located. However, this
+ approach has the drawback of risking collisions, both with other such
+ designated "well-known locations" and with pre-existing resources.
+
+ To address this, this memo defines a path prefix in HTTP(S) URIs for
+ these "well-known locations", "/.well-known/". Future specifications
+ that need to define a resource for such site-wide metadata can
+ register their use to avoid collisions and minimise impingement upon
+ sites' URI space.
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 2]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+1.1. Appropriate Use of Well-Known URIs
+
+ There are a number of possible ways that applications could use Well-
+ known URIs. However, in keeping with the Architecture of the World-
+ Wide Web [W3C.REC-webarch-20041215], well-known URIs are not intended
+ for general information retrieval or establishment of large URI
+ namespaces on the Web. Rather, they are designed to facilitate
+ discovery of information on a site when it isn't practical to use
+ other mechanisms; for example, when discovering policy that needs to
+ be evaluated before a resource is accessed, or when using multiple
+ round-trips is judged detrimental to performance.
+
+ As such, the well-known URI space was created with the expectation
+ that it will be used to make site-wide policy information and other
+ metadata available directly (if sufficiently concise), or provide
+ references to other URIs that provide such metadata.
+
+2. Notational Conventions
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [RFC2119].
+
+3. Well-Known URIs
+
+ A well-known URI is a URI [RFC3986] whose path component begins with
+ the characters "/.well-known/", and whose scheme is "HTTP", "HTTPS",
+ or another scheme that has explicitly been specified to use well-
+ known URIs.
+
+ Applications that wish to mint new well-known URIs MUST register
+ them, following the procedures in Section 5.1.
+
+ For example, if an application registers the name 'example', the
+ corresponding well-known URI on 'http://www.example.com/' would be
+ 'http://www.example.com/.well-known/example'.
+
+ Registered names MUST conform to the segment-nz production in
+ [RFC3986].
+
+ Note that this specification defines neither how to determine the
+ authority to use for a particular context, nor the scope of the
+ metadata discovered by dereferencing the well-known URI; both should
+ be defined by the application itself.
+
+ Typically, a registration will reference a specification that defines
+ the format and associated media type to be obtained by dereferencing
+ the well-known URI.
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 3]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+ It MAY also contain additional information, such as the syntax of
+ additional path components, query strings and/or fragment identifiers
+ to be appended to the well-known URI, or protocol-specific details
+ (e.g., HTTP [RFC2616] method handling).
+
+ Note that this specification does not define a format or media-type
+ for the resource located at "/.well-known/" and clients should not
+ expect a resource to exist at that location.
+
+4. Security Considerations
+
+ This memo does not specify the scope of applicability of metadata or
+ policy obtained from a well-known URI, and does not specify how to
+ discover a well-known URI for a particular application. Individual
+ applications using this mechanism must define both aspects.
+
+ Applications minting new well-known URIs, as well as administrators
+ deploying them, will need to consider several security-related
+ issues, including (but not limited to) exposure of sensitive data,
+ denial-of-service attacks (in addition to normal load issues), server
+ and client authentication, vulnerability to DNS rebinding attacks,
+ and attacks where limited access to a server grants the ability to
+ affect how well-known URIs are served.
+
+5. IANA Considerations
+
+5.1. The Well-Known URI Registry
+
+ This document establishes the well-known URI registry.
+
+ Well-known URIs are registered on the advice of one or more
+ Designated Experts (appointed by the IESG or their delegate), with a
+ Specification Required (using terminology from [RFC5226]). However,
+ to allow for the allocation of values prior to publication, the
+ Designated Expert(s) may approve registration once they are satisfied
+ that such a specification will be published.
+
+ Registration requests should be sent to the
+ wellknown-uri-review@ietf.org mailing list for review and comment,
+ with an appropriate subject (e.g., "Request for well-known URI:
+ example").
+
+ Before a period of 14 days has passed, the Designated Expert(s) will
+ either approve or deny the registration request, communicating this
+ decision both to the review list and to IANA. Denials should include
+ an explanation and, if applicable, suggestions as to how to make the
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 4]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+ request successful. Registration requests that are undetermined for
+ a period longer than 21 days can be brought to the IESG's attention
+ (using the iesg@iesg.org mailing list) for resolution.
+
+5.1.1. Registration Template
+
+ URI suffix: The name requested for the well-known URI, relative to
+ "/.well-known/"; e.g., "example".
+
+ Change controller: For Standards-Track RFCs, state "IETF". For
+ others, give the name of the responsible party. Other details
+ (e.g., postal address, e-mail address, home page URI) may also be
+ included.
+
+ Specification document(s): Reference to the document that specifies
+ the field, preferably including a URI that can be used to retrieve
+ a copy of the document. An indication of the relevant sections
+ may also be included, but is not required.
+
+ Related information: Optionally, citations to additional documents
+ containing further relevant information.
+
+6. References
+
+6.1. Normative References
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
+ Resource Identifier (URI): Generic Syntax", STD 66,
+ RFC 3986, January 2005.
+
+ [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
+ IANA Considerations Section in RFCs", BCP 26, RFC 5226,
+ May 2008.
+
+6.2. Informative References
+
+ [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter,
+ L., Leach, P., and T. Berners-Lee, "Hypertext Transfer
+ Protocol -- HTTP/1.1", RFC 2616, June 1999.
+
+ [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
+ Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 5]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+ [W3C.REC-P3P-20020416]
+ Marchiori, M., "The Platform for Privacy Preferences 1.0
+ (P3P1.0) Specification", World Wide Web Consortium
+ Recommendation REC-P3P-20020416, April 2002,
+ <http://www.w3.org/TR/2002/ REC-P3P-20020416>.
+
+ [W3C.REC-webarch-20041215]
+ Jacobs, I. and N. Walsh, "Architecture of the World Wide
+ Web, Volume One", World Wide Web Consortium
+ Recommendation REC- webarch-20041215, December 2004,
+ <http:// www.w3.org/TR/2004/REC-webarch-20041215>.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 6]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+Appendix A. Acknowledgements
+
+ We would like to acknowledge the contributions of everyone who
+ provided feedback and use cases for this document; in particular,
+ Phil Archer, Dirk Balfanz, Adam Barth, Tim Bray, Brian Eaton, Brad
+ Fitzpatrick, Joe Gregorio, Paul Hoffman, Barry Leiba, Ashok Malhotra,
+ Breno de Medeiros, John Panzer, and Drummond Reed. However, they are
+ not responsible for errors and omissions.
+
+Appendix B. Frequently Asked Questions
+
+ 1. Aren't well-known locations bad for the Web?
+
+ They are, but for various reasons -- both technical and social --
+ they are commonly used and their use is increasing. This memo
+ defines a "sandbox" for them, to reduce the risks of collision and
+ to minimise the impact upon pre-existing URIs on sites.
+
+ 2. Why /.well-known?
+
+ It's short, descriptive, and according to search indices, not
+ widely used.
+
+ 3. What impact does this have on existing mechanisms, such as P3P and
+ robots.txt?
+
+ None, until they choose to use this mechanism.
+
+ 4. Why aren't per-directory well-known locations defined?
+
+ Allowing every URI path segment to have a well-known location
+ (e.g., "/images/.well-known/") would increase the risks of
+ colliding with a pre-existing URI on a site, and generally these
+ solutions are found not to scale well, because they're too
+ "chatty".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 7]
+\f
+RFC 5785 Defining Well-Known URIs April 2010
+
+
+Authors' Addresses
+
+ Mark Nottingham
+
+ EMail: mnot@mnot.net
+ URI: http://www.mnot.net/
+
+
+ Eran Hammer-Lahav
+
+ EMail: eran@hueniverse.com
+ URI: http://hueniverse.com/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nottingham & Hammer-Lahav Standards Track [Page 8]
+\f
# settings as well.
<VirtualHost *:*>
- # Don't forget to change the server name
- # ServerName dav.example.org
+ # Don't forget to change the server name
+ # ServerName dav.example.org
- # The DocumentRoot is also required
+ # The DocumentRoot is also required
# DocumentRoot /home/sabredav/
- RewriteEngine On
- # This makes every request go to server.php
- RewriteRule ^/(.*)$ /server.php [L]
+ RewriteEngine On
+ # This makes every request go to server.php
+ RewriteRule ^/(.*)$ /server.php [L]
- # Output buffering needs to be off, to prevent high memory usage
- php_flag output_buffering off
+ # Output buffering needs to be off, to prevent high memory usage
+ php_flag output_buffering off
- # This is also to prevent high memory usage
- php_flag always_populate_raw_post_data off
+ # This is also to prevent high memory usage
+ php_flag always_populate_raw_post_data off
- # This is almost a given, but magic quotes is *still* on on some
- # linux distributions
- php_flag magic_quotes_gpc off
+ # This is almost a given, but magic quotes is *still* on on some
+ # linux distributions
+ php_flag magic_quotes_gpc off
- # SabreDAV is not compatible with mbstring function overloading
- php_flag mbstring.func_overload off
+ # SabreDAV is not compatible with mbstring function overloading
+ php_flag mbstring.func_overload off
</VirtualHost *:*>
# This configuration assumes CGI or FastCGI is used.
<VirtualHost *:*>
- # Don't forget to change the server name
- # ServerName dav.example.org
+ # Don't forget to change the server name
+ # ServerName dav.example.org
- # The DocumentRoot is also required
+ # The DocumentRoot is also required
# DocumentRoot /home/sabredav/
- # This makes every request go to server.php. This also makes sure
- # the Authentication information is available. If your server script is
- # not called server.php, be sure to change it.
- RewriteEngine On
- RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ # This makes every request go to server.php. This also makes sure
+ # the Authentication information is available. If your server script is
+ # not called server.php, be sure to change it.
+ RewriteEngine On
+ RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</VirtualHost *:*>
/**
* 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.
}
- /**
- * 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.
*
--- /dev/null
+<?php
+
+/**
+ * Every CalDAV backend must at least implement this interface.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Backend_BackendInterface {
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri, which the basename of the uri with which the calendar is
+ * accessed.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ public function getCalendarsForUser($principalUri);
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @return void
+ */
+ public function createCalendar($principalUri,$calendarUri,array $properties);
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The mutations array uses the propertyName in clark-notation as key,
+ * and the array value for the property value. In the case a property
+ * should be deleted, the property value will be null.
+ *
+ * This method must be atomic. If one property cannot be changed, the
+ * entire operation must fail.
+ *
+ * If the operation was successful, true can be returned.
+ * If the operation failed, false can be returned.
+ *
+ * Deletion of a non-existent property is always successful.
+ *
+ * Lastly, it is optional to return detailed information about any
+ * failures. In this case an array should be returned with the following
+ * structure:
+ *
+ * array(
+ * 403 => array(
+ * '{DAV:}displayname' => null,
+ * ),
+ * 424 => array(
+ * '{DAV:}owner' => null,
+ * )
+ * )
+ *
+ * In this example it was forbidden to update {DAV:}displayname.
+ * (403 Forbidden), which in turn also caused {DAV:}owner to fail
+ * (424 Failed Dependency) because the request needs to be atomic.
+ *
+ * @param mixed $calendarId
+ * @param array $mutations
+ * @return bool|array
+ */
+ public function updateCalendar($calendarId, array $mutations);
+
+ /**
+ * Delete a calendar and all it's objects
+ *
+ * @param mixed $calendarId
+ * @return void
+ */
+ public function deleteCalendar($calendarId);
+
+ /**
+ * Returns all calendar objects within a calendar.
+ *
+ * Every item contains an array with the following keys:
+ * * id - unique identifier which will be used for subsequent updates
+ * * calendardata - The iCalendar-compatible calendar data
+ * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
+ * * lastmodified - a timestamp of the last modification time
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+ * ' "abcdef"')
+ * * calendarid - The calendarid as it was passed to this function.
+ * * size - The size of the calendar objects, in bytes.
+ *
+ * Note that the etag is optional, but it's highly encouraged to return for
+ * speed reasons.
+ *
+ * The calendardata is also optional. If it's not returned
+ * 'getCalendarObject' will be called later, which *is* expected to return
+ * calendardata.
+ *
+ * If neither etag or size are specified, the calendardata will be
+ * used/fetched to determine these numbers. If both are specified the
+ * amount of times this is needed is reduced by a great degree.
+ *
+ * @param mixed $calendarId
+ * @return array
+ */
+ public function getCalendarObjects($calendarId);
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return array
+ */
+ public function getCalendarObject($calendarId,$objectUri);
+
+ /**
+ * Creates a new calendar object.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ public function createCalendarObject($calendarId,$objectUri,$calendarData);
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ public function updateCalendarObject($calendarId,$objectUri,$calendarData);
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return void
+ */
+ public function deleteCalendarObject($calendarId,$objectUri);
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by Sabre_CalDAV_CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on either VEVENT or VTODO.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * @param mixed $calendarId
+ * @param array $filters
+ * @return array
+ */
+ public function calendarQuery($calendarId, array $filters);
+
+}
--- /dev/null
+<?php
+
+/**
+ * Adds caldav notification support to a backend.
+ *
+ * Notifications are defined at:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
+ *
+ * These notifications are basically a list of server-generated notifications
+ * displayed to the user. Users can dismiss notifications by deleting them.
+ *
+ * The primary usecase is to allow for calendar-sharing.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Backend_NotificationSupport extends Sabre_CalDAV_Backend_BackendInterface {
+
+ /**
+ * Returns a list of notifications for a given principal url.
+ *
+ * The returned array should only consist of implementations of
+ * Sabre_CalDAV_Notifications_INotificationType.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ public function getNotificationsForPrincipal($principalUri);
+
+ /**
+ * This deletes a specific notifcation.
+ *
+ * This may be called by a client once it deems a notification handled.
+ *
+ * @param string $principalUri
+ * @param Sabre_CalDAV_Notifications_INotificationType $notification
+ * @return void
+ */
+ public function deleteNotification($principalUri, Sabre_CalDAV_Notifications_INotificationType $notification);
+
+}
/**
* CalDAV backend
*
- * @var Sabre_CalDAV_Backend_Abstract
+ * @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
- * @param Sabre_CalDAV_Backend_Abstract $caldavBackend
+ * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param array $calendarInfo
*/
- public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) {
+ public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->principalBackend = $principalBackend;
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
*/
/**
* 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;
// 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.
/**
* CalDAV backend
*
- * @var Sabre_CalDAV_Backend_Abstract
+ * @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
*
*
* @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;
--- /dev/null
+<?php
+
+/**
+ * Sabre_CalDAV_Exception_InvalidComponentType
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Exception_InvalidComponentType extends Sabre_DAV_Exception_Forbidden {
+
+ /**
+ * Adds in extra information in the xml response.
+ *
+ * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
+ *
+ * @param Sabre_DAV_Server $server
+ * @param DOMElement $errorNode
+ * @return void
+ */
+ public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
+
+ $doc = $errorNode->ownerDocument;
+
+ $np = $doc->createElementNS(Sabre_CalDAV_Plugin::NS_CALDAV,'cal:supported-calendar-component');
+ $errorNode->appendChild($np);
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre_CalDAV_Notifications_INode nodes as
+ * its children.
+ *
+ * @package Sabre
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Collection extends Sabre_DAV_Collection implements Sabre_CalDAV_Notifications_ICollection {
+
+ /**
+ * The notification backend
+ *
+ * @var Sabre_CalDAV_Backend_NotificationSupport
+ */
+ protected $caldavBackend;
+
+ /**
+ * Principal uri
+ *
+ * @var string
+ */
+ protected $principalUri;
+
+ /**
+ * Constructor
+ *
+ * @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
+ * @param string $principalUri
+ */
+ public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, $principalUri) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->principalUri = $principalUri;
+
+ }
+
+ /**
+ * Returns all notifications for a principal
+ *
+ * @return array
+ */
+ public function getChildren() {
+
+ $children = array();
+ $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
+
+ foreach($notifications as $notification) {
+
+ $children[] = new Sabre_CalDAV_Notifications_Node(
+ $this->caldavBackend,
+ $notification
+ );
+ }
+
+ return $children;
+
+ }
+
+ /**
+ * Returns the name of this object
+ *
+ * @return string
+ */
+ public function getName() {
+
+ return 'notifications';
+
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre_CalDAV_Notifications_INode nodes as
+ * its children.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_ICollection extends Sabre_DAV_ICollection {
+
+
+}
--- /dev/null
+<?php
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method
+ * MUST return an xml document that matches the requirements of the
+ * 'caldav-notifications.txt' spec.
+ *
+ * For a complete example, check out the Notification class, which contains
+ * some helper functions.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_INode {
+
+ /**
+ * This method must return an xml element, using the
+ * Sabre_CalDAV_Notifications_INotificationType classes.
+ *
+ * @return Sabre_DAVNotification_INotificationType
+ */
+ function getNotificationType();
+
+}
--- /dev/null
+<?php
+
+/**
+ * This interface reflects a single notification type.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_CalDAV_Notifications_INotificationType extends Sabre_DAV_PropertyInterface {
+
+ /**
+ * Serializes the notification as a single property.
+ *
+ * You should usually just encode the single top-level element of the
+ * notification.
+ *
+ * @param Sabre_DAV_Server $server
+ * @param DOMElement $node
+ * @return void
+ */
+ function serialize(Sabre_DAV_Server $server, \DOMElement $node);
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Sabre_DAV_Server $server
+ * @param DOMElement $node
+ * @return void
+ */
+ function serializeBody(Sabre_DAV_Server $server, \DOMElement $node);
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ function getId();
+
+}
--- /dev/null
+<?php
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method
+ * MUST return an xml document that matches the requirements of the
+ * 'caldav-notifications.txt' spec.
+
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Node extends Sabre_DAV_Node implements Sabre_CalDAV_Notifications_INode {
+
+ /**
+ * The notification backend
+ *
+ * @var Sabre_CalDAV_Backend_NotificationSupport
+ */
+ protected $caldavBackend;
+
+ /**
+ * The actual notification
+ *
+ * @var Sabre_CalDAV_Notifications_INotificationType
+ */
+ protected $notification;
+
+ /**
+ * Constructor
+ *
+ * @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
+ * @param Sabre_CalDAV_Notifications_INotificationType $notification
+ */
+ public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, Sabre_CalDAV_Notifications_INotificationType $notification) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->notification = $notification;
+
+ }
+
+ /**
+ * Returns the path name for this notification
+ *
+ * @return id
+ */
+ public function getName() {
+
+ return $this->notification->getId();
+
+ }
+
+ /**
+ * This method must return an xml element, using the
+ * Sabre_CalDAV_Notifications_INotificationType classes.
+ *
+ * @return Sabre_DAVNotification_INotificationType
+ */
+ public function getNotificationType() {
+
+ return $this->notification;
+
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * SystemStatus notification
+ *
+ * This notification can be used to indicate to the user that the system is
+ * down.
+ *
+ * @package Sabre
+ * @subpackage CalDAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Notifications_Notification_SystemStatus extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
+
+ const TYPE_LOW = 1;
+ const TYPE_MEDIUM = 2;
+ const TYPE_HIGH = 3;
+
+ /**
+ * A unique id
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * The type of alert. This should be one of the TYPE_ constants.
+ *
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * A human-readable description of the problem.
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * A url to a website with more information for the user.
+ *
+ * @var string
+ */
+ protected $href;
+
+ /**
+ * Creates the notification.
+ *
+ * Some kind of unique id should be provided. This is used to generate a
+ * url.
+ *
+ * @param string $id
+ * @param int $type
+ * @param string $description
+ * @param string $href
+ */
+ public function __construct($id, $type = self::TYPE_HIGH, $description = null, $href = null) {
+
+ $this->id = $id;
+ $this->type = $type;
+ $this->description = $description;
+ $this->href = $href;
+
+ }
+
+ /**
+ * Serializes the notification as a single property.
+ *
+ * You should usually just encode the single top-level element of the
+ * notification.
+ *
+ * @param Sabre_DAV_Server $server
+ * @param DOMElement $node
+ * @return void
+ */
+ public function serialize(Sabre_DAV_Server $server, \DOMElement $node) {
+
+ switch($this->type) {
+ case self::TYPE_LOW :
+ $type = 'low';
+ break;
+ case self::TYPE_MEDIUM :
+ $type = 'medium';
+ break;
+ default :
+ case self::TYPE_HIGH :
+ $type = 'high';
+ break;
+ }
+
+ $prop = $node->ownerDocument->createElement('cs:systemstatus');
+ $prop->setAttribute('type', $type);
+
+ $node->appendChild($prop);
+
+ }
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Sabre_DAV_Server $server
+ * @param DOMElement $node
+ * @return void
+ */
+ public function serializeBody(Sabre_DAV_Server $server, \DOMElement $node) {
+
+ switch($this->type) {
+ case self::TYPE_LOW :
+ $type = 'low';
+ break;
+ case self::TYPE_MEDIUM :
+ $type = 'medium';
+ break;
+ default :
+ case self::TYPE_HIGH :
+ $type = 'high';
+ break;
+ }
+
+ $prop = $node->ownerDocument->createElement('cs:systemstatus');
+ $prop->setAttribute('type', $type);
+
+ if ($this->description) {
+ $text = $node->ownerDocument->createTextNode($this->description);
+ $desc = $node->ownerDocument->createElement('cs:description');
+ $desc->appendChild($text);
+ $prop->appendChild($desc);
+ }
+ if ($this->href) {
+ $text = $node->ownerDocument->createTextNode($this->href);
+ $href = $node->ownerDocument->createElement('d:href');
+ $href->appendChild($text);
+ $prop->appendChild($href);
+ }
+
+ $node->appendChild($prop);
+
+ }
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ public function getId() {
+
+ return $this->id;
+
+ }
+
+}
$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';
$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,
// 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'
);
}
}
+ // 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'
if (!$node instanceof Sabre_CalDAV_ICalendarObject)
return;
- $this->validateICalendar($data);
+ $this->validateICalendar($data, $path);
}
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;
}
* 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)) {
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) {
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');
}
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);
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);
}
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');
}
/**
* 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();
}
/**
* CalDAV backend
*
- * @var Sabre_CalDAV_Backend_Abstract
+ * @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
* 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;
$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;
}
* 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.
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.');
+ }
+
}
$vcard = Sabre_VObject_Reader::read($vcardData);
+ if (!$filters) return true;
+
foreach($filters as $filter) {
$isDefined = isset($vcard->{$filter['name']});
$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) {
* @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);
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);
--- /dev/null
+<?php
+
+/**
+ * PropertyInterface
+ *
+ * Implement this interface to create new complex properties
+ *
+ * @package Sabre
+ * @subpackage DAV
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+interface Sabre_DAV_PropertyInterface {
+
+ public function serialize(Sabre_DAV_Server $server, DOMElement $prop);
+
+ static function unserialize(DOMElement $prop);
+
+}
+
} catch (Exception $e) {
+ try {
+ $this->broadcastEvent('exception', array($e));
+ } catch (Exception $ignore) {
+ }
$DOM = new DOMDocument('1.0','utf-8');
$DOM->formatOutput = true;
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.
$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);
}
}
);
}
- 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);
--- /dev/null
+<?php
+
+/**
+ * Time zone name translation
+ *
+ * This file translates well-known time zone names into "Olson database" time zone names.
+ *
+ * @package Sabre
+ * @subpackage VObject
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_VObject_TimeZoneUtil {
+
+ public static $map = array(
+
+ // from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
+ // snapshot taken on 2012/01/16
+
+ // windows
+ 'AUS Central Standard Time'=>'Australia/Darwin',
+ 'AUS Eastern Standard Time'=>'Australia/Sydney',
+ 'Afghanistan Standard Time'=>'Asia/Kabul',
+ 'Alaskan Standard Time'=>'America/Anchorage',
+ 'Arab Standard Time'=>'Asia/Riyadh',
+ 'Arabian Standard Time'=>'Asia/Dubai',
+ 'Arabic Standard Time'=>'Asia/Baghdad',
+ 'Argentina Standard Time'=>'America/Buenos_Aires',
+ 'Armenian Standard Time'=>'Asia/Yerevan',
+ 'Atlantic Standard Time'=>'America/Halifax',
+ 'Azerbaijan Standard Time'=>'Asia/Baku',
+ 'Azores Standard Time'=>'Atlantic/Azores',
+ 'Bangladesh Standard Time'=>'Asia/Dhaka',
+ 'Canada Central Standard Time'=>'America/Regina',
+ 'Cape Verde Standard Time'=>'Atlantic/Cape_Verde',
+ 'Caucasus Standard Time'=>'Asia/Yerevan',
+ 'Cen. Australia Standard Time'=>'Australia/Adelaide',
+ 'Central America Standard Time'=>'America/Guatemala',
+ 'Central Asia Standard Time'=>'Asia/Almaty',
+ 'Central Brazilian Standard Time'=>'America/Cuiaba',
+ 'Central Europe Standard Time'=>'Europe/Budapest',
+ 'Central European Standard Time'=>'Europe/Warsaw',
+ 'Central Pacific Standard Time'=>'Pacific/Guadalcanal',
+ 'Central Standard Time'=>'America/Chicago',
+ 'Central Standard Time (Mexico)'=>'America/Mexico_City',
+ 'China Standard Time'=>'Asia/Shanghai',
+ 'Dateline Standard Time'=>'Etc/GMT+12',
+ 'E. Africa Standard Time'=>'Africa/Nairobi',
+ 'E. Australia Standard Time'=>'Australia/Brisbane',
+ 'E. Europe Standard Time'=>'Europe/Minsk',
+ 'E. South America Standard Time'=>'America/Sao_Paulo',
+ 'Eastern Standard Time'=>'America/New_York',
+ 'Egypt Standard Time'=>'Africa/Cairo',
+ 'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg',
+ 'FLE Standard Time'=>'Europe/Kiev',
+ 'Fiji Standard Time'=>'Pacific/Fiji',
+ 'GMT Standard Time'=>'Europe/London',
+ 'GTB Standard Time'=>'Europe/Istanbul',
+ 'Georgian Standard Time'=>'Asia/Tbilisi',
+ 'Greenland Standard Time'=>'America/Godthab',
+ 'Greenwich Standard Time'=>'Atlantic/Reykjavik',
+ 'Hawaiian Standard Time'=>'Pacific/Honolulu',
+ 'India Standard Time'=>'Asia/Calcutta',
+ 'Iran Standard Time'=>'Asia/Tehran',
+ 'Israel Standard Time'=>'Asia/Jerusalem',
+ 'Jordan Standard Time'=>'Asia/Amman',
+ 'Kamchatka Standard Time'=>'Asia/Kamchatka',
+ 'Korea Standard Time'=>'Asia/Seoul',
+ 'Magadan Standard Time'=>'Asia/Magadan',
+ 'Mauritius Standard Time'=>'Indian/Mauritius',
+ 'Mexico Standard Time'=>'America/Mexico_City',
+ 'Mexico Standard Time 2'=>'America/Chihuahua',
+ 'Mid-Atlantic Standard Time'=>'Etc/GMT+2',
+ 'Middle East Standard Time'=>'Asia/Beirut',
+ 'Montevideo Standard Time'=>'America/Montevideo',
+ 'Morocco Standard Time'=>'Africa/Casablanca',
+ 'Mountain Standard Time'=>'America/Denver',
+ 'Mountain Standard Time (Mexico)'=>'America/Chihuahua',
+ 'Myanmar Standard Time'=>'Asia/Rangoon',
+ 'N. Central Asia Standard Time'=>'Asia/Novosibirsk',
+ 'Namibia Standard Time'=>'Africa/Windhoek',
+ 'Nepal Standard Time'=>'Asia/Katmandu',
+ 'New Zealand Standard Time'=>'Pacific/Auckland',
+ 'Newfoundland Standard Time'=>'America/St_Johns',
+ 'North Asia East Standard Time'=>'Asia/Irkutsk',
+ 'North Asia Standard Time'=>'Asia/Krasnoyarsk',
+ 'Pacific SA Standard Time'=>'America/Santiago',
+ 'Pacific Standard Time'=>'America/Los_Angeles',
+ 'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel',
+ 'Pakistan Standard Time'=>'Asia/Karachi',
+ 'Paraguay Standard Time'=>'America/Asuncion',
+ 'Romance Standard Time'=>'Europe/Paris',
+ 'Russian Standard Time'=>'Europe/Moscow',
+ 'SA Eastern Standard Time'=>'America/Cayenne',
+ 'SA Pacific Standard Time'=>'America/Bogota',
+ 'SA Western Standard Time'=>'America/La_Paz',
+ 'SE Asia Standard Time'=>'Asia/Bangkok',
+ 'Samoa Standard Time'=>'Pacific/Apia',
+ 'Singapore Standard Time'=>'Asia/Singapore',
+ 'South Africa Standard Time'=>'Africa/Johannesburg',
+ 'Sri Lanka Standard Time'=>'Asia/Colombo',
+ 'Syria Standard Time'=>'Asia/Damascus',
+ 'Taipei Standard Time'=>'Asia/Taipei',
+ 'Tasmania Standard Time'=>'Australia/Hobart',
+ 'Tokyo Standard Time'=>'Asia/Tokyo',
+ 'Tonga Standard Time'=>'Pacific/Tongatapu',
+ 'US Eastern Standard Time'=>'America/Indianapolis',
+ 'US Mountain Standard Time'=>'America/Phoenix',
+ 'UTC'=>'Etc/GMT',
+ 'UTC+12'=>'Etc/GMT-12',
+ 'UTC-02'=>'Etc/GMT+2',
+ 'UTC-11'=>'Etc/GMT+11',
+ 'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar',
+ 'Venezuela Standard Time'=>'America/Caracas',
+ 'Vladivostok Standard Time'=>'Asia/Vladivostok',
+ 'W. Australia Standard Time'=>'Australia/Perth',
+ 'W. Central Africa Standard Time'=>'Africa/Lagos',
+ 'W. Europe Standard Time'=>'Europe/Berlin',
+ 'West Asia Standard Time'=>'Asia/Tashkent',
+ 'West Pacific Standard Time'=>'Pacific/Port_Moresby',
+ 'Yakutsk Standard Time'=>'Asia/Yakutsk',
+
+ // Microsoft exchange timezones
+ // Source:
+ // http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx
+ //
+ // Correct timezones deduced with help from:
+ // http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+ 'Universal Coordinated Time' => 'UTC',
+ 'Casablanca, Monrovia' => 'Africa/Casablanca',
+ 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
+ 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
+ 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
+ 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
+ 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
+ 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
+ 'Prague, Central Europe' => 'Europe/Prague',
+ 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
+ 'West Central Africa' => 'Africa/Luanda', // This was a best guess
+ 'Athens, Istanbul, Minsk' => 'Europe/Athens',
+ 'Bucharest' => 'Europe/Bucharest',
+ 'Cairo' => 'Africa/Cairo',
+ 'Harare, Pretoria' => 'Africa/Harare',
+ 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
+ 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
+ 'Baghdad' => 'Asia/Baghdad',
+ 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
+ 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
+ 'East Africa, Nairobi' => 'Africa/Nairobi',
+ 'Tehran' => 'Asia/Tehran',
+ 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
+ 'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
+ 'Kabul' => 'Asia/Kabul',
+ 'Ekaterinburg' => 'Asia/Yekaterinburg',
+ 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
+ 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
+ 'Kathmandu, Nepal' => 'Asia/Kathmandu',
+ 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
+ 'Astana, Dhaka' => 'Asia/Dhaka',
+ 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
+ 'Rangoon' => 'Asia/Rangoon',
+ 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
+ 'Krasnoyarsk' => 'Asia/Krasnoyarsk',
+ 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
+ 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
+ 'Kuala Lumpur, Singapore' => 'Asia/Singapore',
+ 'Perth, Western Australia' => 'Australia/Perth',
+ 'Taipei' => 'Asia/Taipei',
+ 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
+ 'Seoul, Korea Standard time' => 'Asia/Seoul',
+ 'Yakutsk' => 'Asia/Yakutsk',
+ 'Adelaide, Central Australia' => 'Australia/Adelaide',
+ 'Darwin' => 'Australia/Darwin',
+ 'Brisbane, East Australia' => 'Australia/Brisbane',
+ 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
+ 'Guam, Port Moresby' => 'Pacific/Guam',
+ 'Hobart, Tasmania' => 'Australia/Hobart',
+ 'Vladivostok' => 'Asia/Vladivostok',
+ 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
+ 'Auckland, Wellington' => 'Pacific/Auckland',
+ 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
+ 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
+ 'Azores' => 'Atlantic/Azores',
+ 'Cape Verde Is.' => 'Atlantic/Cape_Verde',
+ 'Mid-Atlantic' => 'America/Noronha',
+ 'Brasilia' => 'America/Sao_Paulo', // Best guess
+ 'Buenos Aires' => 'America/Argentina/Buenos_Aires',
+ 'Greenland' => 'America/Godthab',
+ 'Newfoundland' => 'America/St_Johns',
+ 'Atlantic Time (Canada)' => 'America/Halifax',
+ 'Caracas, La Paz' => 'America/Caracas',
+ 'Santiago' => 'America/Santiago',
+ 'Bogota, Lima, Quito' => 'America/Bogota',
+ 'Eastern Time (US & Canada)' => 'America/New_York',
+ 'Indiana (East)' => 'America/Indiana/Indianapolis',
+ 'Central America' => 'America/Guatemala',
+ 'Central Time (US & Canada)' => 'America/Chicago',
+ 'Mexico City, Tegucigalpa' => 'America/Mexico_City',
+ 'Saskatchewan' => 'America/Edmonton',
+ 'Arizona' => 'America/Phoenix',
+ 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
+ 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
+ 'Alaska' => 'America/Anchorage',
+ 'Hawaii' => 'Pacific/Honolulu',
+ 'Midway Island, Samoa' => 'Pacific/Midway',
+ 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
+
+ );
+
+ public static $microsoftExchangeMap = array(
+ 0 => 'UTC',
+ 31 => 'Africa/Casablanca',
+ 2 => 'Europe/Lisbon',
+ 1 => 'Europe/London',
+ 4 => 'Europe/Berlin',
+ 6 => 'Europe/Prague',
+ 3 => 'Europe/Paris',
+ 69 => 'Africa/Luanda', // This was a best guess
+ 7 => 'Europe/Athens',
+ 5 => 'Europe/Bucharest',
+ 49 => 'Africa/Cairo',
+ 50 => 'Africa/Harare',
+ 59 => 'Europe/Helsinki',
+ 27 => 'Asia/Jerusalem',
+ 26 => 'Asia/Baghdad',
+ 74 => 'Asia/Kuwait',
+ 51 => 'Europe/Moscow',
+ 56 => 'Africa/Nairobi',
+ 25 => 'Asia/Tehran',
+ 24 => 'Asia/Muscat', // Best guess
+ 54 => 'Asia/Baku',
+ 48 => 'Asia/Kabul',
+ 58 => 'Asia/Yekaterinburg',
+ 47 => 'Asia/Karachi',
+ 23 => 'Asia/Calcutta',
+ 62 => 'Asia/Kathmandu',
+ 46 => 'Asia/Almaty',
+ 71 => 'Asia/Dhaka',
+ 66 => 'Asia/Colombo',
+ 61 => 'Asia/Rangoon',
+ 22 => 'Asia/Bangkok',
+ 64 => 'Asia/Krasnoyarsk',
+ 45 => 'Asia/Shanghai',
+ 63 => 'Asia/Irkutsk',
+ 21 => 'Asia/Singapore',
+ 73 => 'Australia/Perth',
+ 75 => 'Asia/Taipei',
+ 20 => 'Asia/Tokyo',
+ 72 => 'Asia/Seoul',
+ 70 => 'Asia/Yakutsk',
+ 19 => 'Australia/Adelaide',
+ 44 => 'Australia/Darwin',
+ 18 => 'Australia/Brisbane',
+ 76 => 'Australia/Sydney',
+ 43 => 'Pacific/Guam',
+ 42 => 'Australia/Hobart',
+ 68 => 'Asia/Vladivostok',
+ 41 => 'Asia/Magadan',
+ 17 => 'Pacific/Auckland',
+ 40 => 'Pacific/Fiji',
+ 67 => 'Pacific/Tongatapu',
+ 29 => 'Atlantic/Azores',
+ 53 => 'Atlantic/Cape_Verde',
+ 30 => 'America/Noronha',
+ 8 => 'America/Sao_Paulo', // Best guess
+ 32 => 'America/Argentina/Buenos_Aires',
+ 69 => 'America/Godthab',
+ 28 => 'America/St_Johns',
+ 9 => 'America/Halifax',
+ 33 => 'America/Caracas',
+ 65 => 'America/Santiago',
+ 35 => 'America/Bogota',
+ 10 => 'America/New_York',
+ 34 => 'America/Indiana/Indianapolis',
+ 55 => 'America/Guatemala',
+ 11 => 'America/Chicago',
+ 37 => 'America/Mexico_City',
+ 36 => 'America/Edmonton',
+ 38 => 'America/Phoenix',
+ 12 => 'America/Denver', // Best guess
+ 13 => 'America/Los_Angeles', // Best guess
+ 14 => 'America/Anchorage',
+ 15 => 'Pacific/Honolulu',
+ 16 => 'Pacific/Midway',
+ 39 => 'Pacific/Kwajalein',
+ );
+
+ /**
+ * This method will try to find out the correct timezone for an iCalendar
+ * date-time value.
+ *
+ * You must pass the contents of the TZID parameter, as well as the full
+ * calendar.
+ *
+ * If the lookup fails, this method will return UTC.
+ *
+ * @param string $tzid
+ * @param Sabre_VObject_Component $vcalendar
+ * @return DateTimeZone
+ */
+ static public function getTimeZone($tzid, Sabre_VObject_Component $vcalendar = null) {
+
+ // First we will just see if the tzid is a support timezone identifier.
+ try {
+ return new DateTimeZone($tzid);
+ } catch (\Exception $e) {
+ }
+
+ // Next, we check if the tzid is somewhere in our tzid map.
+ if (isset(self::$map[$tzid])) {
+ return new DateTimeZone(self::$map[$tzid]);
+ }
+
+ if ($vcalendar) {
+
+ // If that didn't work, we will scan VTIMEZONE objects
+ foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
+
+ if ((string)$vtimezone->TZID === $tzid) {
+
+ // Some clients add 'X-LIC-LOCATION' with the olson name.
+ if (isset($vtimezone->{'X-LIC-LOCATION'})) {
+ try {
+ return new DateTimeZone($vtimezone->{'X-LIC-LOCATION'});
+ } catch (\Exception $e) {
+ }
+
+ }
+ // Microsoft may add a magic number, which we also have an
+ // answer for.
+ if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
+ if (isset(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value])) {
+ return new DateTimeZone(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value]);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ // If we got all the way here, we default to UTC.
+ return new DateTimeZone(date_default_timezone_get());
+
+
+ }
+
+
+}
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';
<?php
-class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract {
+class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_NotificationSupport {
private $calendarData;
private $calendars;
+ private $notifications;
- function __construct(array $calendars, array $calendarData) {
+ function __construct(array $calendars, array $calendarData, array $notifications = array()) {
$this->calendars = $calendars;
$this->calendarData = $calendarData;
+ $this->notifications = $notifications;
}
*/
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;
}
*/
public function deleteCalendar($calendarId) {
- throw new Exception('Not implemented');
+ foreach($this->calendars as $k=>$calendar) {
+ if ($calendar['id'] === $calendarId) {
+ unset($this->calendars[$k]);
+ }
+ }
}
}
+ /**
+ * 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!');
+
+ }
+
}
$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));
--- /dev/null
+<?php
+
+/**
+ * This unittest is created to check for an endless loop in Sabre_CalDAV_CalendarQueryValidator
+ *
+ *
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
+ */
+class Sabre_CalDAV_Issue220Test extends Sabre_DAVServerTest {
+
+ protected $setupCalDAV = true;
+
+ protected $caldavCalendars = array(
+ array(
+ 'id' => 1,
+ 'name' => 'Calendar',
+ 'principaluri' => 'principals/user1',
+ 'uri' => 'calendar1',
+ )
+ );
+
+ protected $caldavCalendarObjects = array(
+ 1 => array(
+ 'event.ics' => array(
+ 'calendardata' => 'BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20120601T180000
+SUMMARY:Brot backen
+RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO
+TRANSP:OPAQUE
+DURATION:PT20M
+LAST-MODIFIED:20120601T064634Z
+CREATED:20120601T064634Z
+DTSTAMP:20120601T064634Z
+UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd
+BEGIN:VALARM
+TRIGGER;VALUE=DURATION:-PT5M
+ACTION:DISPLAY
+DESCRIPTION:Default Event Notification
+X-WR-ALARMUID:cd952c1b-b3d6-41fb-b0a6-ec3a1a5bdd58
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;TZID=Europe/Berlin:20120606T180000
+SUMMARY:Brot backen
+TRANSP:OPAQUE
+STATUS:CANCELLED
+DTEND;TZID=Europe/Berlin:20120606T182000
+LAST-MODIFIED:20120605T094310Z
+SEQUENCE:1
+RECURRENCE-ID:20120606T160000Z
+UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd
+END:VEVENT
+END:VCALENDAR
+',
+ ),
+ ),
+ );
+
+ function testIssue220() {
+
+ $request = new Sabre_HTTP_Request(array(
+ 'REQUEST_METHOD' => 'REPORT',
+ 'HTTP_CONTENT_TYPE' => 'application/xml',
+ 'REQUEST_URI' => '/calendars/user1/calendar1',
+ 'HTTP_DEPTH' => '1',
+ ));
+
+ $request->setBody('<?xml version="1.0" encoding="utf-8" ?>
+<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
+ <D:prop>
+ <C:calendar-data/>
+ <D:getetag/>
+ </D:prop>
+ <C:filter>
+ <C:comp-filter name="VCALENDAR">
+ <C:comp-filter name="VEVENT">
+ <C:comp-filter name="VALARM">
+ <C:time-range start="20120607T161646Z" end="20120612T161646Z"/>
+ </C:comp-filter>
+ </C:comp-filter>
+ </C:comp-filter>
+ </C:filter>
+</C:calendar-query>');
+
+ $response = $this->request($request);
+
+ $this->assertFalse(strpos($response->body, '<s:exception>PHPUnit_Framework_Error_Warning</s:exception>'), 'Error Warning occurred: ' . $response->body);
+ $this->assertFalse(strpos($response->body, 'Invalid argument supplied for foreach()'), 'Invalid argument supplied for foreach(): ' . $response->body);
+
+ $this->assertEquals('HTTP/1.1 207 Multi-Status', $response->status);
+ }
+}
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Notifications_CollectionTest extends \PHPUnit_Framework_TestCase {
+
+ function testGetChildren() {
+
+ $principalUri = 'principals/user1';
+
+ $systemStatus = new Sabre_CalDAV_Notifications_Notification_SystemStatus(1);
+
+ $caldavBackend = new Sabre_CalDAV_Backend_Mock(array(),array(), array(
+ 'principals/user1' => array(
+ $systemStatus
+ )
+ ));
+
+
+ $col = new Sabre_CalDAV_Notifications_Collection($caldavBackend, $principalUri);
+ $this->assertEquals('notifications', $col->getName());
+
+ $this->assertEquals(array(
+ new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus)
+ ), $col->getChildren());
+
+ }
+
+}
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Notifications_NodeTest extends \PHPUnit_Framework_TestCase {
+
+ function testGetId() {
+
+ $principalUri = 'principals/user1';
+
+ $systemStatus = new Sabre_CalDAV_Notifications_Notification_SystemStatus(1);
+
+ $caldavBackend = new Sabre_CalDAV_Backend_Mock(array(),array(), array(
+ 'principals/user1' => array(
+ $systemStatus
+ )
+ ));
+
+
+ $node = new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus);
+ $this->assertEquals($systemStatus->getId(), $node->getName());
+
+ }
+
+}
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Notifications_Notification extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider dataProvider
+ */
+ function testSerializers($notification, $expected1, $expected2) {
+
+ $this->assertEquals('foo', $notification->getId());
+
+
+ $dom = new DOMDocument('1.0','UTF-8');
+ $elem = $dom->createElement('cs:root');
+ $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
+ $dom->appendChild($elem);
+ $notification->serialize(new Sabre_DAV_Server(), $elem);
+ $this->assertEquals($expected1, $dom->saveXML());
+
+ $dom = new DOMDocument('1.0','UTF-8');
+ $elem = $dom->createElement('cs:root');
+ $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
+ $dom->appendChild($elem);
+ $notification->serializeBody(new Sabre_DAV_Server(), $elem);
+ $this->assertEquals($expected2, $dom->saveXML());
+
+
+ }
+
+ function dataProvider() {
+
+ return array(
+
+ array(
+ new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo'),
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="high"/></cs:root>' . "\n",
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="high"/></cs:root>' . "\n",
+ ),
+
+ array(
+ new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_MEDIUM,'bar'),
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="medium"/></cs:root>' . "\n",
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="medium"><cs:description>bar</cs:description></cs:systemstatus></cs:root>' . "\n",
+ ),
+
+ array(
+ new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_LOW,null,'http://example.org/'),
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="low"/></cs:root>' . "\n",
+ '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<cs:root xmlns:cs="http://calendarserver.org/ns/"><cs:systemstatus type="low"><d:href>http://example.org/</d:href></cs:systemstatus></cs:root>' . "\n",
+ )
+ );
+
+ }
+
+}
$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);
+
}
$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(
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'));
'{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);
$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);
$this->assertInstanceOf('Sabre_DAV_Property_HrefList', $prop);
$this->assertEquals(array('principals/admin'), $prop->getHrefs());
+
}
function testSupportedReportSetPropertyNonCalendar() {
'<d:prop>' .
' <c:calendar-data>' .
' <c:expand start="20000101T000000Z" end="20101231T235959Z" />' .
- ' </c:calendar-data>' .
+ ' </c:calendar-data>' .
' <d:getetag />' .
'</d:prop>' .
'<c:filter>' .
$this->assertEquals('HTTP/1.1 400 Bad request',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body);
}
+
+ function testNotificationProperties() {
+
+ $request = array(
+ '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype',
+ );
+ $result = array();
+ $notification = new Sabre_CalDAV_Notifications_Node(
+ $this->caldavBackend,
+ new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo')
+ );
+ $this->plugin->beforeGetProperties('foo', $notification, $request, $result);
+
+ $this->assertEquals(
+ array(
+ 200 => array(
+ '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype' => $notification->getNotificationType()
+ )
+ ), $result);
+
+ }
+
+ function testNotificationGet() {
+
+ $notification = new Sabre_CalDAV_Notifications_Node(
+ $this->caldavBackend,
+ new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo')
+ );
+
+ $server = new Sabre_DAV_Server(array($notification));
+ $caldav = new Sabre_CalDAV_Plugin();
+
+ $httpResponse = new Sabre_HTTP_ResponseMock();
+ $server->httpResponse = $httpResponse;
+
+ $server->addPlugin($caldav);
+
+ $caldav->beforeMethod('GET','foo');
+
+ $this->assertEquals('HTTP/1.1 200 OK', $httpResponse->status);
+ $this->assertEquals(array(
+ 'Content-Type' => 'application/xml',
+ ), $httpResponse->headers);
+
+ $expected =
+'<?xml version="1.0" encoding="UTF-8"?>
+<cs:notification xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
+ <cs:systemstatus type="high"/>
+</cs:notification>
+';
+
+ $this->assertEquals($expected, $httpResponse->body);
+
+
+ }
+
}
'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') ),
)
);
$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);
+
+ }
}
'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(
'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);
private $tempPath;
+ private $exception;
+
function testAfterBind() {
$this->server->subscribeEvent('afterBind',array($this,'afterBindHandler'));
}
+ 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;
+
+ }
}
$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());
}
--- /dev/null
+<?php
+
+class Sabre_VObject_TimeZoneUtilTest extends PHPUnit_Framework_TestCase {
+
+ /**
+ * @dataProvider getMapping
+ */
+ function testCorrectTZ($timezoneName) {
+
+ $tz = new DateTimeZone($timezoneName);
+
+ }
+
+ function getMapping() {
+
+ // PHPUNit requires an array of arrays
+ return array_map(
+ function($value) {
+ return array($value);
+ },
+ Sabre_VObject_TimeZoneUtil::$map
+ );
+
+ }
+
+ function testExchangeMap() {
+
+ $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+X-MICROSOFT-CDO-TZID:2
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+
+ $tz = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_Reader::read($vobj));
+
+ $this->assertEquals(new DateTimeZone('Europe/Sarajevo'), $tz);
+
+ }
+
+ function testUnknownExchangeId() {
+
+ $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+X-MICROSOFT-CDO-TZID:2000
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+
+ $tz = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_Reader::read($vobj));
+
+ $this->assertEquals(new DateTimeZone(date_default_timezone_get()), $tz);
+
+ }
+
+ function testWindowsTimeZone() {
+
+ $tz = Sabre_VObject_TimeZoneUtil::getTimeZone('Eastern Standard Time');
+ $this->assertEquals(new DateTimeZone('America/New_York'), $tz);
+
+ }
+
+ function testFallBack() {
+
+ $vobj = <<<HI
+BEGIN:VCALENDAR
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:foo
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20120416T092149Z
+DTSTART;TZID="foo":20120418T1
+ 00000
+SUMMARY:Begin Unterhaltsreinigung
+UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000
+ 0100000008FECD2E607780649BE5A4C9EE6418CBC
+ 000
+END:VEVENT
+END:VCALENDAR
+HI;
+ $tz = Sabre_VObject_TimeZoneUtil::getTimeZone('foo', Sabre_VObject_Reader::read($vobj));
+
+ $this->assertEquals(new DateTimeZone(date_default_timezone_get()), $tz);
+
+ }
+
+}
<?php
-$a = get_app();
-$uri = parse_url($a->get_baseurl());
+$a = get_app();
+$uri = parse_url($a->get_baseurl());
$path = "/";
-if (strlen($uri["path"]) > 1) {
+if (isset($uri["path"]) && strlen($uri["path"]) > 1) {
$path = $uri["path"] . "/";
}
define("CALDAV_URL_PREFIX", $path . "dav/");
define("CALDAV_NAMESPACE_PRIVATE", 1);
-define("CALDAV_NAMESPACE_FRIENDICA_NATIVE", 2);
-define("CALDAV_FRIENDICA_MINE", 1);
-define("CALDAV_FRIENDICA_CONTACTS", 2);
+define("CALDAV_FRIENDICA_MINE", "friendica-mine");
+define("CALDAV_FRIENDICA_CONTACTS", "friendica-contacts");
+
+$GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"] = array(CALDAV_FRIENDICA_MINE, CALDAV_FRIENDICA_CONTACTS);
define("CARDDAV_NAMESPACE_COMMUNITYCONTACTS", 1);
define("CARDDAV_NAMESPACE_PHONECONTACTS", 2);
-define("CALDAV_DB_VERSION", 1);
+define("CALDAV_DB_VERSION", 2);
+
+define("CALDAV_MAX_YEAR", date("Y") + 5);
/**
* @return int
*/
-function getCurMicrotime () {
- list($usec, $sec) = explode(" ", microtime());
- return sprintf("%14.0f", $sec * 10000 + $usec * 10000);
+function getCurMicrotime()
+{
+ list($usec, $sec) = explode(" ", microtime());
+ return sprintf("%14.0f", $sec * 10000 + $usec * 10000);
} // function getCurMicrotime
/**
*
*/
-function debug_time() {
- $cur = getCurMicrotime();
- if ($GLOBALS["debug_time_last"] > 0) {
- echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "<br>\n";
- }
- $GLOBALS["debug_time_last"] = $cur;
+function debug_time()
+{
+ $cur = getCurMicrotime();
+ if ($GLOBALS["debug_time_last"] > 0) {
+ echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "<br>\n";
+ }
+ $GLOBALS["debug_time_last"] = $cur;
}
*/
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;
}
*/
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 "";
}
/**
* @return int
*/
-function dav_compat_get_curr_user_id() {
+function dav_compat_get_curr_user_id()
+{
$a = get_app();
return IntVal($a->user["uid"]);
}
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;
}
/**
* @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);
+}
+
+
/**
*/
$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();
+ $uris = array(
+ 'private' => t("Private Calendar"),
+ CALDAV_FRIENDICA_MINE => t("Friendica Events: Mine"),
+ CALDAV_FRIENDICA_CONTACTS => t("Friendica Events: Contacts"),
+ );
+ foreach ($uris as $uri => $name) {
+
+ $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $a->user["uid"], dbesc($uri));
+ if (count($cals) == 0) {
+ q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `displayname`, `timezone`, `ctag`, `uri`, `has_vevent`, `has_vtodo`) VALUES (%d, %d, %d, '%s', '%s', 1, '%s', 1, 0)",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]), dbesc($name), dbesc($a->timezone), dbesc($uri)
+ );
}
- $nextid = IntVal($maxid[0]["maxid"]) + 1;
- q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $nextid, $a->user["uid"], dbesc(t("Private Calendar")), dbesc($a->timezone)
- );
- }
-
- $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_FRIENDICA_NATIVE);
- if (count($cals) < 2) {
- q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_MINE, $a->user["uid"], dbesc(t("Friendica Events: Mine")), dbesc($a->timezone)
- );
- q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_CONTACTS, $a->user["uid"], dbesc(t("Friendica Events: Contacts")), dbesc($a->timezone)
- );
}
}
<title>Really Simple Color Picker</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
-<script language="javascript" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
-<script language="javascript" type="text/javascript" src="jquery.colorPicker.min.js"/></script>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+<script type="text/javascript" src="jquery.colorPicker.min.js"></script>
<script type="text/javascript">
//Run the code when document ready
<?php
+define("DAV_ACL_READ", "{DAV:}read");
+define("DAV_ACL_WRITE", "{DAV:}write");
+define("DAV_DISPLAYNAME", "{DAV:}displayname");
+
+
class vcard_source_data_email
{
public $email, $type;
/** @var array|vcard_source_data_email[] $email */
public $emails;
- /** @var array|vcard_source_data_addresses[] $addresses */
+ /** @var array|vcard_source_data_address[] $addresses */
public $addresses;
/** @var vcard_source_data_photo */
}
-/**
- * @param array $start
- * @param array $end
- * @param bool $allday
- * @return vevent
- */
-function dav_create_vevent($start, $end, $allday)
-{
- if ($end["year"] < $start["year"] ||
- ($end["year"] == $start["year"] && $end["month"] < $start["month"]) ||
- ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] < $start["day"]) ||
- ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] < $start["hour"]) ||
- ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] < $start["minute"]) ||
- ($end["year"] == $start["year"] && $end["month"] == $start["month"] && $end["day"] == $start["day"] && $end["hour"] == $start["hour"] && $end["minute"] == $start["minute"] && $end["second"] < $start["second"])
- ) {
- $end = $start;
- } // DTEND muss <= DTSTART
-
- $vevent = new vevent();
- if ($allday) {
- $vevent->setDtstart($start["year"], $start["month"], $start["day"], FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
- $end = IntVal(mktime(0, 0, 0, $end["month"], $end["day"], $end["year"]) + 3600 * 24);
-
- // If a DST change occurs on the current day
- $end += IntVal(date("Z", ($end - 3600 * 24)) - date("Z", $end));
-
- $vevent->setDtend(date("Y", $end), date("m", $end), date("d", $end), FALSE, FALSE, FALSE, FALSE, array("VALUE"=> "DATE"));
- } else {
- $vevent->setDtstart($start["year"], $start["month"], $start["day"], $start["hour"], $start["minute"], $start["second"], FALSE, array("VALUE"=> "DATE-TIME"));
- $vevent->setDtend($end["year"], $end["month"], $end["day"], $end["hour"], $end["minute"], $end["second"], FALSE, array("VALUE"=> "DATE-TIME"));
- }
- return $vevent;
-}
-
-
/**
* @param int $phpDate (UTC)
* @return string (Lokalzeit)
$str = str_replace("\r\n", "\n", $str);
$str = str_replace("\n\r", "\n", $str);
$str = str_replace("\r", "\n", $str);
+ $str = str_replace("\n\n", "\n", $str);
+ $str = str_replace("\n\n", "\n", $str);
return $str;
}
/**
- * @param DBClass_friendica_calendars $calendar
- * @param DBClass_friendica_calendarobjects $calendarobject
+ * @return Sabre_CalDAV_AnimexxCalendarRootNode
*/
-function renderCalDavEntry_data(&$calendar, &$calendarobject)
+function dav_createRootCalendarNode()
{
- $a = get_app();
-
- $v = new vcalendar();
- $v->setConfig('unique_id', $a->get_hostname());
- $v->parse($calendarobject->calendardata);
- $v->sort();
-
- $eventArray = $v->selectComponents(2009, 1, 1, date("Y") + 2, 12, 30);
-
- $start_min = $end_max = "";
-
- $allday = $summary = $vevent = $rrule = $color = $start = $end = null;
- $location = $description = "";
-
- foreach ($eventArray as $yearArray) {
- foreach ($yearArray as $monthArray) {
- foreach ($monthArray as $day => $dailyEventsArray) {
- foreach ($dailyEventsArray as $vevent) {
- /** @var $vevent vevent */
- $start = "";
- $rrule = "NULL";
- $allday = 0;
-
- $dtstart = $vevent->getProperty('X-CURRENT-DTSTART');
- if (is_array($dtstart)) {
- $start = "'" . $dtstart[1] . "'";
- if (strpos($dtstart[1], ":") === false) $allday = 1;
- } else {
- $dtstart = $vevent->getProperty('dtstart');
- if (isset($dtstart["day"]) && $dtstart["day"] == $day) { // Mehrtägige Events nur einmal rein
- if (isset($dtstart["hour"])) $start = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " " . $dtstart["hour"] . ":" . $dtstart["minute"] . ":" . $dtstart["secont"] . "'";
- else {
- $start = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " 00:00:00'";
- $allday = 1;
- }
- }
- }
-
- $dtend = $vevent->getProperty('X-CURRENT-DTEND');
- if (is_array($dtend)) {
- $end = "'" . $dtend[1] . "'";
- if (strpos($dtend[1], ":") === false) $allday = 1;
- } else {
- $dtend = $vevent->getProperty('dtend');
- if (isset($dtend["hour"])) $end = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " " . $dtend["hour"] . ":" . $dtend["minute"] . ":" . $dtend["second"] . "'";
- else {
- $end = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " 00:00:00' - INTERVAL 1 SECOND";
- $allday = 1;
- }
- }
- $summary = $vevent->getProperty('summary');
- $description = $vevent->getProperty('description');
- $location = $vevent->getProperty('location');
- $rrule_prob = $vevent->getProperty('rrule');
- if ($rrule_prob != null) {
- $rrule = $vevent->createRrule();
- $rrule = "'" . dbesc($rrule) . "'";
- }
- $color_ = $vevent->getProperty("X-ANIMEXX-COLOR");
- $color = (is_array($color_) ? $color_[1] : "NULL");
-
- if ($start_min == "" || preg_replace("/[^0-9]/", "", $start) < preg_replace("/[^0-9]/", "", $start_min)) $start_min = $start;
- if ($end_max == "" || preg_replace("/[^0-9]/", "", $end) > preg_replace("/[^0-9]/", "", $start_min)) $end_max = $end;
- }
- }
- }
- }
-
- if ($start_min != "") {
+ $caldavBackend_std = Sabre_CalDAV_Backend_Private::getInstance();
+ $caldavBackend_community = Sabre_CalDAV_Backend_Friendica::getInstance();
- if ($allday && mb_strlen($end_max) == 12) {
- $x = explode("-", str_replace("'", "", $end_max));
- $time = mktime(0, 0, 0, IntVal($x[1]), IntVal($x[2]), IntVal($x[0]));
- $end_max = date("'Y-m-d H:i:s'", ($time - 1));
- }
-
- q("INSERT INTO %s%sjqcalendar (`uid`, `namespace`, `namespace_id`, `ical_uri`, `Subject`, `Location`, `Description`, `StartTime`, `EndTime`, `IsAllDayEvent`, `RecurringRule`, `Color`)
- VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', %s, %s, %d, '%s', '%s')",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
- IntVal($calendar->uid), IntVal($calendarobject->namespace), IntVal($calendarobject->namespace_id), dbesc($calendarobject->uri), dbesc($summary),
- dbesc($location), dbesc(str_replace("\\n", "\n", $description)), $start_min, $end_max, IntVal($allday), dbesc($rrule), dbesc($color)
- );
-
- foreach ($vevent->components as $comp) {
- /** @var $comp calendarComponent */
- $trigger = $comp->getProperty("TRIGGER");
- $sql_field = ($trigger["relatedStart"] ? $start : $end);
- $sql_op = ($trigger["before"] ? "DATE_SUB" : "DATE_ADD");
- $num = "";
- $rel_type = "";
- $rel_value = 0;
- if (isset($trigger["second"])) {
- $num = IntVal($trigger["second"]) . " SECOND";
- $rel_type = "second";
- $rel_value = IntVal($trigger["second"]);
- }
- if (isset($trigger["minute"])) {
- $num = IntVal($trigger["minute"]) . " MINUTE";
- $rel_type = "minute";
- $rel_value = IntVal($trigger["minute"]);
- }
- if (isset($trigger["hour"])) {
- $num = IntVal($trigger["hour"]) . " HOUR";
- $rel_type = "hour";
- $rel_value = IntVal($trigger["hour"]);
- }
- if (isset($trigger["day"])) {
- $num = IntVal($trigger["day"]) . " DAY";
- $rel_type = "day";
- $rel_value = IntVal($trigger["day"]);
- }
- if (isset($trigger["week"])) {
- $num = IntVal($trigger["week"]) . " WEEK";
- $rel_type = "week";
- $rel_value = IntVal($trigger["week"]);
- }
- if (isset($trigger["month"])) {
- $num = IntVal($trigger["month"]) . " MONTH";
- $rel_type = "month";
- $rel_value = IntVal($trigger["month"]);
- }
- if (isset($trigger["year"])) {
- $num = IntVal($trigger["year"]) . " YEAR";
- $rel_type = "year";
- $rel_value = IntVal($trigger["year"]);
- }
- if ($trigger["before"]) $rel_value *= -1;
-
- if ($rel_type != "") {
- $not_date = "$sql_op($sql_field, INTERVAL $num)";
- q("INSERT INTO %s%snotifications (`uid`, `ical_uri`, `rel_type`, `rel_value`, `alert_date`, `notified`) VALUES ('%s', '%s', '%s', '%s', %s, IF(%s < NOW(), 1, 0))",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
- IntVal($calendar->uid), dbesc($calendarobject->uri), dbesc($rel_type), IntVal($rel_value), $not_date, $not_date);
- }
- }
- }
+ return new Sabre_CalDAV_AnimexxCalendarRootNode(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), array(
+ $caldavBackend_std,
+ $caldavBackend_community,
+ ));
}
-
/**
- *
+ * @return Sabre_CardDAV_AddressBookRootFriendica
*/
-function renderAllCalDavEntries()
+function dav_createRootContactsNode()
{
- q("DELETE FROM %s%sjqcalendar", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
- q("DELETE FROM %s%snotifications", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
- $calendars = q("SELECT * FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
- $anz = count($calendars);
- $i = 0;
- foreach ($calendars as $calendar) {
- $cal = new DBClass_friendica_calendars($calendar);
- $i++;
- if (($i % 100) == 0) echo "$i / $anz\n";
- $calobjs = q("SELECT * FROM %s%scalendarobjects WHERE `namespace` = %d AND `namespace_id` = %d",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["namespace"]), IntVal($calendar["namespace_id"]));
- foreach ($calobjs as $calobj) {
- $obj = new DBClass_friendica_calendarobjects($calobj);
- renderCalDavEntry_data($cal, $obj);
- }
- }
-}
-
+ $carddavBackend_std = Sabre_CardDAV_Backend_Std::getInstance();
+ $carddavBackend_community = Sabre_CardDAV_Backend_FriendicaCommunity::getInstance();
-/**
- * @param string $uri
- * @return bool
- */
-function renderCalDavEntry_uri($uri)
-{
- q("DELETE FROM %s%sjqcalendar WHERE `ical_uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
- q("DELETE FROM %s%snotifications WHERE `ical_uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
-
- $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
- if (count($calobj) == 0) return false;
- $cal = new DBClass_friendica_calendarobjects($calobj[0]);
- $calendars = q("SELECT * FROM %s%scalendars WHERE `namespace`=%d AND `namespace_id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($cal->namespace), IntVal($cal->namespace_id));
- $calendar = new DBClass_friendica_calendars($calendars[0]);
- renderCalDavEntry_data($calendar, $cal);
- return true;
+ return new Sabre_CardDAV_AddressBookRootFriendica(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), array(
+ $carddavBackend_std,
+ $carddavBackend_community,
+ ));
}
/**
- * @param $user_id
- * @return array|DBClass_friendica_calendars[]
+ * @param bool $force_authentication
+ * @param bool $needs_caldav
+ * @param bool $needs_carddav
+ * @return Sabre_DAV_Server
*/
-function dav_getMyCals($user_id)
+function dav_create_server($force_authentication = false, $needs_caldav = true, $needs_carddav = true)
{
- $d = q("SELECT * FROM %s%scalendars WHERE `uid` = %d ORDER BY `calendarorder` ASC",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($user_id), CALDAV_NAMESPACE_PRIVATE
+ $arr = array(
+ new Sabre_DAV_SimpleCollection('principals', array(
+ new Sabre_CalDAV_Principal_Collection(Sabre_DAVACL_PrincipalBackend_Std::getInstance(), "principals/users"),
+ )),
);
- $cals = array();
- foreach ($d as $e) $cals[] = new DBClass_friendica_calendars($e);
- return $cals;
+ if ($needs_caldav) $arr[] = dav_createRootCalendarNode();
+ if ($needs_carddav) $arr[] = dav_createRootContactsNode();
+
+
+ $tree = new Sabre_DAV_SimpleCollection('root', $arr);
+
+// The object tree needs in turn to be passed to the server class
+ $server = new Sabre_DAV_Server($tree);
+
+ $server->setBaseUri(CALDAV_URL_PREFIX);
+
+ $authPlugin = new Sabre_DAV_Auth_Plugin(Sabre_DAV_Auth_Backend_Std::getInstance(), 'SabreDAV');
+ $server->addPlugin($authPlugin);
+
+ $aclPlugin = new Sabre_DAVACL_Plugin_Friendica();
+ $aclPlugin->defaultUsernamePath = "principals/users";
+ $server->addPlugin($aclPlugin);
+
+ if ($needs_caldav) {
+ $caldavPlugin = new Sabre_CalDAV_Plugin();
+ $server->addPlugin($caldavPlugin);
+ }
+ if ($needs_carddav) {
+ $carddavPlugin = new Sabre_CardDAV_Plugin();
+ $server->addPlugin($carddavPlugin);
+ }
+
+ if ($force_authentication) $server->broadcastEvent('beforeMethod', array("GET", "/")); // Make it authenticate
+
+ return $server;
}
/**
- * @param mixed $obj
- * @return string
+ * @param Sabre_DAV_Server $server
+ * @param string $with_privilege
+ * @return array|Sabre_CalDAV_Calendar[]
*/
-function wdcal_jsonp_encode($obj)
+function dav_get_current_user_calendars(&$server, $with_privilege = "")
{
- $str = json_encode($obj);
- if (isset($_REQUEST["callback"])) {
- $str = $_REQUEST["callback"] . "(" . $str . ")";
+ if ($with_privilege == "") $with_privilege = DAV_ACL_READ;
+
+ $a = get_app();
+ $calendar_path = "/calendars/" . strtolower($a->user["nickname"]) . "/";
+
+ /** @var Sabre_CalDAV_AnimexxUserCalendars $tree */
+ $tree = $server->tree->getNodeForPath($calendar_path);
+ /** @var array|Sabre_CalDAV_Calendar[] $calendars */
+ $children = $tree->getChildren();
+
+ $calendars = array();
+ /** @var Sabre_DAVACL_Plugin $aclplugin */
+ $aclplugin = $server->getPlugin("acl");
+ foreach ($children as $child) if (is_a($child, "Sabre_CalDAV_Calendar")) {
+ if ($with_privilege != "") {
+ $caluri = $calendar_path . $child->getName();
+ if ($aclplugin->checkPrivileges($caluri, $with_privilege, Sabre_DAVACL_Plugin::R_PARENT, false)) $calendars[] = $child;
+ } else {
+ $calendars[] = $child;
+ }
}
- return $str;
+ return $calendars;
}
/**
- * @param string $day
- * @param int $weekstartday
- * @param int $num_days
- * @param string $type
- * @return array
+ * @param Sabre_DAV_Server $server
+ * @param Sabre_CalDAV_Calendar $calendar
+ * @param string $calendarobject_uri
+ * @param string $with_privilege
+ * @return null|Sabre_VObject_Component_VEvent
*/
-function wdcal_get_list_range_params($day, $weekstartday, $num_days, $type)
+function dav_get_current_user_calendarobject(&$server, &$calendar, $calendarobject_uri, $with_privilege = "")
{
- $phpTime = IntVal($day);
- switch ($type) {
- case "month":
- $st = mktime(0, 0, 0, date("m", $phpTime), 1, date("Y", $phpTime));
- $et = mktime(0, 0, -1, date("m", $phpTime) + 1, 1, date("Y", $phpTime));
- break;
- case "week":
- //suppose first day of a week is monday
- $monday = date("d", $phpTime) - date('N', $phpTime) + 1;
- //echo date('N', $phpTime);
- $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
- $et = mktime(0, 0, -1, date("m", $phpTime), $monday + 7, date("Y", $phpTime));
- break;
- case "multi_days":
- //suppose first day of a week is monday
- $monday = date("d", $phpTime) - date('N', $phpTime) + $weekstartday;
- //echo date('N', $phpTime);
- $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
- $et = mktime(0, 0, -1, date("m", $phpTime), $monday + $num_days, date("Y", $phpTime));
- break;
- case "day":
- $st = mktime(0, 0, 0, date("m", $phpTime), date("d", $phpTime), date("Y", $phpTime));
- $et = mktime(0, 0, -1, date("m", $phpTime), date("d", $phpTime) + 1, date("Y", $phpTime));
- break;
- default:
- return array(0, 0);
- }
- return array($st, $et);
-}
+ $obj = $calendar->getChild($calendarobject_uri);
+ if ($with_privilege == "") $with_privilege = DAV_ACL_READ;
+ $a = get_app();
+ $uri = "/calendars/" . strtolower($a->user["nickname"]) . "/" . $calendar->getName() . "/" . $calendarobject_uri;
+ /** @var Sabre_DAVACL_Plugin $aclplugin */
+ $aclplugin = $server->getPlugin("acl");
+ if (!$aclplugin->checkPrivileges($uri, $with_privilege, Sabre_DAVACL_Plugin::R_PARENT, false)) return null;
+
+ $data = $obj->get();
+ $vObject = Sabre_VObject_Reader::read($data);
+
+ return $vObject;
+}
/**
- * @param string $uri
- * @param string $recurr_uri
- * @param int $uid
- * @param string $timezone
- * @param string $goaway_url
- * @return string
+ * @param Sabre_DAV_Server $server
+ * @param int $id
+ * @param string $with_privilege
+ * @return null|Sabre_CalDAV_Calendar
*/
-function wdcal_postEditPage($uri, $recurr_uri = "", $uid = 0, $timezone = "", $goaway_url = "")
+function dav_get_current_user_calendar_by_id(&$server, $id, $with_privilege = "")
{
- $uid = IntVal($uid);
- $localization = wdcal_local::getInstanceByUser($uid);
-
- if (isset($_REQUEST["allday"])) {
- $start = $localization->date_parseLocal($_REQUEST["start_date"] . " 00:00");
- $end = $localization->date_parseLocal($_REQUEST["end_date"] . " 20:00");
- $isallday = true;
- } else {
- $start = $localization->date_parseLocal($_REQUEST["start_date"] . " " . $_REQUEST["start_time"]);
- $end = $localization->date_parseLocal($_REQUEST["end_date"] . " " . $_REQUEST["end_time"]);
- $isallday = false;
- }
+ $calendars = dav_get_current_user_calendars($server, $with_privilege);
- if ($uri == "new") {
- $cals = dav_getMyCals($uid);
- foreach ($cals as $c) {
- $cs = wdcal_calendar_factory($uid, $c->namespace, $c->namespace_id);
- $p = $cs->getPermissionsCalendar($uid);
-
- if ($p["write"]) try {
- $cs->addItem($start, $end, dav_compat_getRequestVar("subject"), $isallday, dav_compat_parse_text_serverside("wdcal_desc"),
- dav_compat_getRequestVar("location"), dav_compat_getRequestVar("color"), $timezone,
- isset($_REQUEST["notification"]), $_REQUEST["notification_type"], $_REQUEST["notification_value"]);
- } catch (Exception $e) {
- notification(t("Error") . ": " . $e);
- }
- dav_compat_redirect($goaway_url);
- }
-
- } else {
- $cals = dav_getMyCals($uid);
- foreach ($cals as $c) {
- $cs = wdcal_calendar_factory($uid, $c->namespace, $c->namespace_id);
- $p = $cs->getPermissionsItem($uid, $uri, $recurr_uri);
- if ($p["write"]) try {
- $cs->updateItem($uri, $start, $end,
- dav_compat_getRequestVar("subject"), $isallday, dav_compat_parse_text_serverside("wdcal_desc"),
- dav_compat_getRequestVar("location"), dav_compat_getRequestVar("color"), $timezone,
- isset($_REQUEST["notification"]), $_REQUEST["notification_type"], $_REQUEST["notification_value"]);
- } catch (Exception $e) {
- notification(t("Error") . ": " . $e);
- }
- dav_compat_redirect($goaway_url);
- }
+ $calendar = null;
+ foreach ($calendars as $cal) {
+ $prop = $cal->getProperties(array("id"));
+ if (isset($prop["id"]) && $prop["id"] == $id) $calendar = $cal;
}
+
+ return $calendar;
}
/**
- *
+ * @param string $uid
+ * @return Sabre_VObject_Component_VEvent $vObject
*/
-function wdcal_print_feed($base_path = "")
+function dav_create_empty_vevent($uid = "")
{
- $user_id = dav_compat_get_curr_user_id();
- $cals = array();
- if (isset($_REQUEST["cal"])) foreach ($_REQUEST["cal"] as $c) {
- $x = explode("-", $c);
- $calendarSource = wdcal_calendar_factory($user_id, $x[0], $x[1]);
- $calp = $calendarSource->getPermissionsCalendar($user_id);
- if ($calp["read"]) $cals[] = $calendarSource;
- }
+ $a = get_app();
+ if ($uid == "") $uid = uniqid();
+ return Sabre_VObject_Reader::read("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\nBEGIN:VEVENT\r\nUID:" . $uid . "@" . $a->get_hostname() .
+ "\r\nDTSTAMP:" . date("Ymd") . "T" . date("His") . "Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
+}
- $ret = null;
- /** @var $cals array|AnimexxCalSource[] */
-
- $method = $_GET["method"];
- switch ($method) {
- case "add":
- $cs = null;
- foreach ($cals as $c) if ($cs == null) {
- $x = $c->getPermissionsCalendar($user_id);
- if ($x["read"]) $cs = $c;
- }
- if ($cs == null) {
- echo wdcal_jsonp_encode(array('IsSuccess' => false,
- 'Msg' => t('No access')));
- killme();
- }
- try {
- $start = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarStartTime"]));
- $end = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarEndTime"]));
- $newuri = $cs->addItem($start, $end, $_REQUEST["CalendarTitle"], $_REQUEST["IsAllDayEvent"]);
- $ret = array(
- 'IsSuccess' => true,
- 'Msg' => 'add success',
- 'Data' => $newuri,
- );
-
- } catch (Exception $e) {
- $ret = array(
- 'IsSuccess' => false,
- 'Msg' => $e->__toString(),
- );
- }
- break;
- case "list":
- $weekstartday = (isset($_REQUEST["weekstartday"]) ? IntVal($_REQUEST["weekstartday"]) : 1); // 1 = Monday
- $num_days = (isset($_REQUEST["num_days"]) ? IntVal($_REQUEST["num_days"]) : 7);
- $ret = null;
-
- $date = wdcal_get_list_range_params($_REQUEST["showdate"], $weekstartday, $num_days, $_REQUEST["viewtype"]);
- $ret = array();
- $ret['events'] = array();
- $ret["issort"] = true;
- $ret["start"] = $date[0];
- $ret["end"] = $date[1];
- $ret['error'] = null;
-
- foreach ($cals as $c) {
- $events = $c->listItemsByRange($date[0], $date[1], $base_path);
- $ret["events"] = array_merge($ret["events"], $events);
- }
-
- $tmpev = array();
- foreach ($ret["events"] as $e) {
- if (!isset($tmpev[$e["start"]])) $tmpev[$e["start"]] = array();
- $tmpev[$e["start"]][] = $e;
- }
- ksort($tmpev);
- $ret["events"] = array();
- foreach ($tmpev as $e) foreach ($e as $f) $ret["events"][] = $f;
+
+
+/**
+ * @param Sabre_VObject_Component_VEvent $vObject
+ * @return Sabre_VObject_Component_VEvent|null
+ */
+function dav_get_eventComponent(&$vObject)
+{
+ $component = null;
+ $componentType = "";
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
break;
- case "update":
- $found = false;
- $start = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarStartTime"]));
- $end = wdcal_mySql2icalTime(wdcal_php2MySqlTime($_REQUEST["CalendarEndTime"]));
- foreach ($cals as $c) try {
- $permissions_item = $c->getPermissionsItem($user_id, $_REQUEST["calendarId"], "");
- if ($permissions_item["write"]) {
- $c->updateItem($_REQUEST["calendarId"], $start, $end);
- $found = true;
- }
- } catch (Exception $e) {
- }
- ;
-
- if ($found) {
- $ret = array(
- 'IsSuccess' => true,
- 'Msg' => 'Succefully',
- );
- } else {
- echo wdcal_jsonp_encode(array('IsSuccess' => false,
- 'Msg' => t('No access')));
- killme();
- }
-
- try {
- } catch (Exception $e) {
- $ret = array(
- 'IsSuccess' => false,
- 'Msg' => $e->__toString(),
- );
- }
- break;
- case "remove":
- $found = false;
- foreach ($cals as $c) try {
- $permissions_item = $c->getPermissionsItem($user_id, $_REQUEST["calendarId"], "");
- if ($permissions_item["write"]) $c->removeItem($_REQUEST["calendarId"]);
- } catch (Exception $e) {
- }
-
- if ($found) {
- $ret = array(
- 'IsSuccess' => true,
- 'Msg' => 'Succefully',
- );
- } else {
- echo wdcal_jsonp_encode(array('IsSuccess' => false,
- 'Msg' => t('No access')));
- killme();
- }
- break;
+ }
}
- echo wdcal_jsonp_encode($ret);
- killme();
-}
+ if ($componentType != "VEVENT") return null;
+ return $component;
+}
--- /dev/null
+<?php
+
+
+
+/**
+ * @param Sabre_VObject_Component_VAlarm $alarm
+ * @param Sabre_VObject_Component_VEvent|Sabre_VObject_Component_VTodo $parent
+ * @return DateTime|null
+ * @throws Sabre_DAV_Exception
+ */
+function renderCalDavEntry_calcalarm(&$alarm, &$parent)
+{
+ $trigger = $alarm->__get("TRIGGER");
+ if (!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
+ $triggerDuration = Sabre_VObject_DateTimeParser::parseDuration($trigger);
+
+ $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
+
+ if ($related === 'START') {
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $parent->__get("DTSTART");
+ $effectiveTrigger = $dtstart->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ } else {
+ if ($parent->name === 'VTODO') {
+ $endProp = 'DUE';
+ } else {
+ $endProp = 'DTEND';
+ }
+
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $parent->__get("DTSTART");
+ if (isset($parent->$endProp)) {
+ $effectiveTrigger = clone $parent->$endProp->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ } elseif ($parent->__get("DURATION") != "") {
+ $effectiveTrigger = clone $dtstart->getDateTime();
+ $duration = Sabre_VObject_DateTimeParser::parseDuration($parent->__get("DURATION"));
+ $effectiveTrigger->add($duration);
+ $effectiveTrigger->add($triggerDuration);
+ } else {
+ $effectiveTrigger = clone $dtstart->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ }
+ }
+ } else {
+ // ??? @TODO
+ $effectiveTrigger = $trigger->getDateTime();
+ }
+ return $effectiveTrigger;
+}
+
+/**
+ * @param array $calendar
+ * @param array $calendarobject
+ * @throws Sabre_DAV_Exception_BadRequest
+ * @return void
+ */
+function renderCalDavEntry_data(&$calendar, &$calendarobject)
+{
+ /** @var Sabre_VObject_Component_VCalendar $vObject */
+ $vObject = Sabre_VObject_Reader::read($calendarobject["calendardata"]);
+ $componentType = null;
+ /** @var Sabre_VObject_Component_VEvent $component */
+ $component = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
+ break;
+ }
+ }
+ if (!$componentType) {
+ throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ }
+
+
+ if ($componentType !== 'VEVENT') return;
+
+ $event = array(
+ "description" => ($component->__get("DESCRIPTION") ? $component->__get("DESCRIPTION")->value : null),
+ "summary" => ($component->__get("SUMMARY") ? $component->__get("SUMMARY")->value : null),
+ "location" => ($component->__get("LOCATION") ? $component->__get("LOCATION")->value : null),
+ "color" => ($component->__get("X-ANIMEXX-COLOR") ? $component->__get("X-ANIMEXX-COLOR")->value : null),
+ );
+
+ $recurring = ($component->__get("RRULE") ? 1 : 0);
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $component->__get("DTSTART");
+ $allday = ($dtstart->getDateType() == Sabre_VObject_Property_DateTime::DATE ? 1 : 0);
+
+ /** @var array|Sabre_VObject_Component_VAlarm[] $alarms */
+ $alarms = array();
+ foreach ($component->getComponents() as $a_component) if ($a_component->name == "VALARM") {
+ /** var Sabre_VObject_Component_VAlarm $component */
+ $alarms[] = $a_component;
+ }
+
+ $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->__get("UID"));
+ $last_end = 0;
+ $max_ts = mktime(0, 0, 0, 1, 1, CALDAV_MAX_YEAR * 1);
+ $first = true;
+
+ while ($it->valid() && $last_end < $max_ts && ($recurring || $first)) {
+ $first = false;
+ $last_end = $it->getDtEnd()->getTimestamp();
+ $start = $it->getDtStart()->getTimestamp();
+
+ q("INSERT INTO %s%sjqcalendar (`calendar_id`, `calendarobject_id`, `Summary`, `StartTime`, `EndTime`, `IsEditable`, `IsAllDayEvent`, `IsRecurring`, `Color`) VALUES
+ (%d, %d, '%s', '%s', '%s', %d, %d, %d, '%s')", CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
+ IntVal($calendar["id"]), IntVal($calendarobject["id"]), dbesc($event["summary"]), date("Y-m-d H:i:s", $start), date("Y-m-d H:i:s", $last_end),
+ 1, $allday, $recurring, dbesc(substr($event["color"], 1))
+ );
+
+ foreach ($alarms as $alarm) {
+ $alarm = renderCalDavEntry_calcalarm($alarm, $component);
+ $notified = ($alarm->getTimestamp() < time() ? 1 : 0);
+ q("INSERT INTO %s%snotifications (`calendar_id`, `calendarobject_id`, `alert_date`, `notified`) VALUES (%d, %d, '%s', %d)",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["id"]), IntVal($calendarobject["id"]), $alarm->format("Y-m-d H:i:s"), $notified
+ );
+ }
+
+ $it->next();
+ }
+
+ return;
+
+}
+
+
+/**
+ *
+ */
+function renderAllCalDavEntries()
+{
+ q("DELETE FROM %s%sjqcalendar", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+ q("DELETE FROM %s%snotifications", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+ $calendars = q("SELECT * FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+ $anz = count($calendars);
+ $i = 0;
+ foreach ($calendars as $calendar) {
+ $i++;
+ if (($i % 100) == 0) echo "$i / $anz\n";
+ $calobjs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar["id"]));
+ foreach ($calobjs as $calobj) renderCalDavEntry_data($calendar, $calobj);
+ }
+}
+
+
+/**
+ * @param string $uri
+ * @return bool
+ */
+function renderCalDavEntry_uri($uri)
+{
+ $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($uri));
+ if (count($calobj) == 0) return false;
+
+ q("DELETE FROM %s%sjqcalendar WHERE `calendar_id` = %d AND `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]), IntVal($calobj[0]["id"]));
+ q("DELETE FROM %s%snotifications WHERE `calendar_id` = %d AND `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]), IntVal($calobj[0]["id"]));
+
+ $calendars = q("SELECT * FROM %s%scalendars WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]));
+
+ renderCalDavEntry_data($calendars[0], $calobj[0]);
+ return true;
+}
+
+
+/**
+ * @param int $calobj_id
+ * @return bool
+ */
+function renderCalDavEntry_calobj_id($calobj_id)
+{
+ $calobj_id = IntVal($calobj_id);
+ q("DELETE FROM %s%sjqcalendar WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+ q("DELETE FROM %s%snotifications WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+
+ $calobj = q("SELECT * FROM %s%scalendarobjects WHERE `id` = '%d'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calobj_id);
+ if (count($calobj) == 0) return false;
+
+ $calendars = q("SELECT * FROM %s%scalendars WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calobj[0]["calendar_id"]));
+
+ renderCalDavEntry_data($calendars[0], $calobj[0]);
+ return true;
+}
<?php
-abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract {
+abstract class Sabre_CalDAV_Backend_Common extends Sabre_CalDAV_Backend_Abstract
+{
/**
- * List of CalDAV properties, and how they map to database fieldnames
- *
- * Add your own properties by simply adding on to this array
- *
* @var array
*/
- public $propertyMap = array(
- '{DAV:}displayname' => 'displayname',
+ protected $propertyMap = array(
+ '{DAV:}displayname' => 'displayname',
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
- '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
- '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
);
+ /**
+ * @abstract
+ * @return int
+ */
abstract public function getNamespace();
- abstract public function getCalUrlPrefix();
+
+
+ /**
+ * @param int $calendarId
+ * @param string $sd
+ * @param string $ed
+ * @param string $base_path
+ * @return array
+ */
+ abstract public function listItemsByRange($calendarId, $sd, $ed, $base_path);
+
+
+ /**
+ * @var array
+ */
+ static private $calendarCache = array();
/**
- * @param int $namespace
- * @param int $namespace_id
+ * @var array
*/
- protected function increaseCalendarCtag($namespace, $namespace_id) {
- $namespace = IntVal($namespace);
- $namespace_id = IntVal($namespace_id);
+ static private $calendarObjectCache = array();
- q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $namespace, $namespace_id);
+ /**
+ * @static
+ * @param int $calendarId
+ * @return array
+ */
+ static public function loadCalendarById($calendarId)
+ {
+ if (!isset(self::$calendarCache[$calendarId])) {
+ $c = q("SELECT * FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+ self::$calendarCache[$calendarId] = $c[0];
+ }
+ return self::$calendarCache[$calendarId];
}
+ /**
+ * @static
+ * @param int $obj_id
+ * @return array
+ */
+ static public function loadCalendarobjectById($obj_id)
+ {
+ if (!isset(self::$calendarObjectCache[$obj_id])) {
+ $o = q("SELECT * FROM %s%scalendarobjects WHERE `id` = %d",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($obj_id)
+ );
+ self::$calendarObjectCache[$obj_id] = $o[0];
+ }
+ return self::$calendarObjectCache[$obj_id];
+ }
/**
- * Returns a list of calendars for a principal.
- *
- * Every project is an array with the following keys:
- * * id, a unique id that will be used by other functions to modify the
- * calendar. This can be the same as the uri or a database key.
- * * uri, which the basename of the uri with which the calendar is
- * accessed.
- * * principaluri. The owner of the calendar. Almost always the same as
- * principalUri passed to this method.
+ * @static
+ * @param Sabre_VObject_Component_VEvent $component
+ * @return int
+ */
+ public static function getDtEndTimeStamp(&$component)
+ {
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $component->__get("DTSTART");
+ if ($component->__get("DTEND")) {
+ /** @var Sabre_VObject_Property_DateTime $dtend */
+ $dtend = $component->__get("DTEND");
+ return $dtend->getDateTime()->getTimeStamp();
+ } elseif ($component->__get("DURATION")) {
+ $endDate = clone $dtstart->getDateTime();
+ $endDate->add(Sabre_VObject_DateTimeParser::parse($component->__get("DURATION")->value));
+ return $endDate->getTimeStamp();
+ } elseif ($dtstart->getDateType() === Sabre_VObject_Property_DateTime::DATE) {
+ $endDate = clone $dtstart->getDateTime();
+ $endDate->modify('+1 day');
+ return $endDate->getTimeStamp();
+ } else {
+ return $dtstart->getDateTime()->getTimeStamp() + 3600;
+ }
+
+ }
+
+
+ /**
+ * Parses some information from calendar objects, used for optimized
+ * calendar-queries.
*
- * Furthermore it can contain webdav properties in clark notation. A very
- * common one is '{DAV:}displayname'.
+ * Returns an array with the following keys:
+ * * etag
+ * * size
+ * * componentType
+ * * firstOccurence
+ * * lastOccurence
*
- * @param string $principalUri
+ * @param string $calendarData
+ * @throws Sabre_DAV_Exception_BadRequest
* @return array
*/
- public function getCalendarsForUser($principalUri)
+ protected function getDenormalizedData($calendarData)
{
- list(,$name) = Sabre_DAV_URLUtil::splitPath($principalUri);
- $user_id = dav_compat_username2id($name);
-
- $cals = q("SELECT * FROM %s%scalendars WHERE `uid`=%d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $user_id, $this->getNamespace());
- $ret = array();
- foreach ($cals as $cal) {
- $dat = array(
- "id" => $cal["namespace"] . "-" . $cal["namespace_id"],
- "uri" => $this->getCalUrlPrefix() . "-" . $cal["namespace_id"],
- "principaluri" => $principalUri,
- '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag']?$cal['ctag']:'0',
- "calendar_class" => "Sabre_CalDAV_Calendar",
- );
- foreach ($this->propertyMap as $key=>$field) $dat[$key] = $cal[$field];
+ /** @var Sabre_VObject_Component_VEvent $vObject */
+ $vObject = Sabre_VObject_Reader::read($calendarData);
+ $componentType = null;
+ $component = null;
+ $firstOccurence = null;
+ $lastOccurence = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
+ break;
+ }
+ }
+ if (!$componentType) {
+ throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ }
+ if ($componentType === 'VEVENT') {
+ /** @var Sabre_VObject_Component_VEvent $component */
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $component->__get("DTSTART");
+ $firstOccurence = $dtstart->getDateTime()->getTimeStamp();
+ // Finding the last occurence is a bit harder
+ if (!$component->__get("RRULE")) {
+ $lastOccurence = self::getDtEndTimeStamp($component);
+ } else {
+ $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->__get("UID"));
+ $maxDate = new DateTime(CALDAV_MAX_YEAR . "-01-01");
+ if ($it->isInfinite()) {
+ $lastOccurence = $maxDate->getTimeStamp();
+ } else {
+ $end = $it->getDtEnd();
+ while ($it->valid() && $end < $maxDate) {
+ $end = $it->getDtEnd();
+ $it->next();
+
+ }
+ $lastOccurence = $end->getTimeStamp();
+ }
- $ret[] = $dat;
+ }
}
- return $ret;
+ return array(
+ 'etag' => md5($calendarData),
+ 'size' => strlen($calendarData),
+ 'componentType' => $componentType,
+ 'firstOccurence' => $firstOccurence,
+ 'lastOccurence' => $lastOccurence,
+ );
+
}
/**
* @param array $mutations
* @return bool|array
*/
- public function updateCalendar($calendarId, array $mutations) {
+ public function updateCalendar($calendarId, array $mutations)
+ {
$newValues = array();
- $result = array(
+ $result = array(
200 => array(), // Ok
403 => array(), // Forbidden
424 => array(), // Failed Dependency
$hasError = false;
- foreach($mutations as $propertyName=>$propertyValue) {
+ foreach ($mutations as $propertyName=> $propertyValue) {
// We don't know about this property.
if (!isset($this->propertyMap[$propertyName])) {
- $hasError = true;
+ $hasError = true;
$result[403][$propertyName] = null;
unset($mutations[$propertyName]);
continue;
}
- $fieldName = $this->propertyMap[$propertyName];
+ $fieldName = $this->propertyMap[$propertyName];
$newValues[$fieldName] = $propertyValue;
}
// If there were any errors we need to fail the request
if ($hasError) {
// Properties has the remaining properties
- foreach($mutations as $propertyName=>$propertyValue) {
+ foreach ($mutations as $propertyName=> $propertyValue) {
$result[424][$propertyName] = null;
}
// Removing unused statuscodes for cleanliness
- foreach($result as $status=>$properties) {
- if (is_array($properties) && count($properties)===0) unset($result[$status]);
+ foreach ($result as $status=> $properties) {
+ if (is_array($properties) && count($properties) === 0) unset($result[$status]);
}
return $result;
}
- $x = explode("-", $calendarId);
-
- $this->increaseCalendarCtag($x[0], $x[1]);
+ $this->increaseCalendarCtag($calendarId);
$valuesSql = array();
- foreach($newValues as $fieldName=>$value) $valuesSql[] = "`" . $fieldName . "` = '" . dbesc($value) . "'";
+ foreach ($newValues as $fieldName=> $value) $valuesSql[] = "`" . $fieldName . "` = '" . dbesc($value) . "'";
if (count($valuesSql) > 0) {
- q("UPDATE %s%scalendars SET " . implode(", ", $valuesSql) . " WHERE `namespace` = %d AND `namespace_id` = %d",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1])
- );
+ q("UPDATE %s%scalendars SET " . implode(", ", $valuesSql) . " WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
}
return true;
}
+ /**
+ * @param int $calendarId
+ */
+ protected function increaseCalendarCtag($calendarId)
+ {
+ q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `id` = '%d'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+ self::$calendarObjectCache = array();
+ }
+
+ /**
+ * @abstract
+ * @param int $calendar_id
+ * @param int $calendarobject_id
+ * @return string
+ */
+ abstract function getItemDetailRedirect($calendar_id, $calendarobject_id);
+
}
\ No newline at end of file
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Backend_Private extends Sabre_CalDAV_Backend_Common
+{
+
+
+ /**
+ * @var null|Sabre_CalDAV_Backend_Private
+ */
+ private static $instance = null;
+
+ /**
+ * @static
+ * @return Sabre_CalDAV_Backend_Private
+ */
+ public static function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new Sabre_CalDAV_Backend_Private();
+ }
+ return self::$instance;
+ }
+
+
+ /**
+ * @return int
+ */
+ public function getNamespace()
+ {
+ return CALDAV_NAMESPACE_PRIVATE;
+ }
+
+
+ /**
+ * @obsolete
+ * @param array $calendar
+ * @param int $user
+ * @return array
+ */
+ public function getPermissionsCalendar($calendar, $user)
+ {
+ if ($calendar["namespace"] == CALDAV_NAMESPACE_PRIVATE && $user == $calendar["namespace_id"]) return array("read"=> true, "write"=> true);
+ return array("read"=> false, "write"=> false);
+ }
+
+ /**
+ * @obsolete
+ * @param array $calendar
+ * @param int $user
+ * @param string $calendarobject_id
+ * @param null|array $item_arr
+ * @return array
+ */
+ public function getPermissionsItem($calendar, $user, $calendarobject_id, $item_arr = null)
+ {
+ return $this->getPermissionsCalendar($calendar, $user);
+ }
+
+
+ /**
+ * @param array $row
+ * @param array $calendar
+ * @param string $base_path
+ * @return array
+ */
+ private function jqcal2wdcal($row, $calendar, $base_path)
+ {
+ $not = q("SELECT COUNT(*) num FROM %s%snotifications WHERE `calendar_id` = %d AND `calendarobject_id` = %d",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($row["calendar_id"]), IntVal($row["calendarobject_id"])
+ );
+ $editable = $this->getPermissionsItem($calendar["namespace_id"], $row["calendarobject_id"], $row);
+
+ $end = wdcal_mySql2PhpTime($row["EndTime"]);
+ if ($row["IsAllDayEvent"]) $end -= 1;
+
+ return array(
+ "jq_id" => $row["id"],
+ "ev_id" => $row["calendarobject_id"],
+ "summary" => escape_tags($row["Summary"]),
+ "start" => wdcal_mySql2PhpTime($row["StartTime"]),
+ "end" => $end,
+ "is_allday" => $row["IsAllDayEvent"],
+ "is_moredays" => 0,
+ "is_recurring" => $row["IsRecurring"],
+ "color" => (is_null($row["Color"]) || $row["Color"] == "" ? $calendar["calendarcolor"] : $row["Color"]),
+ "is_editable" => ($editable ? 1 : 0),
+ "is_editable_quick" => ($editable && !$row["IsRecurring"] ? 1 : 0),
+ "location" => "Loc.",
+ "attendees" => '',
+ "has_notification" => ($not[0]["num"] > 0 ? 1 : 0),
+ "url_detail" => $base_path . $row["calendarobject_id"] . "/",
+ "url_edit" => $base_path . $row["calendarobject_id"] . "/edit/",
+ "special_type" => "",
+ );
+ }
+
+ /**
+ * @param int $calendarId
+ * @param string $sd
+ * @param string $ed
+ * @param string $base_path
+ * @return array
+ */
+ public function listItemsByRange($calendarId, $sd, $ed, $base_path)
+ {
+ $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+ $von = wdcal_php2MySqlTime($sd);
+ $bis = wdcal_php2MySqlTime($ed);
+
+ // @TODO Events, die früher angefangen haben, aber noch andauern
+ $evs = q("SELECT * FROM %s%sjqcalendar WHERE `calendar_id` = %d AND `starttime` between '%s' and '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
+ IntVal($calendarId), dbesc($von), dbesc($bis));
+
+ $events = array();
+ foreach ($evs as $row) $events[] = $this->jqcal2wdcal($row, $calendar, $base_path . $row["calendar_id"] . "/");
+
+ return $events;
+ }
+
+
+ /**
+ * @param int $calendar_id
+ * @param int $calendarobject_id
+ * @return string
+ */
+ public function getItemDetailRedirect($calendar_id, $calendarobject_id)
+ {
+ return "/dav/wdcal/$calendar_id/$calendarobject_id/edit/";
+ }
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri, which the basename of the uri with which the calendar is
+ * accessed.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ public function getCalendarsForUser($principalUri)
+ {
+ $n = dav_compat_principal2namespace($principalUri);
+ if ($n["namespace"] != $this->getNamespace()) return array();
+
+ $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"]));
+ $ret = array();
+ foreach ($cals as $cal) {
+ if (in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue;
+
+ $dat = array(
+ "id" => $cal["id"],
+ "uri" => $cal["uri"],
+ "principaluri" => $principalUri,
+ '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag'] ? $cal['ctag'] : '0',
+ "calendar_class" => "Sabre_CalDAV_Calendar",
+ );
+ foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field];
+
+ $ret[] = $dat;
+ }
+
+ return $ret;
+ }
+
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @throws Sabre_DAV_Exception
+ * @return string|void
+ */
+ public function createCalendar($principalUri, $calendarUri, array $properties)
+ {
+
+ $uid = dav_compat_principal2uid($principalUri);
+
+ $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, dbesc($calendarUri));
+ if (count($r) > 0) throw new Sabre_DAV_Exception("A calendar with this URI already exists");
+
+ $keys = array("`namespace`", "`namespace_id`", "`ctag`", "`uri`");
+ $vals = array(CALDAV_NAMESPACE_PRIVATE, IntVal($uid), 1, "'" . dbesc($calendarUri) . "'");
+
+ // Default value
+ $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+ $has_vevent = $has_vtodo = 1;
+ if (isset($properties[$sccs])) {
+ if (!($properties[$sccs] instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet)) {
+ throw new Sabre_DAV_Exception('The ' . $sccs . ' property must be of type: Sabre_CalDAV_Property_SupportedCalendarComponentSet');
+ }
+ $v = $properties[$sccs]->getValue();
+ $has_vevent = $has_vtodo = 0;
+ foreach ($v as $w) {
+ if (mb_strtolower($w) == "vevent") $has_vevent = 1;
+ if (mb_strtolower($w) == "vtodo") $has_vtodo = 1;
+ }
+ }
+ $keys[] = "`has_vevent`";
+ $keys[] = "`has_vtodo`";
+ $vals[] = $has_vevent;
+ $vals[] = $has_vtodo;
+
+ foreach ($this->propertyMap as $xmlName=> $dbName) {
+ if (isset($properties[$xmlName])) {
+ $keys[] = "`$dbName`";
+ $vals[] = "'" . dbesc($properties[$xmlName]) . "'";
+ }
+ }
+
+ $sql = sprintf("INSERT INTO %s%scalendars (" . implode(', ', $keys) . ") VALUES (" . implode(', ', $vals) . ")", CALDAV_SQL_DB, CALDAV_SQL_PREFIX);
+
+ q($sql);
+
+ $x = q("SELECT id FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $uid, $calendarUri
+ );
+ return $x[0]["id"];
+
+ }
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The mutations array uses the propertyName in clark-notation as key,
+ * and the array value for the property value. In the case a property
+ * should be deleted, the property value will be null.
+ *
+ * This method must be atomic. If one property cannot be changed, the
+ * entire operation must fail.
+ *
+ * If the operation was successful, true can be returned.
+ * If the operation failed, false can be returned.
+ *
+ * Deletion of a non-existent property is always successful.
+ *
+ * Lastly, it is optional to return detailed information about any
+ * failures. In this case an array should be returned with the following
+ * structure:
+ *
+ * array(
+ * 403 => array(
+ * '{DAV:}displayname' => null,
+ * ),
+ * 424 => array(
+ * '{DAV:}owner' => null,
+ * )
+ * )
+ *
+ * In this example it was forbidden to update {DAV:}displayname.
+ * (403 Forbidden), which in turn also caused {DAV:}owner to fail
+ * (424 Failed Dependency) because the request needs to be atomic.
+ *
+ * @param string $calendarId
+ * @param array $mutations
+ * @return bool|array
+ */
+ public function updateCalendar($calendarId, array $mutations)
+ {
+
+ $newValues = array();
+ $result = array(
+ 200 => array(), // Ok
+ 403 => array(), // Forbidden
+ 424 => array(), // Failed Dependency
+ );
+
+ $hasError = false;
+
+ foreach ($mutations as $propertyName=> $propertyValue) {
+
+ // We don't know about this property.
+ if (!isset($this->propertyMap[$propertyName])) {
+ $hasError = true;
+ $result[403][$propertyName] = null;
+ unset($mutations[$propertyName]);
+ continue;
+ }
+
+ $fieldName = $this->propertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+
+ }
+
+ // If there were any errors we need to fail the request
+ if ($hasError) {
+ // Properties has the remaining properties
+ foreach ($mutations as $propertyName=> $propertyValue) {
+ $result[424][$propertyName] = null;
+ }
+
+ // Removing unused statuscodes for cleanliness
+ foreach ($result as $status=> $properties) {
+ if (is_array($properties) && count($properties) === 0) unset($result[$status]);
+ }
+
+ return $result;
+
+ }
+
+ $sql = "`ctag` = `ctag` + 1";
+ foreach ($newValues as $key=> $val) $sql .= ", `" . $key . "` = '" . dbesc($val) . "'";
+
+ $sql = sprintf("UPDATE %s%scalendars SET $sql WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+ q($sql);
+
+ return true;
+
+ }
+
+
+ /**
+ * Delete a calendar and all it's objects
+ *
+ * @param string $calendarId
+ * @return void
+ */
+ public function deleteCalendar($calendarId)
+ {
+ q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+ q("DELETE FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+ }
+
+
+ /**
+ * Returns all calendar objects within a calendar.
+ *
+ * Every item contains an array with the following keys:
+ * * id - unique identifier which will be used for subsequent updates
+ * * calendardata - The iCalendar-compatible calendar data
+ * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
+ * * lastmodified - a timestamp of the last modification time
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+ * ' "abcdef"')
+ * * calendarid - The calendarid as it was passed to this function.
+ * * size - The size of the calendar objects, in bytes.
+ *
+ * Note that the etag is optional, but it's highly encouraged to return for
+ * speed reasons.
+ *
+ * The calendardata is also optional. If it's not returned
+ * 'getCalendarObject' will be called later, which *is* expected to return
+ * calendardata.
+ *
+ * If neither etag or size are specified, the calendardata will be
+ * used/fetched to determine these numbers. If both are specified the
+ * amount of times this is needed is reduced by a great degree.
+ *
+ * @param mixed $calendarId
+ * @return array
+ */
+ function getCalendarObjects($calendarId)
+ {
+ $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+ $ret = array();
+ foreach ($objs as $obj) {
+ $ret[] = array(
+ "id" => IntVal($obj["id"]),
+ "calendardata" => $obj["calendardata"],
+ "uri" => $obj["uri"],
+ "lastmodified" => $obj["lastmodified"],
+ "calendarid" => $calendarId,
+ "etag" => $obj["etag"],
+ "size" => IntVal($obj["size"]),
+ );
+ }
+ return $ret;
+ }
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return array
+ */
+ function getCalendarObject($calendarId, $objectUri)
+ {
+ $o = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+ if (count($o) > 0) {
+ $o[0]["calendarid"] = $calendarId;
+ $o[0]["calendardata"] = str_ireplace("Europe/Belgrade", "Europe/Berlin", $o[0]["calendardata"]);
+ return $o[0];
+ } else throw new Sabre_DAV_Exception_NotFound($calendarId . " / " . $objectUri);
+ }
+
+ /**
+ * Creates a new calendar object.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function createCalendarObject($calendarId, $objectUri, $calendarData)
+ {
+
+ $calendarData = icalendar_sanitize_string($calendarData);
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ q("INSERT INTO %s%scalendarobjects (`calendar_id`, `uri`, `calendardata`, `lastmodified`, `componentType`, `firstOccurence`, `lastOccurence`, `etag`, `size`)
+ VALUES (%d, '%s', '%s', NOW(), '%s', '%s', '%s', '%s', %d)",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri), addslashes($calendarData), dbesc($extraData['componentType']),
+ dbesc(wdcal_php2MySqlTime($extraData['firstOccurence'])), dbesc(wdcal_php2MySqlTime($extraData['lastOccurence'])), dbesc($extraData["etag"]), IntVal($extraData["size"])
+ );
+
+ $this->increaseCalendarCtag($calendarId);
+ renderCalDavEntry_uri($objectUri);
+
+ return '"' . $extraData['etag'] . '"';
+ }
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function updateCalendarObject($calendarId, $objectUri, $calendarData)
+ {
+ $calendarData = icalendar_sanitize_string($calendarData);
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ q("UPDATE %s%scalendarobjects SET `calendardata` = '%s', `lastmodified` = NOW(), `etag` = '%s', `size` = %d, `componentType` = '%s', `firstOccurence` = '%s', `lastOccurence` = '%s'
+ WHERE `calendar_id` = %d AND `uri` = '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($calendarData), dbesc($extraData["etag"]), IntVal($extraData["size"]), dbesc($extraData["componentType"]),
+ dbesc(wdcal_php2MySqlTime($extraData["firstOccurence"])), dbesc(wdcal_php2MySqlTime($extraData["lastOccurence"])), IntVal($calendarId), dbesc($objectUri));
+
+ $this->increaseCalendarCtag($calendarId);
+ renderCalDavEntry_uri($objectUri);
+
+ return '"' . $extraData['etag'] . '"';
+ }
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri)
+ {
+ $r = q("SELECT `id` FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+ if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
+
+ q("DELETE FROM %s%scalendarobjects WHERE `calendar_id` = %d AND `uri` = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId), dbesc($objectUri));
+
+ $this->increaseCalendarCtag($calendarId);
+ renderCalDavEntry_calobj_id($r[0]["id"]);
+ }
+}
--- /dev/null
+<?php
+
+abstract class Sabre_CalDAV_Backend_Virtual extends Sabre_CalDAV_Backend_Common
+{
+
+
+
+ /**
+ * @static
+ * @abstract
+ * @param int $calendarId
+ * @param string $uri
+ * @return array
+ */
+ /*
+ abstract public function getItemsByUri($calendarId, $uri);
+ */
+
+ /**
+ * @static
+ * @param int $uid
+ * @param int $namespace
+ */
+ static public function invalidateCache($uid = 0, $namespace = 0) {
+ q("DELETE FROM %s%scal_virtual_object_sync WHERE `uid` = %d AND `namespace` = %d",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid), IntVal($namespace));
+ }
+
+ /**
+ * @static
+ * @abstract
+ * @param int $calendarId
+ */
+ static abstract protected function createCache_internal($calendarId);
+
+ /**
+ * @static
+ * @param int $calendarId
+ */
+ static protected function createCache($calendarId) {
+ $calendarId = IntVal($calendarId);
+ q("DELETE FROM %s%scal_virtual_object_cache WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+ static::createCache_internal($calendarId);
+ q("REPLACE INTO %s%scal_virtual_object_sync (`calendar_id`, `date`) VALUES (%d, NOW())", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+ }
+
+ /**
+ * @param string $calendarId
+ * @return array
+ */
+ public function getCalendarObjects($calendarId)
+ {
+ $calendarId = IntVal($calendarId);
+ $r = q("SELECT COUNT(*) n FROM %s%scal_virtual_object_sync WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+
+ if ($r[0]["n"] == 0) static::createCache($calendarId);
+
+ $r = q("SELECT * FROM %s%scal_virtual_object_cache WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId);
+
+ $ret = array();
+ foreach ($r as $obj) {
+ $ret[] = array(
+ "id" => IntVal($obj["data_uri"]),
+ "calendardata" => $obj["calendardata"],
+ "uri" => $obj["data_uri"],
+ "lastmodified" => $obj["date"],
+ "calendarid" => $calendarId,
+ "etag" => $obj["etag"],
+ "size" => IntVal($obj["size"]),
+ );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return array
+ */
+ public function getCalendarObject($calendarId, $objectUri)
+ {
+ $calendarId = IntVal($calendarId);
+ $r = q("SELECT COUNT(*) n FROM %s%scal_virtual_object_sync WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendarId));
+
+ if ($r[0]["n"] == 0) static::createCache($calendarId);
+
+ $r = q("SELECT * FROM %s%scal_virtual_object_cache WHERE `data_uri` = '%s' AND `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($objectUri), IntVal($calendarId));
+ if (count($r) == 0) throw new Sabre_DAV_Exception_NotFound();
+
+ $obj = $r[0];
+ $ret = array(
+ "id" => IntVal($obj["data_uri"]),
+ "calendardata" => $obj["calendardata"],
+ "uri" => $obj["data_uri"],
+ "lastmodified" => $obj["date"],
+ "calendarid" => $calendarId,
+ "etag" => $obj["etag"],
+ "size" => IntVal($obj["size"]),
+ );
+ return $ret;
+ }
+
+
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return void
+ */
+ public function createCalendar($principalUri, $calendarUri, array $properties)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Delete a calendar and all it's objects
+ *
+ * @param string $calendarId
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return void
+ */
+ public function deleteCalendar($calendarId)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+
+ /**
+ * Creates a new calendar object.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return null|string|void
+ */
+ function createCalendarObject($calendarId, $objectUri, $calendarData)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return null|string|void
+ */
+ function updateCalendarObject($calendarId, $objectUri, $calendarData)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+
+}
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Calendar_Virtual extends Sabre_CalDAV_Calendar {
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ public function getACL() {
+
+ return array(
+ array(
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ),
+ array(
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ),
+ array(
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
+ 'protected' => true,
+ ),
+ array(
+ 'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ ),
+
+ );
+
+ }
+}
class Sabre_CardDAV_Backend_Std extends Sabre_CardDAV_Backend_Abstract
{
+ /**
+ * @var null|Sabre_CardDAV_Backend_Std
+ */
+ private static $instance = null;
+
+ /**
+ * @static
+ * @return Sabre_CardDAV_Backend_Std
+ */
+ public static function getInstance() {
+ if (self::$instance == null) {
+ self::$instance = new Sabre_CardDAV_Backend_Std();
+ }
+ return self::$instance;
+ }
+
+
/**
* Sets up the object
*/
<?php
-/**
- * The UserCalenders class contains all calendars associated to one user
- *
- * @package Sabre
- * @subpackage CalDAV
- * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
- * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
- */
+
class Sabre_CalDAV_AnimexxUserCalendars implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
/**
/**
* CalDAV backends
*
- * @var array|Sabre_CalDAV_Backend_Abstract[]
+ * @var array|Sabre_CalDAV_Backend_Common[]
*/
protected $caldavBackends;
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
- * @param array|Sabre_CalDAV_Backend_Abstract $caldavBackends
+ * @param array|Sabre_CalDAV_Backend_Common[] $caldavBackends
* @param mixed $userUri
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, $caldavBackends, $userUri) {
* Returns a single calendar, by name
*
* @param string $name
- * @throws Sabre_DAV_Exception_FileNotFound
+ * @throws Sabre_DAV_Exception_NotFound
* @todo needs optimizing
* @return \Sabre_CalDAV_Calendar|\Sabre_DAV_INode
*/
return $child;
}
- throw new Sabre_DAV_Exception_FileNotFound('Calendar with name \'' . $name . '\' could not be found');
+ throw new Sabre_DAV_Exception_NotFound('Calendar with name \'' . $name . '\' could not be found');
}
function wdcal_edit_checktime_startChanged() {
"use strict";
+
var time = wdcal_edit_getStartEnd();
if (time.start.getTime() >= time.end.getTime()) {
var newend = new Date(time.start.getTime() + 3600000);
$("#cal_end_date").datepicker("setDate", newend);
$.timePicker("#cal_end_time").setTime(newend);
}
+ wdcal_edit_recur_recalc();
}
function wdcal_edit_checktime_endChanged() {
"use strict";
+
var time = wdcal_edit_getStartEnd();
if (time.start.getTime() >= time.end.getTime()) {
var newstart = new Date(time.end.getTime() - 3600000);
}
}
-function wdcal_edit_init(dateFormat) {
+function wdcal_edit_recur_recalc() {
+ "use strict";
+
+ var start = $("#cal_start_date").datepicker("getDate");
+ $(".rec_month_name").text($.datepicker._defaults.monthNames[start.getMonth()]);
+ $("#rec_yearly_day option[value=bymonthday], #rec_monthly_day option[value=bymonthday]").text($("#rec_yearly_day option[value=bymonthday]").data("orig").replace("#num#", start.getDate()));
+ var month = new Date(start.getFullYear(), start.getMonth() + 1, 0);
+ var monthlast = month.getDate() - start.getDate() + 1;
+ $("#rec_yearly_day option[value=bymonthday_neg], #rec_monthly_day option[value=bymonthday_neg]").text($("#rec_yearly_day option[value=bymonthday_neg]").data("orig").replace("#num#", monthlast));
+ var wk = Math.ceil(start.getDate() / 7);
+ var wkname = $.datepicker._defaults.dayNames[start.getDay()];
+ $("#rec_yearly_day option[value=byday], #rec_monthly_day option[value=byday]").text(
+ $("#rec_yearly_day option[value=byday]").data("orig").replace("#num#", wk).replace("#wkday#", wkname)
+ );
+ var wk_inv = Math.ceil(monthlast / 7);
+ $("#rec_yearly_day option[value=byday_neg], #rec_monthly_day option[value=byday_neg]").text(
+ $("#rec_yearly_day option[value=byday_neg]").data("orig").replace("#num#", wk_inv).replace("#wkday#", wkname)
+ );
+}
+
+function wdcal_edit_init(dateFormat, base_path) {
"use strict";
$("#cal_color").colorPicker();
"dateFormat": dateFormat
}).on("change", wdcal_edit_checktime_endChanged);
+ $("#rec_until_date").datepicker({ "dateFormat": dateFormat });
+
$("#notification").on("click change", function() {
if ($(this).prop("checked")) $("#notification_detail").show();
else ($("#notification_detail")).hide();
if ($(this).prop("checked")) $("#cal_end_time, #cal_start_time").hide();
else $("#cal_end_time, #cal_start_time").show();
}).change();
+
+ $("#rec_frequency").on("click change", function() {
+ var val = $("#rec_frequency").val();
+ if (val == "") $("#rec_details").hide();
+ else $("#rec_details").show();
+
+ if (val == "daily") $(".rec_daily").show();
+ else $(".rec_daily").hide();
+
+ if (val == "weekly") $(".rec_weekly").show();
+ else $(".rec_weekly").hide();
+
+ if (val == "monthly") $(".rec_monthly").show();
+ else $(".rec_monthly").hide();
+
+ if (val == "yearly") $(".rec_yearly").show();
+ else $(".rec_yearly").hide();
+ }).change();
+
+ $("#rec_until_type").on("click change", function() {
+ var val = $("#rec_until_type").val();
+
+ if (val == "count") $("#rec_until_count").show();
+ else $("#rec_until_count").hide();
+
+ if (val == "date") $("#rec_until_date").show();
+ else $("#rec_until_date").hide();
+ }).change();
+
+ $("#rec_yearly_day option, #rec_monthly_day option").each(function() {
+ $(this).data("orig", $(this).text());
+ });
+
+ wdcal_edit_recur_recalc();
+
+ $(document).on("click", ".exception_remover", function(ev) {
+ ev.preventDefault();
+ var $this = $(this),
+ $par = $this.parents(".rec_exceptions");
+ $this.parents(".except").remove();
+ if ($par.find(".rec_exceptions_holder").children().length == 0) {
+ $par.find(".rec_exceptions_holder").hide();
+ $par.find(".rec_exceptions_none").show();
+ }
+ });
+
+ $(".exception_adder").click(function(ev) {
+ ev.preventDefault();
+
+ var exceptions = [];
+ $(".rec_exceptions .except input").each(function() {
+ exceptions.push($(this).val());
+ });
+ var rec_weekly_byday = [];
+ $(".rec_weekly_byday:checked").each(function() {
+ rec_weekly_byday.push($(this).val());
+ });
+ var rec_daily_byday = [];
+ $(".rec_daily_byday:checked").each(function() {
+ rec_daily_byday.push($(this).val());
+ });
+ var opts = {
+ "start_date": $("input[name=start_date]").val(),
+ "start_time": $("input[name=start_time]").val(),
+ "end_date": $("input[name=end_date]").val(),
+ "end_time": $("input[name=end_time]").val(),
+ "rec_frequency": $("#rec_frequency").val(),
+ "rec_interval": $("#rec_interval").val(),
+ "rec_until_type": $("#rec_until_type").val(),
+ "rec_until_count": $("#rec_until_count").val(),
+ "rec_until_date": $("#rec_until_date").val(),
+ "rec_weekly_byday": rec_weekly_byday,
+ "rec_daily_byday": rec_daily_byday,
+ "rec_weekly_wkst": $("input[name=rec_weekly_wkst]:checked").val(),
+ "rec_monthly_day": $("#rec_monthly_day").val(),
+ "rec_yearly_day": $("#rec_yearly_day").val(),
+ "rec_exceptions": exceptions
+ };
+ if ($("#cal_allday").prop("checked")) opts["allday"] = 1;
+ var $dial = $("<div id='exception_setter_dialog'>Loading...</div>");
+ $dial.appendTo("body");
+ $dial.dialog({
+ "width": 400,
+ "height": 300,
+ "title": "Exceptions"
+ });
+ $dial.load(base_path + "getExceptionDates/", opts, function() {
+ $dial.find(".exception_selector_link").click(function(ev2) {
+ ev2.preventDefault();
+ var ts = $(this).data("timestamp");
+ var str = $(this).html();
+ var $part = $("<div data-timestamp='" + ts + "' class='except'><input type='hidden' class='rec_exception' name='rec_exceptions[]' value='" + ts + "'><a href='#' class='exception_remover'>[remove]</a> " + str + "</div>");
+ var found = false;
+ $(".rec_exceptions_holder .except").each(function() {
+ if (!found && ts < $(this).data("timestamp")) {
+ found = true;
+ $part.insertBefore(this);
+ }
+ });
+ if (!found) $(".rec_exceptions_holder").append($part);
+ $(".rec_exceptions .rec_exceptions_holder").show();
+ $(".rec_exceptions .rec_exceptions_none").hide();
+
+ $dial.dialog("destroy").remove();
+ })
+ });
+ });
}
\ No newline at end of file
(function ($) {\r
"use strict";\r
\r
- var __WDAY = new Array(i18n.xgcalendar.dateformat.sun, i18n.xgcalendar.dateformat.mon, i18n.xgcalendar.dateformat.tue, i18n.xgcalendar.dateformat.wed, i18n.xgcalendar.dateformat.thu, i18n.xgcalendar.dateformat.fri, i18n.xgcalendar.dateformat.sat);\r
- var __MonthName = new Array(i18n.xgcalendar.dateformat.jan, i18n.xgcalendar.dateformat.feb, i18n.xgcalendar.dateformat.mar, i18n.xgcalendar.dateformat.apr, i18n.xgcalendar.dateformat.may, i18n.xgcalendar.dateformat.jun, i18n.xgcalendar.dateformat.jul, i18n.xgcalendar.dateformat.aug, i18n.xgcalendar.dateformat.sep, i18n.xgcalendar.dateformat.oct, i18n.xgcalendar.dateformat.nov, i18n.xgcalendar.dateformat.dec);\r
+ var __WDAY = $.datepicker._defaults.dayNamesShort;\r
+ //new Array(i18n.xgcalendar.dateformat.sun, i18n.xgcalendar.dateformat.mon, i18n.xgcalendar.dateformat.tue, i18n.xgcalendar.dateformat.wed, i18n.xgcalendar.dateformat.thu, i18n.xgcalendar.dateformat.fri, i18n.xgcalendar.dateformat.sat);\r
+ var __MonthName = $.datepicker._defaults.monthNamesShort;\r
+ //new Array(i18n.xgcalendar.dateformat.jan, i18n.xgcalendar.dateformat.feb, i18n.xgcalendar.dateformat.mar, i18n.xgcalendar.dateformat.apr, i18n.xgcalendar.dateformat.may, i18n.xgcalendar.dateformat.jun, i18n.xgcalendar.dateformat.jul, i18n.xgcalendar.dateformat.aug, i18n.xgcalendar.dateformat.sep, i18n.xgcalendar.dateformat.oct, i18n.xgcalendar.dateformat.nov, i18n.xgcalendar.dateformat.dec);\r
\r
\r
function dateFormat(format) {\r
for (i = 0; i < l; i++) {\r
var $col = $container.find(".tgCol" + i);\r
for (var j = 0; j < events[i].length; j++) {\r
- if (events[i][j].event["color"] && events[i][j].event["color"].match(/^#[0-9a-f]{6}$/i)) {\r
- c = events[i][j].event["color"];\r
+ if (events[i][j].event["color"] && events[i][j].event["color"].match(/^[0-9a-f]{6}$/i)) {\r
+ c = "#" + events[i][j].event["color"];\r
}\r
else {\r
c = option.std_color;\r
function getTitle(event) {\r
var timeshow, eventshow;\r
var showtime = event["is_allday"] != 1;\r
- eventshow = event["subject"];\r
+ eventshow = event["summary"];\r
var startformat = getymformat(event["start"], null, showtime, true);\r
var endformat = getymformat(event["end"], event["start"], showtime, true);\r
timeshow = dateFormat.call(event["start"], startformat) + " - " + dateFormat.call(event["end"], endformat);\r
var p = { bdcolor:theme[0], bgcolor2:theme[0], bgcolor1:theme[2], width:"70%", icon:"", title:"", data:"" };\r
p.starttime = pZero(e.st.hour) + ":" + pZero(e.st.minute);\r
p.endtime = pZero(e.et.hour) + ":" + pZero(e.et.minute);\r
- p.content = e.event["subject"];\r
+ p.content = e.event["summary"];\r
p.title = getTitle(e.event);\r
var icons = [];\r
if (e.event["has_notification"] == 1) icons.push("<I class=\"cic cic-tmr\"> </I>");\r
var p = { color:theme[2], title:"", extendClass:"", extendHTML:"", data:"" };\r
\r
p.title = getTitle(e.event);\r
- p.id = "bbit_cal_event_" + e.event["uri"];\r
+ p.id = "bbit_cal_event_" + e.event["jq_id"];\r
if (option.enableDrag && e.event["is_editable_quick"] == 1) {\r
p.eclass = "drag";\r
}\r
else {\r
- p.eclass = "cal_" + e.event["uri"];\r
+ p.eclass = "cal_" + e.event["jq_id"];\r
}\r
p.eclass += " " + (e.event["is_editable"] ? "editable" : "not_editable");\r
var sp = "<span style=\"cursor: pointer\">{content}</span>";\r
}\r
var cen;\r
if (!e.allday && !sf) {\r
- cen = pZero(e.st.hour) + ":" + pZero(e.st.minute) + " " + e.event["subject"];\r
+ cen = pZero(e.st.hour) + ":" + pZero(e.st.minute) + " " + e.event["summary"];\r
}\r
else {\r
- cen = e.event["subject"];\r
+ cen = e.event["summary"];\r
}\r
var content = [];\r
if (cen.indexOf("Geburtstag:") == 0) {\r
}\r
if (option.eventItems[i]["start"] >= es) {\r
for (var j = 0; j < jl; j++) {\r
- if (option.eventItems[i]["uri"] == events[j]["uri"] && option.eventItems[i]["start"] < start) {\r
+ if (option.eventItems[i]["jq_id"] == events[j]["jq_id"] && option.eventItems[i]["start"] < start) {\r
events.splice(j, 1); //for duplicated event\r
jl--;\r
break;\r
$("#bbit-cs-buddle").css("visibility", "hidden");\r
var calid = $("#bbit-cs-id").val();\r
var param = [\r
- { "name":"calendarId", value:calid },\r
+ { "name":"jq_id", value:calid },\r
{ "name":"type", value:type}\r
];\r
var de = rebyKey(calid, true);\r
var location = "";\r
if (data["location"] != "") location = data["location"] + ", ";\r
$("#bbit-cs-buddle-timeshow").html(location + ss.join(""));\r
- $bud.find(".bbit-cs-what").html(data["subject"]).attr("href", data["url_detail"]);\r
- $("#bbit-cs-id").val(data["uri"]);\r
+ $bud.find(".bbit-cs-what").html(data["summary"]).attr("href", data["url_detail"]);\r
+ $("#bbit-cs-id").val(data["jq_id"]);\r
$bud.data("cdata", data);\r
$bud.css({ "visibility":"visible", left:pos.left, top:pos.top });\r
\r
return false;\r
}\r
option.isloading = true;\r
- var id = data["uri"];\r
+ var id = data["jq_id"];\r
var os = data["start"];\r
var od = data["end"];\r
var param = [\r
- { "name":"calendarId", value:id },\r
+ { "name":"jq_id", value:id },\r
{ "name":"CalendarStartTime", value:Math.floor(start.getTime() / 1000) },\r
{ "name":"CalendarEndTime", value:Math.floor(end.getTime() / 1000) }\r
];\r
temparr.push('<table class="cb-table"><tbody><tr><th class="cb-key">');\r
temparr.push(i18n.xgcalendar.time, ':</th><td class=cb-value><div id="bbit-cal-buddle-timeshow"></div></td></tr><tr><th class="cb-key">');\r
temparr.push(i18n.xgcalendar.content, ':</th><td class="cb-value"><div class="textbox-fill-wrapper"><div class="textbox-fill-mid"><input id="bbit-cal-what" class="textbox-fill-input"/></div></div><div class="cb-example">');\r
- temparr.push(i18n.xgcalendar.example, '</div></td></tr></tbody></table><input id="bbit-cal-start" type="hidden"/><input id="bbit-cal-end" type="hidden"/><input id="bbit-cal-allday" type="hidden"/><input id="bbit-cal-quickAddBTN" value="');\r
+ temparr.push(i18n.xgcalendar.example, '</div></td></tr></tbody></table><input id="bbit-cal-start" type="hidden"/><input id="bbit-cal-end" type="hidden"/><input id="bbit-cal-allday" type="hidden"/><input value="');\r
temparr.push(i18n.xgcalendar.create_event, '" type="submit"/> <a href="" class="lk bbit-cal-editLink">');\r
temparr.push(i18n.xgcalendar.update_detail, ' <StrONG>>></StrONG></SPAN></div></div></div><tr><td><div id="bl1" class="bubble-corner"><div class="bubble-sprite bubble-bl"></div></div><td><div class="bubble-bottom"></div><td><div id="br1" class="bubble-corner"><div class="bubble-sprite bubble-br"></div></div></tr></tbody></table><div id="bubbleClose1" class="bubble-closebutton"></div><div id="prong2" class="prong"><div class=bubble-sprite></div></div></div>');\r
temparr.push('</form>');\r
param[param.length] = option.extParam[pi];\r
}\r
}\r
-\r
if (option.quickAddHandler && $.isFunction(option.quickAddHandler)) {\r
option.quickAddHandler.call(this, param);\r
$("#bbit-cal-buddle").css("visibility", "hidden");\r
ed = new Date(dateend),\r
diff = DateDiff("d", sd, ed);\r
var newdata = {\r
- "uri":"",\r
- "subject":what,\r
+ "jq_id":"",\r
+ "ev_id":"",\r
+ "summary":what,\r
"start":sd,\r
"end":ed,\r
"is_allday":(allday == "1" ? 1 : 0),\r
var sl = option.eventItems.length;\r
var i = -1;\r
for (var j = 0; j < sl; j++) {\r
- if (option.eventItems[j]["uri"] == key) {\r
+ if (option.eventItems[j]["jq_id"] == key) {\r
i = j;\r
break;\r
}\r
d.target.hide();\r
ny = gP(gh.sh, gh.sm);\r
d.top = ny;\r
- tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], false, false, data["color"]);\r
+ tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], false, false, data["color"]);\r
cpwrap = $("<div class='ca-evpi drag-chip-wrapper' style='top:" + ny + "px'/>").html(tempdata);\r
evid = ".tgOver" + d.target.parent().data("col");\r
$gridcontainer.find(evid).append(cpwrap);\r
//log.info("ny=" + ny);\r
gh = gW(ny, ny + d.h);\r
//log.info("sh=" + gh.sh + ",sm=" + gh.sm);\r
- tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], false, false, data["color"]);\r
+ tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], false, false, data["color"]);\r
d.cpwrap.css("top", ny + "px").html(tempdata);\r
}\r
d.ny = ny;\r
d.target.hide();\r
ny = gP(gh.sh, gh.sm);\r
d.top = ny;\r
- tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], "100%", true, data["color"]);\r
+ tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], "100%", true, data["color"]);\r
cpwrap = $("<div class='ca-evpi drag-chip-wrapper' style='top:" + ny + "px'/>").html(tempdata);\r
evid = ".tgOver" + d.target.parent().data("col");\r
$gridcontainer.find(evid).append(cpwrap);\r
nh = pnh > 1 ? nh - pnh + Math.ceil(option.hour_height / 2) : nh - pnh;\r
if (d.nh != nh) {\r
gh = gW(d.top, d.top + nh);\r
- tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["subject"], "100%", true, data["color"]);\r
+ tempdata = buildtempdayevent(gh.sh, gh.sm, gh.eh, gh.em, gh.h, data["summary"], "100%", true, data["color"]);\r
d.cpwrap.html(tempdata);\r
}\r
d.nh = nh;\r
"year_index": 2,\r
"month_index": 1,\r
"day_index": 0,\r
- "day": "d",\r
- "sun": "So",\r
- "mon": "Mo",\r
- "tue": "Di",\r
- "wed": "Mi",\r
- "thu": "Do",\r
- "fri": "Fr",\r
- "sat": "Sa",\r
- "jan": "Jan",\r
- "feb": "Feb",\r
- "mar": "Mär",\r
- "apr": "Apr",\r
- "may": "Mai",\r
- "jun": "Jun",\r
- "jul": "Jul",\r
- "aug": "Aug",\r
- "sep": "Sep",\r
- "oct": "Okt",\r
- "nov": "Nov",\r
- "dec": "Dez"\r
+ "day": "d"\r
},\r
"no_implemented": "Nicht eingebaut",\r
"to_date_view": "Zum aktuellen Datum gehen",\r
"year_index": 2,
"month_index": 1,
"day_index": 0,
- "day": "d",
- "sun": "Su",
- "mon": "Mo",
- "tue": "Tu",
- "wed": "Mi",
- "thu": "Th",
- "fri": "Fr",
- "sat": "Sa",
- "jan": "Jan",
- "feb": "Feb",
- "mar": "Mar",
- "apr": "Apr",
- "may": "May",
- "jun": "Jun",
- "jul": "Jul",
- "aug": "Aug",
- "sep": "Sep",
- "oct": "Oct",
- "nov": "Nov",
- "dec": "Dec"
+ "day": "d"
},
"no_implemented": "Not implemented",
"to_date_view": "Go to today",
--- /dev/null
+<?php
+
+
+
+
+/**
+ * @param mixed $obj
+ * @return string
+ */
+function wdcal_jsonp_encode($obj)
+{
+ $str = json_encode($obj);
+ if (isset($_REQUEST["callback"])) {
+ $str = $_REQUEST["callback"] . "(" . $str . ")";
+ }
+ return $str;
+}
+
+
+/**
+ * @param string $day
+ * @param int $weekstartday
+ * @param int $num_days
+ * @param string $type
+ * @return array
+ */
+function wdcal_get_list_range_params($day, $weekstartday, $num_days, $type)
+{
+ $phpTime = IntVal($day);
+ switch ($type) {
+ case "month":
+ $st = mktime(0, 0, 0, date("m", $phpTime), 1, date("Y", $phpTime));
+ $et = mktime(0, 0, -1, date("m", $phpTime) + 1, 1, date("Y", $phpTime));
+ break;
+ case "week":
+ //suppose first day of a week is monday
+ $monday = date("d", $phpTime) - date('N', $phpTime) + 1;
+ //echo date('N', $phpTime);
+ $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
+ $et = mktime(0, 0, -1, date("m", $phpTime), $monday + 7, date("Y", $phpTime));
+ break;
+ case "multi_days":
+ //suppose first day of a week is monday
+ $monday = date("d", $phpTime) - date('N', $phpTime) + $weekstartday;
+ //echo date('N', $phpTime);
+ $st = mktime(0, 0, 0, date("m", $phpTime), $monday, date("Y", $phpTime));
+ $et = mktime(0, 0, -1, date("m", $phpTime), $monday + $num_days, date("Y", $phpTime));
+ break;
+ case "day":
+ $st = mktime(0, 0, 0, date("m", $phpTime), date("d", $phpTime), date("Y", $phpTime));
+ $et = mktime(0, 0, -1, date("m", $phpTime), date("d", $phpTime) + 1, date("Y", $phpTime));
+ break;
+ default:
+ return array(0, 0);
+ }
+ return array($st, $et);
+}
+
+
+/**
+ * @param Sabre_DAV_Server $server
+ * @param string $right
+ * @return null|Sabre_CalDAV_Calendar
+ */
+function wdcal_print_feed_getCal(&$server, $right)
+{
+ $cals = dav_get_current_user_calendars($server, $right);
+ $calfound = null;
+ for ($i = 0; $i < count($cals) && $calfound === null; $i++) {
+ $prop = $cals[$i]->getProperties(array("id"));
+ if (isset($prop["id"]) && (!isset($_REQUEST["cal"]) || in_array($prop["id"], $_REQUEST["cal"]))) $calfound = $cals[$i];
+ }
+ return $calfound;
+}
+
+
+/**
+ *
+ */
+function wdcal_print_feed($base_path = "")
+{
+ $server = dav_create_server(true, true, false);
+
+ $ret = null;
+
+ $method = $_GET["method"];
+ switch ($method) {
+ case "add":
+ $cs = wdcal_print_feed_getCal($server, DAV_ACL_WRITE);
+ if ($cs == null) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+ try {
+ $item = dav_create_empty_vevent();
+ $component = dav_get_eventComponent($item);
+ $component->add("SUMMARY", icalendar_sanitize_string(dav_compat_parse_text_serverside("CalendarTitle")));
+
+ if (isset($_REQUEST["allday"])) $type = Sabre_VObject_Property_DateTime::DATE;
+ else $type = Sabre_VObject_Property_DateTime::LOCALTZ;
+
+ $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART");
+ $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarStartTime"]))), $type);
+ $datetime_end = new Sabre_VObject_Property_DateTime("DTEND");
+ $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarEndTime"]))), $type);
+
+ $component->add($datetime_start);
+ $component->add($datetime_end);
+
+ $uid = $component->__get("UID");
+ $data = $item->serialize();
+
+ $cs->createFile($uid . ".ics", $data);
+
+ $ret = array(
+ 'IsSuccess' => true,
+ 'Msg' => 'add success',
+ 'Data' => $uid . ".ics",
+ );
+
+ } catch (Exception $e) {
+ $ret = array(
+ 'IsSuccess' => false,
+ 'Msg' => $e->__toString(),
+ );
+ }
+ break;
+ case "list":
+ $weekstartday = (isset($_REQUEST["weekstartday"]) ? IntVal($_REQUEST["weekstartday"]) : 1); // 1 = Monday
+ $num_days = (isset($_REQUEST["num_days"]) ? IntVal($_REQUEST["num_days"]) : 7);
+ $ret = null;
+
+ $date = wdcal_get_list_range_params($_REQUEST["showdate"], $weekstartday, $num_days, $_REQUEST["viewtype"]);
+ $ret = array();
+ $ret['events'] = array();
+ $ret["issort"] = true;
+ $ret["start"] = $date[0];
+ $ret["end"] = $date[1];
+ $ret['error'] = null;
+
+ $cals = dav_get_current_user_calendars($server, DAV_ACL_READ);
+ foreach ($cals as $cal) {
+ $prop = $cal->getProperties(array("id"));
+ if (isset($prop["id"]) && (!isset($_REQUEST["cal"]) || in_array($prop["id"], $_REQUEST["cal"]))) {
+ $backend = wdcal_calendar_factory_by_id($prop["id"]);
+ $events = $backend->listItemsByRange($prop["id"], $date[0], $date[1], $base_path);
+ $ret["events"] = array_merge($ret["events"], $events);
+ }
+ }
+
+ $tmpev = array();
+ foreach ($ret["events"] as $e) {
+ if (!isset($tmpev[$e["start"]])) $tmpev[$e["start"]] = array();
+ $tmpev[$e["start"]][] = $e;
+ }
+ ksort($tmpev);
+ $ret["events"] = array();
+ foreach ($tmpev as $e) foreach ($e as $f) $ret["events"][] = $f;
+
+ break;
+ case "update":
+ $r = q("SELECT `calendarobject_id`, `calendar_id` FROM %s%sjqcalendar WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["jq_id"]));
+ if (count($r) != 1) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+ try {
+ $cs = dav_get_current_user_calendar_by_id($server, $r[0]["calendar_id"], DAV_ACL_READ);
+ $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($r[0]["calendarobject_id"]);
+
+ $vObject = dav_get_current_user_calendarobject($server, $cs, $obj_uri["uri"], DAV_ACL_WRITE);
+ $component = dav_get_eventComponent($vObject);
+
+ if (!$component) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+
+ if (isset($_REQUEST["allday"])) $type = Sabre_VObject_Property_DateTime::DATE;
+ else $type = Sabre_VObject_Property_DateTime::LOCALTZ;
+
+ $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART");
+ $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarStartTime"]))), $type);
+ $datetime_end = new Sabre_VObject_Property_DateTime("DTEND");
+ $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", IntVal($_REQUEST["CalendarEndTime"]))), $type);
+
+ $component->__unset("DTSTART");
+ $component->__unset("DTEND");
+ $component->add($datetime_start);
+ $component->add($datetime_end);
+
+ $data = $vObject->serialize();
+ /** @var Sabre_CalDAV_CalendarObject $child */
+ $child = $cs->getChild($obj_uri["uri"]);
+ $child->put($data);
+
+ $ret = array(
+ 'IsSuccess' => true,
+ 'Msg' => 'Succefully',
+ );
+ } catch (Exception $e) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+ break;
+ case "remove":
+ $r = q("SELECT `calendarobject_id`, `calendar_id` FROM %s%sjqcalendar WHERE `id`=%d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["jq_id"]));
+ if (count($r) != 1) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+ try {
+ $cs = dav_get_current_user_calendar_by_id($server, $r[0]["calendar_id"], DAV_ACL_WRITE);
+ $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($r[0]["calendarobject_id"]);
+ $child = $cs->getChild($obj_uri["uri"]);
+ $child->delete();
+
+ $ret = array(
+ 'IsSuccess' => true,
+ 'Msg' => 'Succefully',
+ );
+ } catch (Exception $e) {
+ echo wdcal_jsonp_encode(array('IsSuccess' => false,
+ 'Msg' => t('No access')));
+ killme();
+ }
+
+ break;
+ }
+ echo wdcal_jsonp_encode($ret);
+ killme();
+}
+
return $format;
}
+ /**
+ * @static
+ * @abstract
+ * @return string
+ */
+ abstract static function getLanguageCode();
+
/**
* @abstract
* @static
*/
abstract function date_timestamp2local($ts);
+ /**
+ * @abstract
+ * @param int $ts
+ * @return string
+ */
+ abstract function date_timestamp2localDate($ts);
+
/**
* @abstract
* @return int
class wdcal_local_us extends wdcal_local {
+ /**
+ * @static
+ * @return string
+ */
+ static function getLanguageCode() {
+ return "en";
+ }
+
/**
* @return string
*/
return date("m/d/Y H:i", $ts);
}
+ /**
+ * @param int $ts
+ * @return string
+ */
+ function date_timestamp2localDate($ts) {
+ return date("l, F jS Y", $ts);
+ }
+
/**
* @return int
*/
class wdcal_local_de extends wdcal_local {
+ /**
+ * @static
+ * @return string
+ */
+ static function getLanguageCode() {
+ return "de";
+ }
+
/**
* @return string
*/
return date("d.m.Y H:i", $ts);
}
+ /**
+ * @param int $ts
+ * @return string
+ */
+ function date_timestamp2localDate($ts) {
+ return date("l, j. F Y", $ts);
+ }
+
/**
* @return int
*/
--- /dev/null
+<?php
+
+/**
+ * @param wdcal_local $localization
+ * @param string $baseurl
+ * @param int $uid
+ * @param int $calendar_id
+ * @param int $uri
+ * @param string $recurr_uri
+ * @return string
+ */
+function wdcal_getEditPage_str(&$localization, $baseurl, $uid, $calendar_id, $uri, $recurr_uri = "")
+{
+ $server = dav_create_server(true, true, false);
+
+ if ($uri > 0) {
+ $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+ if (!$calendar) {
+ $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_READ);
+ $calendars = array();
+ } else {
+ $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+ }
+
+ if ($calendar == null) return "Calendar not found";
+
+ $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($uri);
+
+ $vObject = dav_get_current_user_calendarobject($server, $calendar, $obj_uri["uri"], DAV_ACL_WRITE);
+ $component = dav_get_eventComponent($vObject);
+
+ if ($component == null) return t('Could not open component for editing');
+
+ /** @var Sabre_VObject_Property_DateTime $dtstart */
+ $dtstart = $component->__get("DTSTART");
+ $event = array(
+ "id" => IntVal($uri),
+ "Summary" => ($component->__get("SUMMARY") ? $component->__get("SUMMARY")->value : null),
+ "StartTime" => $dtstart->getDateTime()->getTimeStamp(),
+ "EndTime" => Sabre_CalDAV_Backend_Common::getDtEndTimeStamp($component),
+ "IsAllDayEvent" => (strlen($dtstart->value) == 8),
+ "Description" => ($component->__get("DESCRIPTION") ? $component->__get("DESCRIPTION")->value : null),
+ "Location" => ($component->__get("LOCATION") ? $component->__get("LOCATION")->value : null),
+ "Color" => ($component->__get("X-ANIMEXX-COLOR") ? $component->__get("X-ANIMEXX-COLOR")->value : null),
+ );
+
+ $exdates = $component->select("EXDATE");
+ $recurrentce_exdates = array();
+ /** @var Sabre_VObject_Property_MultiDateTime $x */
+ foreach ($exdates as $x) {
+ /** @var DateTime $y */
+ $z = $x->getDateTimes();
+ foreach ($z as $y) $recurrentce_exdates[] = $y->getTimeStamp();
+ }
+
+ if ($component->select("RRULE")) $recurrence = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->__get("UID"));
+ else $recurrence = null;
+
+ } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) {
+ $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+ $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+
+ $event = array(
+ "id" => 0,
+ "Summary" => $_REQUEST["title"],
+ "StartTime" => InTVal($_REQUEST["start"]),
+ "EndTime" => IntVal($_REQUEST["end"]),
+ "IsAllDayEvent" => $_REQUEST["isallday"],
+ "Description" => "",
+ "Location" => "",
+ "Color" => "#5858ff",
+ );
+ if ($_REQUEST["isallday"]) {
+ $notifications = array(array("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 24));
+ } else {
+ $notifications = array(array("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 1));
+ }
+ $recurrence = null;
+ $recurrentce_exdates = array();
+ } else {
+ $calendars = dav_get_current_user_calendars($server, DAV_ACL_WRITE);
+ $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE);
+
+ $event = array(
+ "id" => 0,
+ "Summary" => "",
+ "StartTime" => time(),
+ "EndTime" => time() + 3600,
+ "IsAllDayEvent" => "0",
+ "Description" => "",
+ "Location" => "",
+ "Color" => "#5858ff",
+ );
+ $notifications = array(array("rel" => "start", "type" => "duration", "period" => "hour", "period_val" => 1));
+ $recurrence = null;
+ $recurrentce_exdates = array();
+ }
+
+ $postto = $baseurl . "/dav/wdcal/" . ($uri == 0 ? "new/" : $calendar_id . "/" . $uri . "/edit/");
+
+ $out = "<a href='" . $baseurl . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
+ $out .= "<form method='POST' action='$postto'>
+ <input type='hidden' name='form_security_token' value='" . get_form_security_token('caledit') . "'>\n";
+
+ $out .= "<h2>" .t("Event data") . "</h2>";
+
+ $out .= "<label for='calendar'>" . t("Calendar") . ":</label><select name='calendar' size='1'>";
+ foreach ($calendars as $cal) {
+ $prop = $cal->getProperties(array("id", DAV_DISPLAYNAME));
+ $out .= "<option value='" . $prop["id"] . "' ";
+ if ($prop["id"] == $calendar_id) $out .= "selected";
+ $out .= ">" . escape_tags($prop[DAV_DISPLAYNAME]) . "</option>\n";
+ }
+ $out .= "</select><br>\n";
+
+ $out .= "<label class='block' for='cal_summary'>" . t("Subject") . ":</label>
+ <input name='color' id='cal_color' value='" . (strlen($event["Color"]) != 7 ? "#5858ff" : escape_tags($event["Color"])) . "'>
+ <input name='summary' id='cal_summary' value='" . escape_tags($event["Summary"]) . "'><br>\n";
+ $out .= "<label class='block' for='cal_allday'>Is All-Day event:</label><input type='checkbox' name='allday' id='cal_allday' " . ($event["IsAllDayEvent"] ? "checked" : "") . "><br>\n";
+
+ $out .= "<label class='block' for='cal_startdate'>" . t("Starts") . ":</label>";
+ $out .= "<input name='start_date' value='" . $localization->dateformat_datepicker_php($event["StartTime"]) . "' id='cal_start_date'>";
+ $out .= "<input name='start_time' value='" . date("H:i", $event["StartTime"]) . "' id='cal_start_time'>";
+ $out .= "<br>\n";
+
+ $out .= "<label class='block' for='cal_enddate'>" . t("Ends") . ":</label>";
+ $out .= "<input name='end_date' value='" . $localization->dateformat_datepicker_php($event["EndTime"]) . "' id='cal_end_date'>";
+ $out .= "<input name='end_time' value='" . date("H:i", $event["EndTime"]) . "' id='cal_end_time'>";
+ $out .= "<br>\n";
+
+ $out .= "<label class='block' for='cal_location'>" . t("Location") . ":</label><input name='location' id='cal_location' value='" . escape_tags($event["Location"]) . "'><br>\n";
+
+ $out .= "<label class='block' for='event-desc-textarea'>" . t("Description") . ":</label> <textarea id='event-desc-textarea' name='wdcal_desc' style='vertical-align: top; width: 400px; height: 100px;'>" . escape_tags($event["Description"]) . "</textarea>";
+ $out .= "<br style='clear: both;'>";
+
+ $out .= "<h2>" .t("Recurrence") . "</h2>";
+
+ $out .= "<label class='block' for='rec_frequency'>" . t("Frequency") . ":</label> <select id='rec_frequency' name='rec_frequency' size='1'>";
+ $out .= "<option value=''>" . t("None") . "</option>\n";
+ $out .= "<option value='daily' "; if ($recurrence && $recurrence->frequency == "daily") $out .= "selected"; $out .= ">" . t("Daily") . "</option>\n";
+ $out .= "<option value='weekly' "; if ($recurrence && $recurrence->frequency == "weekly") $out .= "selected"; $out .= ">" . t("Weekly") . "</option>\n";
+ $out .= "<option value='monthly' "; if ($recurrence && $recurrence->frequency == "monthly") $out .= "selected"; $out .= ">" . t("Monthly") . "</option>\n";
+ $out .= "<option value='yearly' "; if ($recurrence && $recurrence->frequency == "yearly") $out .= "selected"; $out .= ">" . t("Yearly") . "</option>\n";
+ $out .="</select><br>\n";
+ $out .= "<div id='rec_details'>";
+
+ $select = "<select id='rec_interval' name='rec_interval' size='1'>";
+ for ($i = 1; $i < 50; $i++) {
+ $select .= "<option value='$i' ";
+ if ($recurrence && $i == $recurrence->interval) $select .= "selected";
+ $select .= ">$i</option>\n";
+ }
+ $select .= "</select>";
+ $time = "<span class='rec_daily'>" . t("days") . "</span>";
+ $time .= "<span class='rec_weekly'>" . t("weeks") . "</span>";
+ $time .= "<span class='rec_monthly'>" . t("months") . "</span>";
+ $time .= "<span class='rec_yearly'>" . t("years") . "</span>";
+ $out .= "<label class='block' for='rev_interval'>" . t("Interval") . ":</label> " . str_replace(array("%select%", "%time%"), array($select, $time), t("All %select% %time%")) . "<br>";
+
+
+ $out .= "<div class='rec_daily'>";
+ $out .= "<label class='block'>" . t("Days") . ":</label>";
+ if ($recurrence && $recurrence->byDay) {
+ $byday = $recurrence->byDay;
+ } else {
+ $byday = array("MO", "TU", "WE", "TH", "FR", "SA", "SU");
+ }
+ if ($localization->getFirstDayOfWeek() == 0) {
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SU' "; if (in_array("SU", $byday)) $out .= "checked"; $out .= ">" . t("Sunday") . "</label> ";
+ }
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='MO' "; if (in_array("MO", $byday)) $out .= "checked"; $out .= ">" . t("Monday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='TU' "; if (in_array("TU", $byday)) $out .= "checked"; $out .= ">" . t("Tuesday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='WE' "; if (in_array("WE", $byday)) $out .= "checked"; $out .= ">" . t("Wednesday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='TH' "; if (in_array("TH", $byday)) $out .= "checked"; $out .= ">" . t("Thursday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='FR' "; if (in_array("FR", $byday)) $out .= "checked"; $out .= ">" . t("Friday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SA' "; if (in_array("SA", $byday)) $out .= "checked"; $out .= ">" . t("Saturday") . "</label> ";
+ if ($localization->getFirstDayOfWeek() != 0) {
+ $out .= "<label class='plain'><input class='rec_daily_byday' type='checkbox' name='rec_daily_byday[]' value='SU' "; if (in_array("SU", $byday)) $out .= "checked"; $out .= ">" . t("Sunday") . "</label> ";
+ }
+ $out .= "</div>";
+
+
+ $out .= "<div class='rec_weekly'>";
+ $out .= "<label class='block'>" . t("Days") . ":</label>";
+ if ($recurrence && $recurrence->byDay) {
+ $byday = $recurrence->byDay;
+ } else {
+ $byday = array("MO", "TU", "WE", "TH", "FR", "SA", "SU");
+ }
+ if ($localization->getFirstDayOfWeek() == 0) {
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SU' "; if (in_array("SU", $byday)) $out .= "checked"; $out .= ">" . t("Sunday") . "</label> ";
+ }
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='MO' "; if (in_array("MO", $byday)) $out .= "checked"; $out .= ">" . t("Monday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='TU' "; if (in_array("TU", $byday)) $out .= "checked"; $out .= ">" . t("Tuesday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='WE' "; if (in_array("WE", $byday)) $out .= "checked"; $out .= ">" . t("Wednesday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='TH' "; if (in_array("TH", $byday)) $out .= "checked"; $out .= ">" . t("Thursday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='FR' "; if (in_array("FR", $byday)) $out .= "checked"; $out .= ">" . t("Friday") . "</label> ";
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SA' "; if (in_array("SA", $byday)) $out .= "checked"; $out .= ">" . t("Saturday") . "</label> ";
+ if ($localization->getFirstDayOfWeek() != 0) {
+ $out .= "<label class='plain'><input class='rec_weekly_byday' type='checkbox' name='rec_weekly_byday[]' value='SU' "; if (in_array("SU", $byday)) $out .= "checked"; $out .= ">" . t("Sunday") . "</label> ";
+ }
+ $out .= "<br>";
+
+ $out .= "<label class='block'>" . t("First day of week:") . "</label>";
+ if ($recurrence && $recurrence->weekStart != "") $wkst = $recurrence->weekStart;
+ else {
+ if ($localization->getFirstDayOfWeek() == 0) $wkst = "SU";
+ else $wkst = "MO";
+ }
+ $out .= "<label class='plain'><input type='radio' name='rec_weekly_wkst' value='SU' "; if ($wkst == "SU") $out .= "checked"; $out .= ">" . t("Sunday") . "</label> ";
+ $out .= "<label class='plain'><input type='radio' name='rec_weekly_wkst' value='MO' "; if ($wkst == "MO") $out .= "checked"; $out .= ">" . t("Monday") . "</label><br>\n";
+
+ $out .= "</div>";
+
+ $monthly_rule = "bymonthday"; // @TODO
+ $out .= "<div class='rec_monthly'>";
+ $out .= "<label class='block' name='rec_monthly_day'>" . t("Day of month") . ":</label>";
+ $out .= "<select id='rec_monthly_day' name='rec_monthly_day' size='1'>";
+ $out .= "<option value='bymonthday' "; if ($monthly_rule == "bymonthday") $out .= "selected"; $out .= ">" . t("#num#th of each month") . "</option>\n";
+ $out .= "<option value='bymonthday_neg' "; if ($monthly_rule == "bymonthday_neg") $out .= "selected"; $out .= ">" . t("#num#th-last of each month") . "</option>\n";
+ $out .= "<option value='byday' "; if ($monthly_rule == "byday") $out .= "selected"; $out .= ">" . t("#num#th #wkday# of each month") . "</option>\n";
+ $out .= "<option value='byday_neg' "; if ($monthly_rule == "byday_neg") $out .= "selected"; $out .= ">" . t("#num#th-last #wkday# of each month") . "</option>\n";
+ $out .= "</select>";
+ $out .= "</div>\n";
+
+
+ $out .= "<div class='rec_yearly'>";
+ $out .= "<label class='block' name='rec_yearly_day'>" . t("Month") . ":</label> <span class='rec_month_name'>#month#</span><br>\n";
+ $out .= "<label class='block' name='rec_yearly_day'>" . t("Day of month") . ":</label>";
+ $out .= "<select id='rec_yearly_day' name='rec_yearly_day' size='1'>";
+ $out .= "<option value='bymonthday' "; if ($monthly_rule == "bymonthday") $out .= "selected"; $out .= ">" . t("#num#th of each month") . "</option>\n";
+ $out .= "<option value='bymonthday_neg' "; if ($monthly_rule == "bymonthday_neg") $out .= "selected"; $out .= ">" . t("#num#th-last of each month") . "</option>\n";
+ $out .= "<option value='byday' "; if ($monthly_rule == "byday") $out .= "selected"; $out .= ">" . t("#num#th #wkday# of each month") . "</option>\n";
+ $out .= "<option value='byday_neg' "; if ($monthly_rule == "byday_neg") $out .= "selected"; $out .= ">" . t("#num#th-last #wkday# of each month") . "</option>\n";
+ $out .= "</select>";
+ $out .= "</div>\n";
+
+
+ if ($recurrence) {
+ $until = $recurrence->until;
+ $count = $recurrence->count;
+ if (is_a($until, "DateTime")) {
+ /** @var DateTime $until */
+ $rule_type = "date";
+ $rule_until_date = $until->getTimestamp();
+ $rule_until_count = 1;
+ } elseif ($count > 0) {
+ $rule_type = "count";
+ $rule_until_date = time();
+ $rule_until_count = $count;
+ } else {
+ $rule_type = "infinite";
+ $rule_until_date = time();
+ $rule_until_count = 1;
+ }
+ } else {
+ $rule_type = "infinite";
+ $rule_until_date = time();
+ $rule_until_count = 1;
+ }
+ $out .= "<label class='block' for='rec_until_type'>" . t("Repeat until") . ":</label> ";
+ $out .= "<select name='rec_until_type' id='rec_until_type' size='1'>";
+ $out .= "<option value='infinite' "; if ($rule_type == "infinite") $out .= "selected"; $out .= ">" . t("Infinite") . "</option>\n";
+ $out .= "<option value='date' "; if ($rule_type == "date") $out .= "selected"; $out .= ">" . t("Until the following date") . ":</option>\n";
+ $out .= "<option value='count' "; if ($rule_type == "count") $out .= "selected"; $out .= ">" . t("Number of times") . ":</option>\n";
+ $out .= "</select>";
+
+ $out .= "<input name='rec_until_date' value='" . $localization->dateformat_datepicker_php($rule_until_date) . "' id='rec_until_date'>";
+ $out .= "<input name='rec_until_count' value='$rule_until_count' id='rec_until_count'><br>";
+
+ $out .= "<label class='block'>" . t("Exceptions") . ":</label><div class='rec_exceptions'>";
+ $out .= "<div class='rec_exceptions_none' ";
+ if (count($recurrentce_exdates) > 0) $out .= "style='display: none;'";
+ $out .= ">" . t("none") . "</div>";
+ $out .= "<div class='rec_exceptions_holder' ";
+ if (count($recurrentce_exdates) == 0) $out .= "style='display: none;'";
+ $out .= ">";
+
+ foreach ($recurrentce_exdates as $exdate) {
+ $out .= "<div data-timestamp='$exdate' class='except'><input type='hidden' class='rec_exception' name='rec_exceptions[]' value='$exdate'>";
+ $out .= "<a href='#' class='exception_remover'>[remove]</a> ";
+ $out .= $localization->date_timestamp2localDate($exdate);
+ $out .= "</div>\n";
+ }
+ $out .= "</div><div><a href='#' class='exception_adder'>[add]</a></div>";
+ $out .= "</div>\n";
+ $out .= "<br>\n";
+
+ $out .= "</div><br>";
+
+ $out .= "<h2>" .t("Notification") . "</h2>";
+
+ /*
+ $out .= '<input type="checkbox" name="notification" id="notification" ';
+ if ($notification) $out .= "checked";
+ $out .= '> ';
+ $out .= '<span id="notification_detail" style="display: none;">
+ <input name="notification_value" value="' . $notification_value . '" size="3">
+ <select name="notification_type" size="1">
+ <option value="minute" ';
+ if ($notification_type == "minute") $out .= "selected";
+ $out .= '> ' . t('Minutes') . '</option>
+ <option value="hour" ';
+ if ($notification_type == "hour") $out .= "selected";
+ $out .= '> ' . t('Hours') . '</option>
+ <option value="day" ';
+ if ($notification_type == "day") echo "selected";
+ $out .= '> ' . t('Days') . '</option>
+ </select> ' . t('before') . '
+ </span><br><br>';
+ */
+
+ $out .= "<script>\$(function() {
+ wdcal_edit_init('" . $localization->dateformat_datepicker_js() . "', '${baseurl}/dav/');
+ });</script>";
+
+ $out .= "<input type='submit' name='save' value='Save'></form>";
+
+ return $out;
+}
+
+
+/**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param wdcal_local $localization
+ */
+function wdcal_set_component_date(&$component, &$localization) {
+ if (isset($_REQUEST["allday"])) {
+ $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " 00:00");
+ $ts_end = $localization->date_local2timestamp($_REQUEST["end_date"] . " 00:00");
+ $type = Sabre_VObject_Property_DateTime::DATE;
+ } else {
+ $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " " . $_REQUEST["start_time"]);
+ $ts_end = $localization->date_local2timestamp($_REQUEST["end_date"] . " " . $_REQUEST["end_time"]);
+ $type = Sabre_VObject_Property_DateTime::LOCALTZ;
+ }
+ $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART");
+ $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type);
+ $datetime_end = new Sabre_VObject_Property_DateTime("DTEND");
+ $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type);
+
+ $component->__unset("DTSTART");
+ $component->__unset("DTEND");
+ $component->add($datetime_start);
+ $component->add($datetime_end);
+}
+
+ /**
+ * @param Sabre_VObject_Component_VEvent $component
+ * @param 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 = "<ul>";
+
+ $i = 0;
+ while($it->valid() && $last_start < $max_ts && $i++ < 1000) {
+ $last_start = $it->getDtStart()->getTimestamp();
+ $o .= "<li><a href='#' class='exception_selector_link' data-timestamp='$last_start'>" . $localization->date_timestamp2localDate($last_start) . "</a></li>\n";
+ $it->next();
+ }
+ $o .= "</ul>\n";
+
+ return $o;
+}
\ No newline at end of file
<?php
+/**
+ * @param int $from_version
+ * @return array|string[]
+ */
+function dav_get_update_statements($from_version)
+{
+ $stms = array();
+
+ if ($from_version <= 0) {
+ $stms[] = "ALTER TABLE `dav_calendars` ADD `uri` VARCHAR( 50 ) NULL DEFAULT NULL AFTER `description` , ADD `has_vevent` TINYINT NOT NULL DEFAULT '1' AFTER `uri` , ADD `has_vtodo` TINYINT NOT NULL DEFAULT '1' AFTER `has_vevent`";
+
+ $stms[] = "UPDATE `dav_calendars` SET `uri` = 'private' WHERE `namespace` = 1";
+ $stms[] = "UPDATE `dav_calendars` SET `uri` = 'friendica-mine' WHERE `namespace` = 2 AND `namespace_id` = 1";
+ $stms[] = "UPDATE `dav_calendars` SET `uri` = 'friendica-contacts' WHERE `namespace` = 2 AND `namespace_id` = 2";
+ $stms[] = "ALTER TABLE `dav_calendars` DROP PRIMARY KEY ";
+ $stms[] = "ALTER TABLE `dav_calendars` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST ";
+
+ $stms[] = "ALTER TABLE `dav_calendarobjects` ADD `calendar_id` INT NOT NULL AFTER `id` ";
+ $stms[] = "UPDATE `dav_calendarobjects` a JOIN `dav_calendars` b ON a.`namespace` = b.`namespace` AND a.`namespace_id` = b.`namespace_id` SET a.`calendar_id` = b.`id`";
+ $stms[] = "ALTER TABLE `dav_calendarobjects` DROP `namespace` , DROP `namespace_id` ;";
+ $stms[] = "ALTER TABLE `dav_calendarobjects` ADD INDEX ( `calendar_id` ) ";
+ $stms[] = "ALTER TABLE `dav_calendarobjects` ADD `componentType` ENUM( 'VEVENT', 'VTODO' ) NOT NULL DEFAULT 'VEVENT' AFTER `calendardata` ,
+ ADD `firstOccurence` TIMESTAMP NOT NULL AFTER `lastmodified` ,
+ ADD `lastOccurence` TIMESTAMP NOT NULL AFTER `firstOccurence`";
+
+ $stms[] = "DROP TABLE `dav_jqcalendar`";
+ $stms[] = "CREATE TABLE IF NOT EXISTS `dav_jqcalendar` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `ical_recurr_uri` varchar(100) DEFAULT NULL,
+ `calendar_id` int(10) unsigned NOT NULL,
+ `calendarobject_id` int(10) unsigned NOT NULL,
+ `Subject` varchar(1000) NOT NULL,
+ `StartTime` timestamp NULL DEFAULT NULL,
+ `EndTime` timestamp NULL DEFAULT NULL,
+ `IsEditable` tinyint(3) unsigned NOT NULL,
+ `IsAllDayEvent` tinyint(4) NOT NULL,
+ `IsRecurring` tinyint(4) NOT NULL,
+ `Color` CHAR(6) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `calendarByStart` (`calendar_id`,`StartTime`),
+ KEY `calendarobject_id` (`calendarobject_id`,`ical_recurr_uri`)
+ ) DEFAULT CHARSET=utf8 ";
+
+
+ $stms[] = "ALTER TABLE `dav_notifications` ADD `calendar_id` INT NOT NULL AFTER `ical_recurr_uri` ";
+ $stms[] = "ALTER TABLE `dav_notifications` DROP INDEX `ical_uri` , ADD INDEX `ical_uri` ( `calendar_id` , `ical_uri` , `ical_recurr_uri` ) ";
+ $stms[] = "TRUNCATE TABLE `dav_notifications`";
+ $stms[] = "ALTER TABLE `dav_notifications` DROP `namespace` , DROP `namespace_id`";
+
+ $stms[] = "TRUNCATE TABLE `dav_cal_virtual_object_cache`";
+ $stme[] = "ALTER TABLE `dav_cal_virtual_object_cache` ADD `calendar_id` INT UNSIGNED NOT NULL AFTER `id` ";
+ $stms[] = "ALTER TABLE `dav_cal_virtual_object_cache` DROP INDEX `ref_type` , ADD INDEX `ref_type` ( `calendar_id` , `data_end` ) ";
+ $stms[] = "ALTER TABLE `dav_cal_virtual_object_cache` DROP `uid`, DROP `namespace` , DROP `namespace_id` ";
+
+ $stms[] = "CREATE TABLE `friendica`.`dav_cal_virtual_object_sync` (
+ `calendar_id` INT UNSIGNED NOT NULL ,
+ `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+ PRIMARY KEY ( `calendar_id` )
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8";
+
+ $stms[] = "DROP TABLE `dav_cache_synchronized` ";
+
+ $stms[] = "UPDATE `dav_calendars` SET `namespace` = 1, `namespace_id` = `uid`"; // last
+ $stms[] = "ALTER TABLE `dav_calendars` DROP `uid`";
+ }
+
+ return $stms;
+}
+
/**
* @return array
*/
-function dav_get_create_statements() {
+function dav_get_create_statements()
+{
$arr = array();
$arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` (
`displayname` varchar(200) NOT NULL,
`timezone` text NOT NULL,
`description` varchar(500) NOT NULL,
+ `uri` varchar(50) DEFAULT NULL,
+ `has_vevent` tinyint(4) NOT NULL DEFAULT '1',
+ `has_vtodo` tinyint(4) NOT NULL DEFAULT '1',
`ctag` int(10) unsigned NOT NULL,
PRIMARY KEY (`namespace`,`namespace_id`),
KEY `uid` (`uid`)
/**
* @return int
*/
-function dav_check_tables() {
+function dav_check_tables()
+{
$dbv = get_config("dav", "db_version");
if ($dbv == CALDAV_DB_VERSION) return 0; // Correct
- if (is_numeric($dbv)) return 1; // Older version (update needed)
+ if (is_numeric($dbv) || $dbv == "CALDAV_DB_VERSION") return 1; // Older version (update needed)
return -1; // Not installed
}
*/
function dav_create_tables()
{
- $stms = dav_get_create_statements();
+ $stms = dav_get_create_statements();
+ $errors = array();
+
+ global $db;
+ foreach ($stms as $st) {
+ $db->q($st);
+ if ($db->error) $errors[] = $db->error;
+ }
+
+ if (count($errors) == 0) set_config("dav", "db_version", CALDAV_DB_VERSION);
+
+ return $errors;
+}
+
+/**
+ * @return array
+ */
+function dav_upgrade_tables()
+{
+ $dbv = get_config("dav", "db_version");
+ if ($dbv == "CALDAV_DB_VERSION") $ver = 0;
+ else $ver = IntVal($dbv);
+ $stms = dav_get_update_statements($ver);
$errors = array();
global $db;
/**
* Name: Calendar with CalDAV Support
* Description: A web-based calendar system with CalDAV-support. Also brings your Friendica-Contacts to your CardDAV-capable mobile phone. Requires PHP >= 5.3.
- * Version: 0.1.1
+ * Version: 0.2.0
* Author: Tobias Hößl <https://github.com/CatoTH/>
*/
--- /dev/null
+<?php
+
+class Sabre_CalDAV_Backend_Friendica extends Sabre_CalDAV_Backend_Virtual
+{
+
+ /**
+ * @var null|Sabre_CalDAV_Backend_Friendica
+ */
+ private static $instance = null;
+
+ /**
+ * @static
+ * @return Sabre_CalDAV_Backend_Friendica
+ */
+ public static function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new Sabre_CalDAV_Backend_Friendica();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNamespace()
+ {
+ return CALDAV_NAMESPACE_PRIVATE;
+ }
+
+
+ /**
+ * @static
+ * @param int $calendarId
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return void
+ */
+ protected static function createCache_internal($calendarId)
+ {
+ $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+
+ switch ($calendar["uri"]) {
+ case CALDAV_FRIENDICA_MINE:
+ $sql_where = " AND cid = 0";
+ break;
+ case CALDAV_FRIENDICA_CONTACTS:
+ $sql_where = " AND cid > 0";
+ break;
+ default:
+ throw new Sabre_DAV_Exception_NotFound();
+ }
+
+ $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"]));
+
+ foreach ($r as $row) {
+ $uid = $calendar["uri"] . "-" . $row["id"];
+ $vevent = dav_create_empty_vevent($uid);
+ $component = dav_get_eventComponent($vevent);
+
+ if ($row["adjust"]) {
+ $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]);
+ $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]);
+ } else {
+ $start = $row["start"];
+ $finish = $row["finish"];
+ }
+
+ $summary = ($row["summary"] != "" ? $row["summary"] : $row["desc"]);
+ $desc = ($row["summary"] != "" ? $row["desc"] : "");
+ $component->add("SUMMARY", icalendar_sanitize_string($summary));
+ $component->add("LOCATION", icalendar_sanitize_string($row["location"]));
+ $component->add("DESCRIPTION", icalendar_sanitize_string($desc));
+
+ $ts_start = wdcal_mySql2PhpTime($start);
+ $ts_end = wdcal_mySql2PhpTime($start);
+
+ $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false);
+ $type = ($allday ? Sabre_VObject_Property_DateTime::DATE : Sabre_VObject_Property_DateTime::LOCALTZ);
+
+ $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART");
+ $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type);
+ $datetime_end = new Sabre_VObject_Property_DateTime("DTEND");
+ $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type);
+
+ $component->add($datetime_start);
+ $component->add($datetime_end);
+
+ $data = $vevent->serialize();
+
+ q("INSERT INTO %s%scal_virtual_object_cache (`calendar_id`, `data_uri`, `data_summary`, `data_location`, `data_start`, `data_end`, `data_allday`, `data_type`,
+ `calendardata`, `size`, `etag`) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', %d, '%s')",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId, dbesc($uid), dbesc($summary), dbesc($row["location"]), dbesc($row["start"]), dbesc($row["finish"]),
+ ($allday ? 1 : 0), dbesc(($row["type"] == "birthday" ? "birthday" : "")), dbesc($data), strlen($data), md5($data));
+
+ }
+
+ }
+
+
+ /**
+ * @param array $row
+ * @param array $calendar
+ * @param string $base_path
+ * @return array
+ */
+ private function jqcal2wdcal($row, $calendar, $base_path)
+ {
+ if ($row["adjust"]) {
+ $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]);
+ $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]);
+ } else {
+ $start = $row["start"];
+ $finish = $row["finish"];
+ }
+
+ $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false);
+
+ $summary = (($row["summary"]) ? $row["summary"] : substr(preg_replace("/\[[^\]]*\]/", "", $row["desc"]), 0, 100));
+
+ return array(
+ "jq_id" => $row["id"],
+ "ev_id" => $row["id"],
+ "summary" => escape_tags($summary),
+ "start" => wdcal_mySql2PhpTime($start),
+ "end" => wdcal_mySql2PhpTime($finish),
+ "is_allday" => ($allday ? 1 : 0),
+ "is_moredays" => (substr($start, 0, 10) != substr($finish, 0, 10)),
+ "is_recurring" => ($row["type"] == "birthday"),
+ "color" => "#f8f8ff",
+ "is_editable" => 0,
+ "is_editable_quick" => 0,
+ "location" => $row["location"],
+ "attendees" => '',
+ "has_notification" => 0,
+ "url_detail" => $base_path . "/events/event/" . $row["id"],
+ "url_edit" => "",
+ "special_type" => ($row["type"] == "birthday" ? "birthday" : ""),
+ );
+ }
+
+
+ /**
+ * @param int $calendarId
+ * @param string $date_from
+ * @param string $date_to
+ * @param string $base_path
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return array
+ */
+ public function listItemsByRange($calendarId, $date_from, $date_to, $base_path)
+ {
+ $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId);
+
+ if ($calendar["namespace"] != CALDAV_NAMESPACE_PRIVATE) throw new Sabre_DAV_Exception_NotFound();
+
+ switch ($calendar["uri"]) {
+ case CALDAV_FRIENDICA_MINE:
+ $sql_where = " AND cid = 0";
+ break;
+ case CALDAV_FRIENDICA_CONTACTS:
+ $sql_where = " AND cid > 0";
+ break;
+ default:
+ throw new Sabre_DAV_Exception_NotFound();
+ }
+
+ if ($date_from != "") {
+ if (is_numeric($date_from)) $sql_where .= " AND `finish` >= '" . date("Y-m-d H:i:s", $date_from) . "'";
+ else $sql_where .= " AND `finish` >= '" . dbesc($date_from) . "'";
+ }
+ if ($date_to != "") {
+ if (is_numeric($date_to)) $sql_where .= " AND `start` <= '" . date("Y-m-d H:i:s", $date_to) . "'";
+ else $sql_where .= " AND `start` <= '" . dbesc($date_to) . "'";
+ }
+ $ret = array();
+
+ $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"]));
+
+ $a = get_app();
+ foreach ($r as $row) {
+ $r = $this->jqcal2wdcal($row, $calendar, $a->get_baseurl());
+ $r["calendar_id"] = $calendar["id"];
+ $ret[] = $r;
+ }
+
+ return $ret;
+ }
+
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri, which the basename of the uri with which the calendar is
+ * accessed.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ public function getCalendarsForUser($principalUri)
+ {
+ $n = dav_compat_principal2namespace($principalUri);
+ if ($n["namespace"] != $this->getNamespace()) return array();
+
+ $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"]));
+ $ret = array();
+ foreach ($cals as $cal) {
+ if (!in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue;
+
+ $dat = array(
+ "id" => $cal["id"],
+ "uri" => $cal["uri"],
+ "principaluri" => $principalUri,
+ '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag'] ? $cal['ctag'] : '0',
+ "calendar_class" => "Sabre_CalDAV_Calendar_Virtual",
+ );
+ foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field];
+
+ $ret[] = $dat;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param int $calendar_id
+ * @param int $calendarobject_id
+ * @return string
+ */
+ function getItemDetailRedirect($calendar_id, $calendarobject_id)
+ {
+ $a = get_app();
+ $item = q("SELECT `id` FROM `item` WHERE `event-id` = %d AND `uid` = %d AND deleted = 0", IntVal($calendarobject_id), $a->user["uid"]);
+ if (count($item) == 0) return "/events/";
+ return "/display/" . $a->user["nickname"] . "/" . IntVal($item[0]["id"]);
+
+ }
+}
--- /dev/null
+<?php
+
+class Sabre_CardDAV_Backend_FriendicaCommunity extends Sabre_CardDAV_Backend_Abstract
+{
+
+ /**
+ * @var null|Sabre_CardDAV_Backend_FriendicaCommunity
+ */
+ private static $instance = null;
+
+ /**
+ * @static
+ * @return Sabre_CardDAV_Backend_FriendicaCommunity
+ */
+ public static function getInstance() {
+ if (self::$instance == null) {
+ self::$instance = new Sabre_CardDAV_Backend_FriendicaCommunity();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Sets up the object
+ */
+ public function __construct()
+ {
+
+ }
+
+ /**
+ * Returns the list of addressbooks for a specific user.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ public function getAddressBooksForUser($principalUri)
+ {
+ $uid = dav_compat_principal2uid($principalUri);
+
+ $addressBooks = array();
+
+ $books = q("SELECT ctag FROM %s%saddressbooks_community WHERE uid = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid));
+ if (count($books) == 0) {
+ q("INSERT INTO %s%saddressbooks_community (uid, ctag) VALUES (%d, 1)", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($uid));
+ $ctag = 1;
+ } else {
+ $ctag = $books[0]["ctag"];
+ }
+ $addressBooks[] = array(
+ 'id' => CARDDAV_NAMESPACE_COMMUNITYCONTACTS . "-" . $uid,
+ 'uri' => "friendica",
+ 'principaluri' => $principalUri,
+ '{DAV:}displayname' => t("Friendica-Contacts"),
+ '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => t("Your Friendica-Contacts"),
+ '{http://calendarserver.org/ns/}getctag' => $ctag,
+ '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' =>
+ new Sabre_CardDAV_Property_SupportedAddressData(),
+ );
+
+ return $addressBooks;
+
+ }
+
+
+ /**
+ * Updates an addressbook's properties
+ *
+ * See Sabre_DAV_IProperties for a description of the mutations array, as
+ * well as the return value.
+ *
+ * @param string $addressBookId
+ * @param array $mutations
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @see Sabre_DAV_IProperties::updateProperties
+ * @return bool|array
+ */
+ public function updateAddressBook($addressBookId, array $mutations)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Creates a new address book
+ *
+ * @param string $principalUri
+ * @param string $url Just the 'basename' of the url.
+ * @param array $properties
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return void
+ */
+ public function createAddressBook($principalUri, $url, array $properties)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Deletes an entire addressbook and all its contents
+ *
+ * @param int $addressBookId
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return void
+ */
+ public function deleteAddressBook($addressBookId)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+
+ /**
+ * @param array $contact
+ * @return array
+ */
+ private function dav_contactarr2vcardsource($contact)
+ {
+ $name = explode(" ", $contact["name"]);
+ $first_name = $last_name = "";
+ $middle_name = array();
+ $num = count($name);
+ for ($i = 0; $i < $num && $first_name == ""; $i++) if ($name[$i] != "") {
+ $first_name = $name[$i];
+ unset($name[$i]);
+ }
+ for ($i = $num - 1; $i >= 0 && $last_name == ""; $i--) if (isset($name[$i]) && $name[$i] != "") {
+ $last_name = $name[$i];
+ unset($name[$i]);
+ }
+ foreach ($name as $n) if ($n != "") $middle_name[] = $n;
+ $vcarddata = new vcard_source_data($first_name, implode(" ", $middle_name), $last_name);
+ $vcarddata->homepages[] = new vcard_source_data_homepage("pref", $contact["url"]);
+ $vcarddata->last_update = ($contact["last-update"] > 0 ? $contact["last-update"] : $contact["created"]);
+
+ $photo = q("SELECT * FROM photo WHERE `contact-id` = %d ORDER BY scale DESC", $contact["id"]); //prefer size 80x80
+ if ($photo && count($photo) > 0) {
+ $photodata = new vcard_source_data_photo();
+ $photodata->width = $photo[0]["width"];
+ $photodata->height = $photo[0]["height"];
+ $photodata->type = "JPEG";
+ $photodata->binarydata = $photo[0]["data"];
+ $vcarddata->photo = $photodata;
+ }
+
+ switch ($contact["network"]) {
+ case "face":
+ $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("facebook", $contact["notify"], "http://www.facebook.com/" . $contact["notify"]);
+ break;
+ case "dfrn":
+ $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("dfrn", $contact["nick"], $contact["url"]);
+ break;
+ case "twitter":
+ $vcarddata->socialnetworks[] = new vcard_source_data_socialnetwork("twitter", $contact["nick"], "http://twitter.com/" . $contact["nick"]); // @TODO Stimmt das?
+ break;
+ }
+
+ $vcard = vcard_source_compile($vcarddata);
+ return array(
+ "id" => $contact["id"],
+ "carddata" => $vcard,
+ "uri" => $contact["id"] . ".vcf",
+ "lastmodified" => wdcal_mySql2PhpTime($vcarddata->last_update),
+ "etag" => md5($vcard),
+ "size" => strlen($vcard),
+ );
+
+ }
+
+ /**
+ * @param int $uid
+ * @param array|int[] $exclude_ids
+ * @return array
+ */
+ private function dav_getCommunityContactsVCards($uid = 0, $exclude_ids = array())
+ {
+ $notin = (count($exclude_ids) > 0 ? " AND id NOT IN (" . implode(", ", $exclude_ids) . ") " : "");
+ $uid = IntVal($uid);
+ $contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0 $notin ORDER BY `name` ASC", $uid);
+
+ $retdata = array();
+ foreach ($contacts as $contact) {
+ $x = $this->dav_contactarr2vcardsource($contact);
+ $x["contact"] = $contact["id"];
+ $retdata[] = $x;
+ }
+ return $retdata;
+ }
+
+
+ /**
+ * Returns all cards for a specific addressbook id.
+ *
+ * This method should return the following properties for each card:
+ * * carddata - raw vcard data
+ * * uri - Some unique url
+ * * lastmodified - A unix timestamp
+ *
+ * It's recommended to also return the following properties:
+ * * etag - A unique etag. This must change every time the card changes.
+ * * size - The size of the card in bytes.
+ *
+ * If these last two properties are provided, less time will be spent
+ * calculating them. If they are specified, you can also ommit carddata.
+ * This may speed up certain requests, especially with large cards.
+ *
+ * @param string $addressbookId
+ * @return array
+ */
+ public function getCards($addressbookId)
+ {
+ $add = explode("-", $addressbookId);
+
+ $indb = q('SELECT id, carddata, uri, lastmodified, etag, size, contact, manually_deleted FROM %s%scards WHERE namespace = %d AND namespace_id = %d',
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($add[0]), IntVal($add[1])
+ );
+ $found_contacts = array();
+ $contacts = array();
+ foreach ($indb as $x) {
+ if ($x["manually_deleted"] == 0) $contacts[] = $x;
+ $found_contacts[] = IntVal($x["contact"]);
+ }
+ $new_found = $this->dav_getCommunityContactsVCards($add[1], $found_contacts);
+ foreach ($new_found as $new) {
+ q("INSERT INTO %s%scards (namespace, namespace_id, contact, carddata, uri, lastmodified, manually_edited, manually_deleted, etag, size)
+ VALUES (%d, %d, %d, '%s', '%s', %d, 0, 0, '%s', %d)", CALDAV_SQL_DB, CALDAV_SQL_PREFIX,
+ IntVal($add[0]), IntVal($add[1]), IntVal($new["contact"]), dbesc($new["carddata"]), dbesc($new["uri"]), time(), md5($new["carddata"]), strlen($new["carddata"])
+ );
+ }
+ return array_merge($contacts, $new_found);
+ }
+
+ /**
+ * Returns a specfic card.
+ *
+ * The same set of properties must be returned as with getCards. The only
+ * exception is that 'carddata' is absolutely required.
+ *
+ * @param mixed $addressBookId
+ * @param string $cardUri
+ * @throws Sabre_DAV_Exception_NotFound
+ * @return array
+ */
+ public function getCard($addressBookId, $cardUri)
+ {
+ $x = explode("-", $addressBookId);
+ $x = q("SELECT id, carddata, uri, lastmodified, etag, size FROM %s%scards WHERE namespace = %d AND namespace_id = %d AND uri = '%s'",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
+ if (count($x) == 0) throw new Sabre_DAV_Exception_NotFound();
+ return $x[0];
+ }
+
+ /**
+ * Creates a new card.
+ *
+ * The addressbook id will be passed as the first argument. This is the
+ * same id as it is returned from the getAddressbooksForUser method.
+ *
+ * The cardUri is a base uri, and doesn't include the full path. The
+ * cardData argument is the vcard body, and is passed as a string.
+ *
+ * It is possible to return an ETag from this method. This ETag is for the
+ * newly created resource, and must be enclosed with double quotes (that
+ * is, the string itself must contain the double quotes).
+ *
+ * You should only return the ETag if you store the carddata as-is. If a
+ * subsequent GET request on the same card does not have the same body,
+ * byte-by-byte and you did return an ETag here, clients tend to get
+ * confused.
+ *
+ * If you don't return an ETag, you can just return null.
+ *
+ * @param string $addressBookId
+ * @param string $cardUri
+ * @param string $cardData
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return string
+ */
+ public function createCard($addressBookId, $cardUri, $cardData)
+ {
+ throw new Sabre_DAV_Exception_Forbidden();
+ }
+
+ /**
+ * Updates a card.
+ *
+ * The addressbook id will be passed as the first argument. This is the
+ * same id as it is returned from the getAddressbooksForUser method.
+ *
+ * The cardUri is a base uri, and doesn't include the full path. The
+ * cardData argument is the vcard body, and is passed as a string.
+ *
+ * It is possible to return an ETag from this method. This ETag should
+ * match that of the updated resource, and must be enclosed with double
+ * quotes (that is: the string itself must contain the actual quotes).
+ *
+ * You should only return the ETag if you store the carddata as-is. If a
+ * subsequent GET request on the same card does not have the same body,
+ * byte-by-byte and you did return an ETag here, clients tend to get
+ * confused.
+ *
+ * If you don't return an ETag, you can just return null.
+ *
+ * @param string $addressBookId
+ * @param string $cardUri
+ * @param string $cardData
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return string|null
+ */
+ public function updateCard($addressBookId, $cardUri, $cardData)
+ {
+ $x = explode("-", $addressBookId);
+
+ $etag = md5($cardData);
+ q("UPDATE %s%scards SET carddata = '%s', lastmodified = %d, etag = '%s', size = %d, manually_edited = 1 WHERE uri = '%s' AND namespace = %d AND namespace_id =%d",
+ CALDAV_SQL_DB, CALDAV_SQL_PREFIX, dbesc($cardData), time(), $etag, strlen($cardData), dbesc($cardUri), IntVal($x[10]), IntVal($x[1])
+ );
+ q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
+
+ return '"' . $etag . '"';
+ }
+
+ /**
+ * Deletes a card
+ *
+ * @param string $addressBookId
+ * @param string $cardUri
+ * @throws Sabre_DAV_Exception_Forbidden
+ * @return bool
+ */
+ public function deleteCard($addressBookId, $cardUri)
+ {
+ $x = explode("-", $addressBookId);
+
+ q("UPDATE %s%scards SET manually_deleted = 1 WHERE namespace = %d AND namespace_id = %d AND uri = '%s'", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[0]), IntVal($x[1]), dbesc($cardUri));
+ q('UPDATE %s%saddressbooks_community SET ctag = ctag + 1 WHERE uid = %d', CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($x[1]));
+
+ return true;
+ }
+}
<?php
-class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBasic {
+class Sabre_DAV_Auth_Backend_Std extends Sabre_DAV_Auth_Backend_AbstractBasic {
public function __construct() {
}
- public function getUsers() {
+ /**
+ * @var Sabre_DAV_Auth_Backend_Std|null
+ */
+ private static $intstance = null;
+
+ /**
+ * @static
+ * @return Sabre_DAV_Auth_Backend_Std
+ */
+ public static function &getInstance() {
+ if (is_null(self::$intstance)) {
+ self::$intstance = new Sabre_DAV_Auth_Backend_Std();
+ }
+ return self::$intstance;
+ }
+
+
+ /**
+ * @return array
+ */
+ public function getUsers() {
return array($this->currentUser);
}
-
- public function getCurrentUser() {
+
+ /**
+ * @return null|string
+ */
+ public function getCurrentUser() {
return $this->currentUser;
}
*/
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);
}
+ /**
+ * @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);
}
}
<?php
-class Sabre_DAVACL_PrincipalBackend_Friendica implements Sabre_DAVACL_IPrincipalBackend
+class Sabre_DAVACL_PrincipalBackend_Std implements Sabre_DAVACL_IPrincipalBackend
{
/**
}
+ /**
+ * @var Sabre_DAVACL_IPrincipalBackend|null
+ */
+ private static $intstance = null;
+
+ /**
+ * @static
+ * @return Sabre_DAVACL_IPrincipalBackend
+ */
+ public static function &getInstance() {
+ if (is_null(self::$intstance)) {
+ $authBackend = Sabre_DAV_Auth_Backend_Std::getInstance();
+ self::$intstance = new Sabre_DAVACL_PrincipalBackend_Std($authBackend);
+ }
+ return self::$intstance;
+ }
+
/**
* Returns a list of principals based on a prefix.
*
--- /dev/null
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*!
+ * jQuery UI CSS Framework 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*!
+ * jQuery UI Dialog 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*!
+ * jQuery UI Datepicker 1.8.21
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ display: none; /*sorry for IE5*/
+ display/**/: block; /*sorry for IE5*/
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}
\ No newline at end of file
--- /dev/null
+/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;return b[d]>0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.widget.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){return c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}}),d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;return e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e,f&&e.charAt(0)==="_"?h:(f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b)return h=f,!1}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))}),h)}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}return this._setOptions(e),this},_setOptions:function(b){var c=this;return a.each(b,function(a,b){c._setOption(a,b)}),this},_setOption:function(a,b){return this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);return this.element.trigger(c,d),!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.position.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.dialog.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("<span></span>").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('<button type="button"></button>').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return!1})},1),a(document).bind("keydown.dialog-overlay",function(c){b.options.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}),a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize));var c=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b<c?a(window).height()+"px":b+"px"):a(document).height()+"px"},width:function(){var b,c;return a.browser.msie?(b=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),c=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),b<c?a(window).width()+"px":b+"px"):a(document).width()+"px"},resize:function(){var b=a([]);a.each(a.ui.dialog.overlay.instances,function(){b=b.add(this)}),b.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}}),a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.datepicker.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);return c&&!c.inline&&this._setDateFromField(c,b),c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;return c&&s++,c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;return r+=f[0].length,parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase())return f=c[0],r+=d.length,!1});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;return c&&m++,c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;return c&&e++,c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()==a.lastVal)return;var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;return b.setDate(b.getDate()+a),b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());return f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0)),this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){return a?(a.setHours(a.getHours()>12?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', -"+i+", 'M');\""+' title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', +"+i+", 'M');\""+' title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+a.id+"');\""+">"+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+a.id+"',"+Y.getMonth()+","+Y.getFullYear()+', this);return false;"')+">"+(bb&&!G?" ":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" "+">";for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="</div>",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;return e=d&&e>d?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;
\ No newline at end of file
--- /dev/null
+/* 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']);
+});
{
$a = get_app();
- $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.css' . '" media="all" />' . "\r\n";
- $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.min.js"></script>' . "\r\n";
+ $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.css' . '" media="all" />' . "\r\n";
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.min.js"></script>' . "\r\n";
$a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal.css' . '" media="all" />' . "\r\n";
$a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal.js"></script>' . "\r\n";
switch (get_config("system", "language")) {
case "de":
$a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_DE.js"></script>' . "\r\n";
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery.ui.datepicker-de.js"></script>' . "\r\n";
break;
default:
$a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_EN.js"></script>' . "\r\n";
*/
function wdcal_addRequiredHeadersEdit()
{
- $a = get_app();
- $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.css' . '" media="all" />' . "\r\n";
- $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.20.custom.min.js"></script>' . "\r\n";
+ $a = get_app();
+ $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
+
+ $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.css' . '" media="all" />' . "\r\n";
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery-ui-1.8.21.custom.min.js"></script>' . "\r\n";
$a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/colorpicker/colorPicker.css' . '" media="all" />' . "\r\n";
$a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/colorpicker/jquery.colorPicker.min.js"></script>' . "\r\n";
$a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/dav/wdcal.css' . '" media="all" />' . "\r\n";
$a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal.js"></script>' . "\r\n";
+ switch ($localization->getLanguageCode()) {
+ case "de":
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_DE.js"></script>' . "\r\n";
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/jqueryui/jquery.ui.datepicker-de.js"></script>' . "\r\n";
+ break;
+ default:
+ $a->page['htmlhead'] .= '<script type="text/javascript" src="' . $a->get_baseurl() . '/addon/dav/common/wdcal/js/wdCalendar_lang_EN.js"></script>' . "\r\n";
+ }
+
+}
+
+/**
+ * @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
* @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,
<div id="animexxcalendar" class="animexxcalendar">
<div class="calselect"><strong>Available Calendars:</strong>';
- foreach ($cals_avail as $cal) {
- $x .= '<label style="margin-left: 10px; margin-right: 10px;"><input type="checkbox" name="cals[]" value="' . $cal["ns"] . '-' . $cal["id"] . '"';
+ foreach ($calendars as $cal) {
+ $cal_id = $cal->getProperties(array("id", DAV_DISPLAYNAME));
+ $x .= '<label style="margin-left: 10px; margin-right: 10px;"><input type="checkbox" name="cals[]" value="' . $cal_id["id"] . '"';
$found = false;
- foreach ($calendar_preselected as $pre) if ($pre["ns"] == $cal["ns"] && $pre["id"] == $cal["id"]) $found = true;
+ foreach ($calendars_selected as $pre) if ($pre["id"] == $cal_id["id"]) $found = true;
if ($found) $x .= ' checked';
- $x .= '> ' . escape_tags($cal["displayname"]) . '</label> ';
+ $x .= '> ' . escape_tags($cal_id[DAV_DISPLAYNAME]) . '</label> ';
}
$x .= '</div>
/**
- * @param string $uri
+ * @param int $calendar_id
+ * @param int $calendarobject_id
* @param string $recurr_uri
* @return string
*/
-function wdcal_getDetailPage($uri, $recurr_uri)
+function wdcal_getDetailPage($calendar_id, $calendarobject_id, $recurr_uri)
{
$a = get_app();
- $details = null;
- $cals = dav_getMyCals($a->user["uid"]);
- foreach ($cals as $c) {
- $cs = wdcal_calendar_factory($a->user["uid"], $c->namespace, $c->namespace_id);
- $p = $cs->getPermissionsItem($a->user["uid"], $uri, $recurr_uri);
- if ($p["read"]) try {
- $redirect = $cs->getItemDetailRedirect($uri);
- if ($redirect !== null) goaway($redirect);
- $details = $cs->getItemByUri($uri);
- } catch (Exception $e) {
- notification(t("Error") . ": " . $e);
- goaway($a->get_baseurl() . "/dav/wdcal/");
- }
- }
+ try {
+ $details = null;
+ $server = dav_create_server(true, true, false);
+ $cal = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_READ);
+ $obj = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($calendarobject_id);
+ dav_get_current_user_calendarobject($server, $cal, $obj["uri"], DAV_ACL_READ); // Check permissions
+
+ $calbackend = wdcal_calendar_factory_by_id($calendar_id);
+ $redirect = $calbackend->getItemDetailRedirect($calendar_id, $calendarobject_id);
+ if ($redirect !== null) goaway($a->get_baseurl() . $redirect);
- return $uri . " / " . $recurr_uri . "<br>" . print_r($details, true);
+ $details = $obj;
+ } catch (Exception $e) {
+ info(t("Error") . ": " . $e);
+ goaway($a->get_baseurl() . "/dav/wdcal/");
+ }
+
+ return print_r($details, true);
}
+
/**
- * @param string $uri
+ * @param int $calendar_id
+ * @param int $uri
* @param string $recurr_uri
* @return string
*/
-function wdcal_getEditPage($uri, $recurr_uri = "")
+function wdcal_getEditPage($calendar_id, $uri, $recurr_uri = "")
{
-
$a = get_app();
$localization = wdcal_local::getInstanceByUser($a->user["uid"]);
- if ($uri != "" && $uri != "new") {
- $o = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
- );
- if (count($o) != 1) return t('Not found');
- $event = $o[0];
-
- $calendarSource = wdcal_calendar_factory($a->user["uid"], $event["namespace"], $event["namespace_id"]);
-
- $permissions = $calendarSource->getPermissionsItem($a->user["uid"], $uri, $recurr_uri, $event);
-
- if (!$permissions["write"]) return t('No access');
-
- $n = q("SELECT * FROM %s%snotifications WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'",
- CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri)
- );
- if (count($n) > 0) {
- $notification_type = $n[0]["rel_type"];
- $notification_value = -1 * $n[0]["rel_value"];
- $notification = true;
- } else {
- if ($event["IsAllDayEvent"]) {
- $notification_type = "hour";
- $notification_value = 24;
- } else {
- $notification_type = "minute";
- $notification_value = 60;
- }
- $notification = false;
- }
-
-
- } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) {
- $event = array(
- "id" => 0,
- "Subject" => $_REQUEST["title"],
- "Location" => "",
- "Description" => "",
- "StartTime" => wdcal_php2MySqlTime($_REQUEST["start"]),
- "EndTime" => wdcal_php2MySqlTime($_REQUEST["end"]),
- "IsAllDayEvent" => $_REQUEST["isallday"],
- "Color" => null,
- "RecurringRule" => null,
- );
- if ($_REQUEST["isallday"]) {
- $notification_type = "hour";
- $notification_value = 24;
- } else {
- $notification_type = "hour";
- $notification_value = 1;
- }
+ return wdcal_getEditPage_str($localization, $a->get_baseurl(), $a->user["uid"], $calendar_id, $uri, $recurr_uri);
+}
- $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;
- }
+function wdcal_getNewPage()
+{
+ $a = get_app();
+ $localization = wdcal_local::getInstanceByUser($a->user["uid"]);
- $postto = $a->get_baseurl() . "/dav/wdcal/" . ($uri == "new" ? "new/" : $uri . "/edit/");
-
- $out = "<a href='" . $a->get_baseurl() . "/dav/wdcal/'>" . t("Go back to the calendar") . "</a><br><br>";
- $out .= "<form method='POST' action='$postto'><input type='hidden' name='form_security_token' value='" . get_form_security_token('caledit') . "'>\n";
-
- $out .= "<label for='cal_subject'>Subject:</label>
- <input name='color' id='cal_color' value='" . (strlen($event["Color"]) != 7 ? "#5858ff" : escape_tags($event["Color"])) . "'>
- <input name='subject' id='cal_subject' value='" . escape_tags($event["Subject"]) . "'><br>\n";
- $out .= "<label for='cal_allday'>Is All-Day event:</label><input type='checkbox' name='allday' id='cal_allday' " . ($event["IsAllDayEvent"] ? "checked" : "") . "><br>\n";
-
- $out .= "<label for='cal_startdate'>" . t("Starts") . ":</label>";
- $out .= "<input name='start_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["StartTime"])) . "' id='cal_start_date'>";
- $out .= "<input name='start_time' value='" . substr($event["StartTime"], 11, 5) . "' id='cal_start_time'>";
- $out .= "<br>\n";
-
- $out .= "<label for='cal_enddate'>" . t("Ends") . ":</label>";
- $out .= "<input name='end_date' value='" . $localization->dateformat_datepicker_php(wdcal_mySql2PhpTime($event["EndTime"])) . "' id='cal_end_date'>";
- $out .= "<input name='end_time' value='" . substr($event["EndTime"], 11, 5) . "' id='cal_end_time'>";
- $out .= "<br>\n";
-
- $out .= "<label for='cal_location'>" . t("Location") . ":</label><input name='location' id='cal_location' value='" . escape_tags($event["Location"]) . "'><br>\n";
-
- $out .= "<label for='event-desc-textarea'>" . t("Description") . ":</label> <textarea id='event-desc-textarea' name='wdcal_desc' style='vertical-align: top; width: 400px; height: 100px;'>" . escape_tags($event["Description"]) . "</textarea>";
- $out .= "<br style='clear: both;'>";
-
- $out .= "<label for='notification'>" . t('Notification') . ":</label>";
- $out .= '<input type="checkbox" name="notification" id="notification" ';
- if ($notification) $out .= "checked";
- $out .= '> ';
- $out .= '<span id="notification_detail" style="display: none;">
- <input name="notification_value" value="' . $notification_value . '" size="3">
- <select name="notification_type" size="1">
- <option value="minute" ';
- if ($notification_type == "minute") $out .= "selected";
- $out .= '> ' . t('Minutes') . '</option>
- <option value="hour" ';
- if ($notification_type == "hour") $out .= "selected";
- $out .= '> ' . t('Hours') . '</option>
- <option value="day" ';
- if ($notification_type == "day") echo "selected";
- $out .= '> ' . t('Days') . '</option>
- </select> ' . t('before') . '
- </span><br><br>';
-
-
- $out .= "<script>\$(function() {
- wdcal_edit_init('" . $localization->dateformat_datepicker_js() . "');
- });</script>";
-
- $out .= "<input type='submit' name='save' value='Save'></form>";
-
- return $out;
+ return wdcal_getEditPage_str($localization, $a->get_baseurl(), $a->user["uid"], 0, 0);
}
function dav_include_files()
{
- require_once (__DIR__ . "/common/dbclasses/dbclass_animexx.class.php");
- require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendars.class.php");
- require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.jqcalendar.class.php");
- require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.notifications.class.php");
- require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendarobjects.class.php");
-
/*
require_once (__DIR__ . "/SabreDAV/lib/Sabre.includes.php");
require_once (__DIR__ . "/SabreDAV/lib/Sabre/VObject/includes.php");
*/
require_once (__DIR__ . "/SabreDAV/lib/Sabre/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");
}
dav_include_files();
- if (false) {
+ if (true) {
dbg(true);
error_reporting(E_ALL);
ini_set("display_errors", 1);
}
return;
}
+ if ($a->argc >= 2 && $a->argv[1] == "getExceptionDates") {
+ echo wdcal_getEditPage_exception_selector();
+ killme();
+ }
if ($a->argc >= 2 && $a->argv[1] == "settings") {
return;
}
- $authBackend = new Sabre_DAV_Auth_Backend_Friendica();
- $principalBackend = new Sabre_DAVACL_PrincipalBackend_Friendica($authBackend);
- $caldavBackend_std = new Sabre_CalDAV_Backend_Std();
- $caldavBackend_community = new Sabre_CalDAV_Backend_Friendica();
- $carddavBackend_std = new Sabre_CardDAV_Backend_Std();
- $carddavBackend_community = new Sabre_CardDAV_Backend_FriendicaCommunity();
-
- if (isset($_SERVER["PHP_AUTH_USER"])) {
- $tree = new Sabre_DAV_SimpleCollection('root', array(
- new Sabre_DAV_SimpleCollection('principals', array(
- new Sabre_CalDAV_Principal_Collection($principalBackend, "principals/users"),
- )),
- new Sabre_CalDAV_AnimexxCalendarRootNode($principalBackend, array(
- $caldavBackend_std,
- $caldavBackend_community,
- )),
- new Sabre_CardDAV_AddressBookRootFriendica($principalBackend, array(
- $carddavBackend_std,
- $carddavBackend_community,
- )),
- ));
- } else {
- $tree = new Sabre_DAV_SimpleCollection('root', array());
- }
-
-// The object tree needs in turn to be passed to the server class
- $server = new Sabre_DAV_Server($tree);
-
- $url = parse_url($a->get_baseurl());
- $server->setBaseUri(CALDAV_URL_PREFIX);
-
- $authPlugin = new Sabre_DAV_Auth_Plugin($authBackend, 'SabreDAV');
- $server->addPlugin($authPlugin);
-
- $aclPlugin = new Sabre_DAVACL_Plugin_Friendica();
- $aclPlugin->defaultUsernamePath = "principals/users";
- $server->addPlugin($aclPlugin);
- $caldavPlugin = new Sabre_CalDAV_Plugin();
- $server->addPlugin($caldavPlugin);
+ if (isset($_REQUEST["test"])) {
+ renderAllCalDavEntries();
+ }
- $carddavPlugin = new Sabre_CardDAV_Plugin();
- $server->addPlugin($carddavPlugin);
+ $server = dav_create_server();
$browser = new Sabre_DAV_Browser_Plugin();
$server->addPlugin($browser);
-
$server->exec();
killme();
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;
{
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);
}
/**
{
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);
}
/**
{
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"])) {
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();
.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