diff --git a/dav/wdcal/Changelog.txt b/dav/Changelog.txt similarity index 50% rename from dav/wdcal/Changelog.txt rename to dav/Changelog.txt index 9cc23a4ad..90117b718 100644 --- a/dav/wdcal/Changelog.txt +++ b/dav/Changelog.txt @@ -1,3 +1,11 @@ +v0.2.0 +====== +[FEATURE] Multiple private Calendars can be created. Each calendar can have its own default color; single events of a calendar can override this setting. +[FEATURE] Support for recurring events. +[FEATURE] ICS files can be imported to and exported from a calendar. +[FEATURE] Notification by e-mail is supported. +[COMPATIBILITY] When creating or updating an event using CalDAV, the etag is returned. + v0.1.1 ====== [FEATURE] A "New Event" Button in the navigation bar of the calendar is added. @@ -5,6 +13,6 @@ v0.1.1 [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 +v0.1.0 ====== Initial Release \ No newline at end of file diff --git a/dav/README.md b/dav/README.md index bab0a1dda..a82439722 100644 --- a/dav/README.md +++ b/dav/README.md @@ -6,10 +6,15 @@ It's still in a very early stage, so expect major bugs. Please feel free to repo At the moment, the calendar system supports the following features: - A web-based drag&drop interface for managing events - All-Day-Events, Multi-Day-Events, and time-based events -- Access to the events using CalDAV (using iPhone, Thunderbird Lightning etc., see below) -- 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) +- Recurrences (not the whole set of options given in the iCalendar spec, but the most important ones) +- Notification by e-mail. Multiple notifications can be set per event +- Multiple calendars per user +- Access to the events using CalDAV (using iPhone, Thunderbird Lightning etc., see below) +- Read-only access to the friendica-native events (also using CalDAV) +- The friendica-contacts are made available using CardDAV (confirmed to work with iOS) +- The events of a calendar can be exported as ICS file. ICS files can be imported into a calendar + Internationalization: - At the moment, settings for the US and the german systems are selectable (regarding the date format and the first day of the week). More will be added on request. @@ -17,8 +22,10 @@ Internationalization: CalDAV device compatibility: - iOS (iPhone/iPodTouch) works -- Thunderbird Lightning should work, not tested yet -- Android: http://dmfs.org/caldav/ seems to work, not much tested yet, though +- Thunderbird Lightning works +- Android: + - aCal (http://andrew.mcmillan.net.nz/projects/aCal) works, available in F-Droid and Google Play + - CalDAV-Sync (http://dmfs.org/caldav/) works, non-free Installation After activating, serveral tables in the database have to be created. The admin-interface of the plugin will try to do this automatically. @@ -26,12 +33,10 @@ In case of errors, the SQL-statement to create the tables manually are shown in Functuality missing: (a.k.a. "Roadmap") -- Recurrence of events (this is only supported using the CalDAV-interface; recurring events saved using CalDAV will appear correctly multiple times in the web-based frontend; hovever those events will be read-only at the web-based frondend) -- Sharing events; all events are private at the moment, therefore this system is not yet a complete replacement for the friendica-native events +- Sharing events; all events are private at the moment, therefore this system is not a complete replacement for the friendica-native events - Attendees / Collaboration - Used libraries SabreDAV @@ -46,10 +51,6 @@ jQueryUI http://jqueryui.com/ Dual-licenced: MIT and GPL licenses -iCalCreator -http://kigkonsult.se/iCalcreator/ -GNU Lesser General Public License - TimePicker http://www.texotela.co.uk/code/jquery/timepicker/ Dual-licenced: MIT and GPL licenses diff --git a/dav/SabreDAV/ChangeLog b/dav/SabreDAV/ChangeLog index 7ba68c47d..1199ae766 100644 --- a/dav/SabreDAV/ChangeLog +++ b/dav/SabreDAV/ChangeLog @@ -1,31 +1,63 @@ 1.7.0-alpha (2012-??-??) - * BC Break: The calendarobjects database table has a bunch of new fields, - and a migration script is required to ensure everything will keep - working. Read the wiki for more details. + * BC Break: The calendarobjects database table has a bunch of new + fields, and a migration script is required to ensure everything will + keep working. Read the wiki for more details. * BC Break: The iCalendar interface now has a new method: calendarQuery. * BC Break: In this version a number of classes have been deleted, that have been previously deprecated. Namely: - Sabre_DAV_Directory (now: Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now: Sabre_DAV_SimpleCollection) - - Sabre_VObject_Element_DateTime (now: Sabre_VObject_Property_DateTime) - - Sabre_VObject_Element_MultiDateTime (now .._Property_MultiDateTime) + - Sabre_VObject_Element_DateTime (now: .._Property_DateTime) + - Sabre_VObject_Element_MultiDateTime (-> .._Property_MultiDateTime) * BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument. If you extended this class, you should fix this method. It's only used for informational purposes. - * Changed: Responsibility for dealing with the calendar-query is now moved - from the CalDAV plugin to the CalDAV backends. This allows for heavy - optimizations. - * Changed: The CalDAV PDO backend is now a lot faster for common calendar - queries. - * Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded. + * BC Break: The DAV: namespace is no longer converted to urn:DAV. This was + a workaround for a bug in older PHP versions (pre-5.3). + * New feature: Support for caldav notifications! + * Changed: Responsibility for dealing with the calendar-query is now + moved from the CalDAV plugin to the CalDAV backends. This allows for + heavy optimizations. + * Changed: The CalDAV PDO backend is now a lot faster for common + calendar queries. + * Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 + encoded. * Fixed: Workaround for the SOGO connector, as it doesn't understand receiving "text/x-vcard; charset=utf-8" for a contenttype. * Added: Sabre_DAV_Client now throws more specific exceptions in cases where we already has an exception class. - * Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH - method to update parts of a file. + * Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the + PATCH method to update parts of a file. + * Added: Tons of timezone name mappings for Microsoft Exchange. + * Added: Support for an 'exception' event. + * Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!) + * Fixed: Rejecting calendar objects if they are not in the + supported-calendar-component list. (thanks Armin!) + * Fixed: Workaround for 10.8 Mountain Lion vCards, as it needs \r line + endings to parse them correctly. + * Fixed: Issue 219: serialize() now reorders correctly. + * Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes + if there is whitespace in $dom. -1.6.3-stable (2012-??-??) +1.6.4-stable (2012-??-??) + * Fixed: Issue 220: Calendar-query filters may fail when filtering on + alarms, if an overridden event has it's alarm removed. + * Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler. + * Fixed: Issue 222: beforeWriteContent shouldn't be called for lock + requests. + * Fixed: Problem with POST requests to the outbox if mailto: was not lower + cased. + * Fixed: Yearly recurrence rule expansion on leap-days no behaves + correctly. + * Fixed: Correctly checking if recurring, all-day events with no dtstart + fall in a timerange if the start of the time-range exceeds the start of + the instance of an event, but not the end. + * Fixed: All-day recurring events wouldn't match if an occurence ended + exactly on the start of a time-range. + * Fixed: HTTP basic auth did not correctly deal with passwords containing + colons on some servers. + +1.6.3-stable (2012-06-12) * Added: It's now possible to specify in Sabre_DAV_Client which type of authentication is to be used. * Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed. @@ -43,6 +75,7 @@ compatibility. * Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See https://bugs.kde.org/show_bug.cgi?id=300047 + * Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken. 1.6.2-stable (2012-04-16) * Fixed: Sabre_VObject_Node::$parent should have been public. diff --git a/dav/SabreDAV/README.md b/dav/SabreDAV/README.md new file mode 100644 index 000000000..15e859331 --- /dev/null +++ b/dav/SabreDAV/README.md @@ -0,0 +1,42 @@ +# What is SabreDAV + +SabreDAV allows you to easily add WebDAV support to a PHP application. SabreDAV is meant to cover the entire standard, and attempts to allow integration using an easy to understand API. + +### Feature list: + +* Fully WebDAV compliant +* Supports Windows XP, Windows Vista, Mac OS/X, DavFSv2, Cadaver, Netdrive, Open Office, and probably more. +* Passing all Litmus tests. +* Supporting class 1, 2 and 3 Webdav servers. +* Locking support. +* Custom property support. +* CalDAV (tested with [Evolution](http://code.google.com/p/sabredav/wiki/Evolution), [iCal](http://code.google.com/p/sabredav/wiki/ICal), [iPhone](http://code.google.com/p/sabredav/wiki/IPhone) and [Lightning](http://code.google.com/p/sabredav/wiki/Lightning)). +* CardDAV (tested with [OS/X addressbook](http://code.google.com/p/sabredav/wiki/OSXAddressbook), the [iOS addressbook](http://code.google.com/p/sabredav/wiki/iOSCardDAV) and [Evolution](http://code.google.com/p/sabredav/wiki/Evolution)). +* Over 97% unittest code coverage. + +### Supported RFC's: + +* [RFC2617](http://www.ietf.org/rfc/rfc2617.txt): Basic/Digest auth. +* [RFC2518](http://www.ietf.org/rfc/rfc2518.txt): First WebDAV spec. +* [RFC3744](http://www.ietf.org/rfc/rfc3744.txt): ACL (some features missing). +* [RFC4709](http://www.ietf.org/rfc/rfc4709.txt): [DavMount](http://code.google.com/p/sabredav/wiki/DavMount). +* [RFC4791](http://www.ietf.org/rfc/rfc4791.txt): CalDAV. +* [RFC4918](http://www.ietf.org/rfc/rfc4918.txt): WebDAV revision. +* [RFC5397](http://www.ietf.org/rfc/rfc5689.txt): current-user-principal. +* [RFC5689](http://www.ietf.org/rfc/rfc5689.txt): Extended MKCOL. +* [RFC5789](http://tools.ietf.org/html/rfc5789): PATCH method for HTTP. +* [RFC6352](http://www.ietf.org/rfc/rfc6352.txt): CardDAV +* [draft-daboo-carddav-directory-gateway](http://tools.ietf.org/html/draft-daboo-carddav-directory-gateway): CardDAV directory gateway +* CalDAV ctag, CalDAV-proxy. + +## Live Demo + +### Head over to: + +* Url: [http://demo.sabredav.org/public/](http://demo.sabredav.org/public/) +* Username: testuser +* Password: test + +**Please note:** Due to the webserver stack (nginx with varnish) some clients will not work correctly. At the very least this includes Finder and Cyberduck. Any client using chunked transfer encoding or expect *100-Continue* will fail. + +The demo site is kindly hosted by sourceforge, so take it easy with the diskspace. It's limited! \ No newline at end of file diff --git a/dav/SabreDAV/bin/migrateto17.php b/dav/SabreDAV/bin/migrateto17.php index 4340013b5..89207877c 100644 --- a/dav/SabreDAV/bin/migrateto17.php +++ b/dav/SabreDAV/bin/migrateto17.php @@ -95,7 +95,12 @@ foreach($fields17 as $field) { if ($found === 0) { echo "The database had the 1.6 schema. Table will now be altered.\n"; echo "This may take some time for large tables\n"; - $pdo->exec(<<getAttribute(PDO::ATTR_DRIVER_NAME)) { + + case 'mysql' : + + $pdo->exec(<<exec('ALTER TABLE calendarobjects ADD etag text'); + $pdo->exec('ALTER TABLE calendarobjects ADD size integer'); + $pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT'); + $pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer'); + $pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer'); + break; + + default : + die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n"); + + } echo "Database schema upgraded.\n"; } elseif ($found === 5) { @@ -205,7 +223,7 @@ function getDenormalizedData($calendarData) { } } else { $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID); - $maxDate = new DateTime(self::MAX_DATE); + $maxDate = new DateTime(Sabre_CalDAV_Backend_PDO::MAX_DATE); if ($it->isInfinite()) { $lastOccurence = $maxDate->getTimeStamp(); } else { diff --git a/dav/SabreDAV/docs/caldav-notifications.txt b/dav/SabreDAV/docs/caldav-notifications.txt new file mode 100644 index 000000000..75c2e5e07 --- /dev/null +++ b/dav/SabreDAV/docs/caldav-notifications.txt @@ -0,0 +1,1568 @@ + + + +Calendar Server Extension C. Daboo + Apple Inc. + March 19, 2012 + + + CalDAV: Calendar User Notifications + +Abstract + + This specification defines an extension to CalDAV that allows the + server to provide notifications to calendar users. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Daboo [Page 1] + + CalDAV User Notifications March 2012 + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 2. Open Issues . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 3. Conventions Used in This Document . . . . . . . . . . . . . . 3 + 4. Notifications . . . . . . . . . . . . . . . . . . . . . . . . 3 + 4.1. Additional Principal Properties . . . . . . . . . . . . . 4 + 4.1.1. CS:notification-URL Property . . . . . . . . . . . . . 5 + 4.2. Properties on Notification Resources . . . . . . . . . . . 5 + 4.2.1. CS:notificationtype Property . . . . . . . . . . . . . 5 + 4.3. XML Element Definitions . . . . . . . . . . . . . . . . . 6 + 4.3.1. CS:notifications . . . . . . . . . . . . . . . . . . . 6 + 4.3.2. CS:notification . . . . . . . . . . . . . . . . . . . 6 + 4.3.3. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . 7 + 5. Notification Definitions . . . . . . . . . . . . . . . . . . . 7 + 5.1. System Status Notification . . . . . . . . . . . . . . . . 7 + 5.1.1. CS:systemstatus Element Definition . . . . . . . . . . 8 + 5.2. Quota Notification . . . . . . . . . . . . . . . . . . . . 8 + 5.2.1. CS:quotastatus Element Definition . . . . . . . . . . 9 + 5.3. Resource Changes Notification . . . . . . . . . . . . . . 10 + 5.3.1. CS:resource-change Element Definition . . . . . . . . 11 + 5.3.2. CS:calendar-changes Element Definition . . . . . . . . 15 + 5.3.2.1. Handling Recurrences in CS:calendar-changes . . . 17 + 5.3.3. CS:deleted-details Element Definition . . . . . . . . 18 + 5.3.4. CS:notify-changes Property . . . . . . . . . . . . . . 20 + 6. Security Considerations . . . . . . . . . . . . . . . . . . . 20 + 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 21 + 8. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 21 + 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 21 + 9.1. Normative References . . . . . . . . . . . . . . . . . . . 21 + 9.2. Informative References . . . . . . . . . . . . . . . . . . 21 + Appendix A. Examples . . . . . . . . . . . . . . . . . . . . . . 21 + A.1. Resource Created . . . . . . . . . . . . . . . . . . . . . 21 + A.2. Resource Updated - Property Change . . . . . . . . . . . . 22 + A.3. Resource Updated - Parameter Change . . . . . . . . . . . 23 + A.4. Resource Updated - Multiple Instances Change . . . . . . . 23 + A.5. Resource Updated - Multiple User Change . . . . . . . . . 24 + A.6. Resource Deleted . . . . . . . . . . . . . . . . . . . . . 25 + A.7. Collection Created . . . . . . . . . . . . . . . . . . . . 26 + A.8. Collection Updated . . . . . . . . . . . . . . . . . . . . 26 + A.9. Collection Deleted . . . . . . . . . . . . . . . . . . . . 27 + Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 27 + + + + + + + + + +Daboo [Page 2] + + CalDAV User Notifications March 2012 + + +1. Introduction + + CalDAV [RFC4791] provides a way for calendar users to store calendar + data and exchange this data via scheduling operations. Based on the + WebDAV [RFC4918] protocol, it also includes the ability to manage + access to calendar data via the WebDAV ACL [RFC3744] extension. + + It is often useful for servers to communicate arbitrary information + to calendar users, e.g., system status, message of the day, quota + warnings, changes to shared resources made by others etc. This + specification defines a generic "notification" mechanism that allows + a server to do that. Whilst primarily aimed at CalDAV [RFC4791], + this mechanism has been designed to be adaptable to WebDAV [RFC4918]. + + +2. Open Issues + + 1. Define specific child elements for system status notification, + e.g. "server-maintenance-period", "server-read-only-period", + "client-upgrade-required". + + +3. Conventions Used in This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + When XML element types in the namespaces "DAV:" and + "urn:ietf:params:xml:ns:caldav" are referenced in this document + outside of the context of an XML fragment, the string "DAV:" and + "CALDAV:" will be prefixed to the element type names respectively. + + The namespace "http://calendarserver.org/ns/" is used for XML + elements defined in this specification. When XML element types in + that namespace are referenced in this document outside of the context + of an XML fragment, the string "CS:" will be prefixed to the element + type names. + + +4. Notifications + + When this feature is available, a CS:notification-URL (Section 4.1.1) + property appears on principal resources for those principals who are + able to receive notifications. That property specifies a single DAV: + href element whose content refers to a WebDAV collection resource. + Notification "messages" are deposited into this collection and can be + retrieved by clients and acted on accordingly. + + + +Daboo [Page 3] + + CalDAV User Notifications March 2012 + + + The notification collection referenced by the CS:notification-URL + (Section 4.1.1) property MUST have a DAV:resourcetype property with + DAV:collection and CS:notifications (Section 4.3.1) child elements. + + Notification "messages" are XML documents stored as resources in the + notification collection. Each XML document contains a CS: + notification (Section 4.3.2) element as its root. The root element + contains a CS:dtstamp element, and one additional element which + represents the type of notification being conveyed in the message. + That child element will typically contain additional content that + describes the notification. + + Each notification resource has a CS:notificationtype (Section 4.2.1) + property which contains as its single child element an empty element + that matches the child element of the notification resource XML + document root. Any attributes on the child element in the XML + document are also present in the property child element. + + Notifications are automatically generated by the server (perhaps in + response to a action) with an appropriate resource stored in the + notifications collection of the user to whom the notification is + targeted. Clients SHOULD monitor the notification collection looking + for new notification resources. When doing so, clients SHOULD look + at the CS:notificationtype (Section 4.2.1) property to ensure that + the notification is of a type that the client can handle. Once a + client has handled the notification in whatever way is appropriate it + SHOULD delete the notification resource. Clients SHOULD remove + notifications being displayed to a user when the notification + resource is removed from the notification collection, to enable the + user to dismiss a notification on one device and have it + automatically removed from others. Clients MUST ignore all + notifications for types they do not recognize. Servers MAY delete + notification resources on their own if they determine that the + notifications are no longer relevant or valid. Servers MAY coalesce + notifications as appropriate. + + Servers MUST prevent clients from adding resources in the + notification collection. + +4.1. Additional Principal Properties + + This section defines new properties for WebDAV principal resources as + defined in RFC3744 [RFC3744]. These properties are likely to be + protected but the server MAY allow them to be written by appropriate + users. + + + + + + +Daboo [Page 4] + + CalDAV User Notifications March 2012 + + +4.1.1. CS:notification-URL Property + + Name: notification-URL + + Namespace: http://calendarserver.org/ns/ + + Purpose: Identify the URL of the notification collection owned by + the associated principal resource. + + Protected: This property SHOULD be protected. + + PROPFIND behavior: This property SHOULD NOT be returned by a + PROPFIND allprop request (as defined in Section 14.2 of + [RFC4918]). + + COPY/MOVE behavior: This property value SHOULD be preserved in COPY + and MOVE operations. + + Description: This property is needed for a client to determine where + the notification collection of the current user is located so that + processing of notification messages can occur. If not present, + then the associated calendar user is not enabled for notification + messages on the server. + + Definition: + + + +4.2. Properties on Notification Resources + + The following new WebDAV properties are defined for notification + resources. + +4.2.1. CS:notificationtype Property + + Name: notificationtype + + Namespace: http://calendarserver.org/ns/ + + Purpose: Identify the type of notification of the corresponding + resource. + + Protected: This property MUST be protected. + + PROPFIND behavior: This property SHOULD NOT be returned by a + PROPFIND allprop request (as defined in Section 14.2 of + [RFC4918]). + + + + +Daboo [Page 5] + + CalDAV User Notifications March 2012 + + + COPY/MOVE behavior: This property value MUST be preserved in COPY + and MOVE operations. + + Description: This property allows a client, via a PROPFIND Depth:1 + request, to quickly find notification messages that the client can + handle in a notification collection. The single child element is + the notification resource root element's child defining the + notification itself. This element MUST be empty, though any + attributes on the element in the notification resource MUST be + present in the property element. + + Definition: + + + + +4.3. XML Element Definitions + +4.3.1. CS:notifications + + Name: notifications + + Namespace: http://calendarserver.org/ns/ + + Purpose: Indicates a notification collection. + + Description: This XML element is used in a DAV:resourcetype element + to indicate that the corresponding resource is a notification + collection. + + Definition: + + + +4.3.2. CS:notification + + Name: notification + + Namespace: http://calendarserver.org/ns/ + + Purpose: Notification message root element. + + Description: The root element used in notification resources. + + + + + + + +Daboo [Page 6] + + CalDAV User Notifications March 2012 + + + Definition: + + + + +4.3.3. CS:dtstamp + + Name: dtstamp + + Namespace: http://calendarserver.org/ns/ + + Purpose: Date-time stamp. + + Description: Contains the date-time stamp corresponding to the + creation of a notification message, using the format defined in + [RFC3339], or the "compact" format without "-" and ":" characters + between date and time elements, respectively. + + Definition: + + + + + +5. Notification Definitions + + This section defines a set of common notification types. + +5.1. System Status Notification + + The system status notification is used to convey a URI and/or textual + description to the user. The assumption is that the URI points to a + webpage where current system status is described in detail, with the + provided description being a summary of that. A "type" attribute on + the element is used to indicate the importance of the current status + notification, and has the values "low", "medium" and "high", + representing the increasing level of importance of the message + respectively. + + Servers might have knowledge of specific calendar user language + preferences, in which case it MAY localise the CS:description value + as appropriate based on the calendar user accessing the notification, + but if it does, it SHOULD include an xml:lang attribute on the CS: + description element to indicate what language is being used. + + + + + +Daboo [Page 7] + + CalDAV User Notifications March 2012 + + +5.1.1. CS:systemstatus Element Definition + + Name: systemstatus + + Namespace: http://calendarserver.org/ns/ + + Purpose: Indicates a system status notification. + + Description: This XML element is used in a CS:notification element + to describe a system status notification. + + Definition: + + + + + + + + + Example: This is an example of the body of a notification resource + for an emergency system outage: + + + + 2011-12-09T11:12:53-05:00 + + http://example.com/emergency_shutdown.html + Emergency shutdown now + + + + Example: This is an example of the WebDAV property on the example + notification resource above: + + + + + + +5.2. Quota Notification + + The quota notification is used to convey information about the status + of one or more quotas for the user. The notification contains + elements for different types of quota being reported to the user. In + + + +Daboo [Page 8] + + CalDAV User Notifications March 2012 + + + some cases these may be warnings (e.g., a user getting to 80% of + their quota limit), or in other cases errors (e.g., a user exceeding + their quota). + +5.2.1. CS:quotastatus Element Definition + + Name: quotastatus + + Namespace: http://calendarserver.org/ns/ + + Purpose: Indicates a quota status notification. + + Description: This XML element is used in a CS:notification element + to describe a quota status notification. The CS:quota-percent- + used element contains an integer greater than or equal to zero. + If the value is greater than or equal to 100, then the user's + quota has been reached or exceeded. The DAV:href element contains + a URI for a webpage where the user can go to get further + information about their quota status or take corrective action. + + Definition: + + + + + + + + + + + + + + + + Example: This is an example of the body of a notification resource + for a quota warning: + + + + + + + + + + + + +Daboo [Page 9] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:12:53-05:00 + + + + 80 + https://example.com/your-account.html + + + + + Example: This is an example of the body of a notification resource + for a quota that has been exceeded, and a count-based limit that + is shown as a warning: + + + + 2011-12-09T11:12:53-05:00 + + + + 102 + https://example.com/fix-account.html + + + + 82 + 4980 + https://example.com/buy-more-space.html + + + + +5.3. Resource Changes Notification + + The resource change notification is used to inform the user of new, + updated or deleted resources caused by changes made by someone else + (note: servers MUST NOT generate notifications to users for changes + they themselves make, though the possibility of an automated process + acting on behalf of a user needs to be considered). This + notification can be used by clients to show changes that a user can + acknowledge in their own time. When the notification is present, it + can be displayed on all devices a user is accessing their data from. + When the user acknowledges and dismisses the notification on one + device, other devices SHOULD also remove the notification when they + + + +Daboo [Page 10] + + CalDAV User Notifications March 2012 + + + next synchronize the notification collection. + + A new WebDAV property CS:notify-changes (Section 5.3.4) is defined + for calendar collections. This allows users to enable or disable the + sending of resource change notifications for the calendar and its + child resources. Servers MUST allow users to set this property on a + per-user basis on any calendars accessible to them. Servers MUST + honor the chosen setting to enable or disable change notifications. + + Servers can send notifications for calendar object resources, and + ones for calendar collections. Servers SHOULD coalesce notifications + that refer to the same resource into a single notification resource, + containing multiple CS:created, CS:updated or CS:deleted elements all + with the same DAV:href child element value. Servers MAY coalesce + changes to multiple resources into a change notification for the + parent collection of those resources and use a CS:collection-changes + element to indicate the number of individual resources that changed. + +5.3.1. CS:resource-change Element Definition + + Name: resource-change + + Namespace: http://calendarserver.org/ns/ + + Purpose: Indicates that resources have been created, updated or + deleted. + + Description: This XML element is used in a CS:notification element + to describe a resource change notification. It can describe a + change directly to a calendar object resource or to a calendar + collection. + + When used for a calendar object resource change, it can contain + one of the CS:created, or CS:deleted elements, or multiple CS: + updated elements, which indicate a created, deleted or updated + resource, respectively. The DAV:href element within those + elements, contains the URI of the changed resource, optional + information about who changed the resource and when that change + was made (the CS:changed-by element), and information specific to + the nature of the change. Servers SHOULD coalesce resource change + notifications for the same resource into a single notification + resource where possible. The CS:updated element optionally + contains CS:content and/or DAV:prop elements to indicate a change + to the body of the resource or resource WebDAV properties, + respectively. The DAV:prop element MAY contain a list of property + elements to indicate which properties changed. The CS:updated + element can also contain zero or more CS:calendar-changes elements + to list details of the changes. If no CS:calendar-changes element + + + +Daboo [Page 11] + + CalDAV User Notifications March 2012 + + + is present, the specific details are not provided, and clients + will need to assume that some set of changes occurred, but the + server is unwilling to disclose the full details. The CS:deleted + element can also contain zero or more CS:deleted-details elements + to list details of the deleted resource. + + When used for a calendar collection change, it can contain a CS: + collection-changes element. The DAV:href element within that + element, contains the URI of the changed calendar collection. The + DAV:prop element indicates a change to WebDAV properties on the + calendar collection resource. The CS:child-created, CS:child- + updated, and CS:child-deleted elements each contain a positive + integer value indicating how many child resources were added, + updated or deleted in the collection, respectively. + + Definition: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Daboo [Page 12] + + CalDAV User Notifications March 2012 + + + + + + + + + + + + + + + + + + + + + + + Example: This is an example of the body of a notification resource + for changes where one resource has been created: + + + + + + + + + + + + + + + +Daboo [Page 13] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus Daboo + /principals/cyrusdaboo + + + + + + Example: This is an example of the body of a notification resource + for changes where a resource has been updated twice. One of the + updated resources elements contains additional information + indicating which recurrence instances in the iCalendar data were + changed: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/event.ics + + Oliver + Daboo + mailto:oliver@example.com + + + + http://example.com/cyrus/calendar/event.ics + + Eleanor + Daboo + mailto:eleanor@example.com + + + + + + + + + + + +Daboo [Page 14] + + CalDAV User Notifications March 2012 + + + Example: This is an example of the body of a notification resource + for changes where one resource has been deleted: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/old.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + Example: This example is the same as the previous three, except that + all the individual resource changes have been coalesced into a + single notification about changes to the parent calendar + collection: + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/ + 1 + 2 + 1 + + + + +5.3.2. CS:calendar-changes Element Definition + + Name: calendar-changes + + Namespace: http://calendarserver.org/ns/ + + Purpose: Indicates which portions of an calendar object resource + have changed, or provides details of deleted calendar object + resources. + + + + +Daboo [Page 15] + + CalDAV User Notifications March 2012 + + + Description: This XML element is used in a CS:updated element to + describe how a calendar object resource changed, or in a CS: + deleted element to provide details of a deleted resource. It can + identify the master instance, or individual recurrence instances, + and for each indicate which iCalendar properties and parameters + changed during the update for which the notification was + generated. For details of handling recurrences please see + Section 5.3.2.1. + + Definition: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Example: This example indicates that a non-recurring component, or + the master component in a recurring component, was changed and + that the change was to the "SUMMARY" iCalendar property. + + + + + + +Daboo [Page 16] + + CalDAV User Notifications March 2012 + + + + + + + + + + + + Example: This example indicates that an instance of a recurring + component was changed and that the change was to the "DTSTART" + iCalendar property. + + + + 20111215T160000Z + + + + + + +5.3.2.1. Handling Recurrences in CS:calendar-changes + + Changes to recurring components can be complex. This section + describes the possible set of changes that could occur, and what the + CS:calendar-changes element will contain as a result. + + Master exists, unchanged override added In this case, a CS: + recurrence element will be present, containing a CS:recurrence-id + element with a value equal to the RECURRENCE-ID property value (in + UTC) of the added component. A CS:added element will be present. + There will not be any CS:removed or CS:changes elements. + + Master exists, changed override added In this case, a CS:recurrence + element will be present, containing a CS:recurrence-id element + with a value equal to the RECURRENCE-ID property value (in UTC) of + the added component. Both CS:added and CS:changes elements will + be present. There will not be a CS:removed element. + + Master exists, override changed In this case, a CS:recurrence + element will be present, containing a CS:recurrence-id element + with a value equal to the RECURRENCE-ID property value (in UTC) of + the added component. A CS:changes element will be present. There + will not be any CS:added or CS:removed elements. + + + + + + +Daboo [Page 17] + + CalDAV User Notifications March 2012 + + + Master exists, override removed In this case, a CS:recurrence + element will be present, containing a CS:recurrence-id element + with a value equal to the RECURRENCE-ID property value (in UTC) of + the added component. A CS:removed element will be present. There + will not be a CS:added element. A CS:changes element will only be + present if the removed component differs from the "derived" master + instance. + + Master exists, override cancelled In this case, a CS:recurrence + element will be present, containing a CS:recurrence-id element + with a value equal to the RECURRENCE-ID property value (in UTC) of + the added component. A CS:removed element will be present. There + will not be any CS:added or CS:changes element. There will also + be a CS:master element present, with an appropriate CS:changes + element, likely covering a change to "RRULE" or addition of + "EXDATE" properties. + + Master does not exist, override added In this case, a CS:recurrence + element will be present, containing a CS:recurrence-id element + with a value equal to the RECURRENCE-ID property value (in UTC) of + the added component. A CS:added element will be present. There + will not be a CS:removed or CS:changes element. + + Master does not exist, override changed In this case, a CS: + recurrence element will be present, containing a CS:recurrence-id + element with a value equal to the RECURRENCE-ID property value (in + UTC) of the added component. A CS:changes element will be + present. There will not be any CS:added or CS:removed elements. + + Master does not exist, override removed In this case, a CS: + recurrence element will be present, containing a CS:recurrence-id + element with a value equal to the RECURRENCE-ID property value (in + UTC) of the added component. A CS:removed element will be + present. There will not be any CS:added or CS:changes element. + +5.3.3. CS:deleted-details Element Definition + + Name: deleted-details + + Namespace: http://calendarserver.org/ns/ + + Purpose: Provides summary information about a deleted resource or + collection. + + Description: This XML element is used in a CS:deleted element to + describe useful information about a deleted resource or + collection, so clients can provide a meaningful notification + message to users. This element has two variants: one for deletion + + + +Daboo [Page 18] + + CalDAV User Notifications March 2012 + + + of a calendar object resource, the other for deletion of a + calendar collection. + + Definition: + + + + + + + + + + + + + + + + + + + + + Example: This example indicates deletion of a non-recurring event + that was yet to occur at the time of deletion. + + + VEVENT + Birthday Party + + Holidays + + +5.3.4. CS:notify-changes Property + + Name: notify-changes + + Namespace: http://calendarserver.org/ns/ + + Purpose: Allows a user to specify whether resource change + notifications are generated by the server. + + Protected: This property MUST NOT be protected. + + PROPFIND behavior: This property SHOULD NOT be returned by a + PROPFIND allprop request (as defined in Section 14.2 of + [RFC4918]). + + COPY/MOVE behavior: This property value MUST be preserved in COPY + and MOVE operations. + + Description: This property allows a user to enable or disable the + server generation of resource change notifications for the + calendar collection, and all its child resources, on which the + property resides. If the property is not present on a calendar + collection, the client and server MUST assume that resource change + notifications are enabled. + + Definition: + + + + + + + + +6. Security Considerations + + Some notification mechanisms might allow a user to trigger a + notification to be delivered to other users (e.g., an invitation to + share a calendar). In such cases servers MUST ensure that suitable + limits are placed on the number and frequency of such user generated + notifications. + + + +Daboo [Page 20] + + CalDAV User Notifications March 2012 + + + TBD: More? + + +7. IANA Considerations + + This document does not require any actions on the part of IANA. + + +8. Acknowledgments + + This specification is the result of discussions between the various + Apple calendar server and client teams. + + +9. References + +9.1. Normative References + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC3339] Klyne, G., Ed. and C. Newman, "Date and Time on the + Internet: Timestamps", RFC 3339, July 2002. + + [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed + Authoring and Versioning (WebDAV)", RFC 4918, June 2007. + +9.2. Informative References + + [RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web + Distributed Authoring and Versioning (WebDAV) + Access Control Protocol", RFC 3744, May 2004. + + [RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault, + "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791, + March 2007. + + +Appendix A. Examples + + This section provides more detailed examples of resource change + notifications for illustrative purposes only. + +A.1. Resource Created + + This is an example of the body of a notification resource where one + resource has been created. + + + + +Daboo [Page 21] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + +A.2. Resource Updated - Property Change + + This is an example of the body of a notification resource where one + non-recurring event has had its "DTSTART" and "SUMMARY" iCalendar + property values changed. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + + + + + + + + + + + + + +Daboo [Page 22] + + CalDAV User Notifications March 2012 + + +A.3. Resource Updated - Parameter Change + + This is an example of the body of a notification resource where one + non-recurring event has had the "PARTSTAT" iCalendar property + parameter on an "ATTENDEE" property changed, and a "TRANSP" property + added. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + + + + + + + + + + + + + + +A.4. Resource Updated - Multiple Instances Change + + This is an example of the body of a notification resource where two + instances of a recurring event have their "DTSTART" and "SUMMARY" + iCalendar property values changed. + + + + + + + + + + +Daboo [Page 23] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + 20120209T170000Z + + + + + + + 20120210T170000Z + + + + + + + + + + +A.5. Resource Updated - Multiple User Change + + This is an example of the body of a notification resource where two + instances of a recurring event have their "DTSTART" and "SUMMARY" + iCalendar property values changed. Each instance was changed by a + different user. + + + + + + + + + + + + + +Daboo [Page 24] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + + 20120209T170000Z + + + + + + + + + http://example.com/cyrus/calendar/new.ics + + Eric + York + /principals/ericyork + + + + 20120210T170000Z + + + + + + + + + + +A.6. Resource Deleted + + This is an example of the body of a notification resource where one + resource has been deleted. The resource was a VEVENT whose next + occurrence was in the future on 20120210T170000Z. + + + + +Daboo [Page 25] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/new.ics + + Cyrus + Daboo + /principals/cyrusdaboo + + + VEVENT + CalDAV Meeting + 20120210T170000Z + + + + + +A.7. Collection Created + + This is an example of the body of a notification resource where a + calendar collection has been created. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/new-calendar/ + + Cyrus + Daboo + /principals/cyrusdaboo + + + + + +A.8. Collection Updated + + This is an example of the body of a notification resource where + coalesced changes in a calendar collection are shown. In this case 1 + child resource was created, 2 updated, and 1 deleted. + + + +Daboo [Page 26] + + CalDAV User Notifications March 2012 + + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/calendar/ + 1 + 2 + 1 + + + + +A.9. Collection Deleted + + This is an example of the body of a notification resource where a + calendar collection has been deleted. + + + + 2011-12-09T11:51:14-05:00 + + + http://example.com/cyrus/old-calendar/ + + Cyrus + Daboo + /principals/cyrusdaboo + + + Holidays + + + + + + + + + + + + + + + + + + +Daboo [Page 27] + + CalDAV User Notifications March 2012 + + +Author's Address + + Cyrus Daboo + Apple Inc. + 1 Infinite Loop + Cupertino, CA 95014 + USA + + Email: cyrus@daboo.name + URI: http://www.apple.com/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Daboo [Page 28] + diff --git a/dav/SabreDAV/docs/caldav-sharing-02.txt b/dav/SabreDAV/docs/caldav-sharing-02.txt new file mode 100644 index 000000000..7850e0aac --- /dev/null +++ b/dav/SabreDAV/docs/caldav-sharing-02.txt @@ -0,0 +1,224 @@ + + + + + + + + + + + + + /CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt – Calendar and Contacts Server + + + + + + + + + + + + + + + + +
+ +
+
+
Projects
+ + +
+
+
+ Wiki +     + Timeline +     + Roadmap +     + Browse Source +     + View Tickets +     + New Ticket +     + Search +
+
+ +
+ +
+

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

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

+Sharing extension specification.
+

+
+
+
Line 
1
2
3
4Calendar Server Extension                                       C. Daboo
5                                                                 E. York
6                                                              Apple Inc.
7                                                         October 6, 2010
8
9
10                Shared and Published Calendars in CalDAV
11
12Abstract
13
14   This specification defines an extension to CalDAV that enables the
15   sharing of calendars between users on a CalDAV server.
16
17
18Table of Contents
19
20   1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
21   2.  Conventions Used in This Document  . . . . . . . . . . . . . .  3
22   3.  Overview . . . . . . . . . . . . . . . . . . . . . . . . . . .  4
23   4.  Notifications  . . . . . . . . . . . . . . . . . . . . . . . .  5
24     4.1.  Additional Principal Properties  . . . . . . . . . . . . .  5
25       4.1.1.  CS:notification-URL Property . . . . . . . . . . . . .  6
26     4.2.  Properties on Notification Resources . . . . . . . . . . .  6
27       4.2.1.  CS:notificationtype Property . . . . . . . . . . . . .  6
28   5.  Shared Calendaring . . . . . . . . . . . . . . . . . . . . . .  7
29     5.1.  Feature Discovery  . . . . . . . . . . . . . . . . . . . .  7
30     5.2.  Additional Properties for Calendars  . . . . . . . . . . .  7
31       5.2.1.  DAV:resourcetype Property  . . . . . . . . . . . . . .  7
32       5.2.2.  CS:invite Property . . . . . . . . . . . . . . . . . .  8
33       5.2.3.  CS:allowed-sharing-modes Property  . . . . . . . . . .  8
34       5.2.4.  CS:shared-url Property . . . . . . . . . . . . . . . .  9
35     5.3.  Sharer Actions on Shared Calendars . . . . . . . . . . . .  9
36       5.3.1.  Creating a Shared Calendar . . . . . . . . . . . . . .  9
37         5.3.1.1.  Example: Successful MKCALENDAR Request . . . . . . 10
38         5.3.1.2.  Example: Successful Extended MKCOL Request . . . . 10
39       5.3.2.  Sharing an Existing Calendar . . . . . . . . . . . . . 11
40         5.3.2.1.  Example: Successful PROPPATCH Request  . . . . . . 11
41       5.3.3.  Manipulating Sharees of a Shared Calendar  . . . . . . 12
42         5.3.3.1.  Example: Successful Sharee Add Request . . . . . . 13
43         5.3.3.2.  Example: Successful Multiple Sharee Change
44                   Request  . . . . . . . . . . . . . . . . . . . . . 14
45     5.4.  Sharee Actions on Shared Calendars . . . . . . . . . . . . 15
46       5.4.1.  Replying to a Sharing Invite . . . . . . . . . . . . . 15
47       5.4.2.  Removing a Shared Calendar . . . . . . . . . . . . . . 16
48     5.5.  General Considerations . . . . . . . . . . . . . . . . . . 16
49       5.5.1.  Access Levels  . . . . . . . . . . . . . . . . . . . . 16
50       5.5.2.  Allowing or Disallowing Sharing  . . . . . . . . . . . 16
51       5.5.3.  Per-user WebDAV Properties . . . . . . . . . . . . . . 17
52
53
54
55Daboo & York                                                    [Page 1]
56
57                      CalDAV Sharing and Publishing         October 2010
58
59
60       5.5.4.  Per-user Calendar Data . . . . . . . . . . . . . . . . 17
61       5.5.5.  Scheduling . . . . . . . . . . . . . . . . . . . . . . 18
62   6.  XML Element Definitions  . . . . . . . . . . . . . . . . . . . 19
63     6.1.  CS:shared-owner  . . . . . . . . . . . . . . . . . . . . . 19
64     6.2.  CS:shared  . . . . . . . . . . . . . . . . . . . . . . . . 20
65     6.3.  CS:can-be-shared . . . . . . . . . . . . . . . . . . . . . 20
66     6.4.  CS:can-be-published  . . . . . . . . . . . . . . . . . . . 21
67     6.5.  CS:user  . . . . . . . . . . . . . . . . . . . . . . . . . 21
68     6.6.  CS:invite-noresponse . . . . . . . . . . . . . . . . . . . 21
69     6.7.  CS:invite-deleted  . . . . . . . . . . . . . . . . . . . . 22
70     6.8.  CS:invite-accepted . . . . . . . . . . . . . . . . . . . . 22
71     6.9.  CS:invite-declined . . . . . . . . . . . . . . . . . . . . 22
72     6.10. CS:invite-invalid  . . . . . . . . . . . . . . . . . . . . 23
73     6.11. CS:access  . . . . . . . . . . . . . . . . . . . . . . . . 23
74     6.12. CS:read  . . . . . . . . . . . . . . . . . . . . . . . . . 24
75     6.13. CS:read-write  . . . . . . . . . . . . . . . . . . . . . . 24
76     6.14. CS:summary . . . . . . . . . . . . . . . . . . . . . . . . 24
77     6.15. CS:invite-notification . . . . . . . . . . . . . . . . . . 25
78     6.16. CS:uid . . . . . . . . . . . . . . . . . . . . . . . . . . 25
79     6.17. CS:hosturl . . . . . . . . . . . . . . . . . . . . . . . . 25
80     6.18. CS:organizer . . . . . . . . . . . . . . . . . . . . . . . 26
81     6.19. CS:common-name . . . . . . . . . . . . . . . . . . . . . . 26
82     6.20. CS:invite-reply  . . . . . . . . . . . . . . . . . . . . . 26
83     6.21. CS:in-reply-to . . . . . . . . . . . . . . . . . . . . . . 27
84     6.22. CS:notification  . . . . . . . . . . . . . . . . . . . . . 27
85     6.23. CS:dtstamp . . . . . . . . . . . . . . . . . . . . . . . . 28
86     6.24. CS:share . . . . . . . . . . . . . . . . . . . . . . . . . 28
87     6.25. CS:set . . . . . . . . . . . . . . . . . . . . . . . . . . 28
88     6.26. CS:remove  . . . . . . . . . . . . . . . . . . . . . . . . 29
89     6.27. CS:shared-as . . . . . . . . . . . . . . . . . . . . . . . 29
90   7.  Security Considerations  . . . . . . . . . . . . . . . . . . . 29
91   8.  IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 29
92   9.  Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 30
93   10. Normative References . . . . . . . . . . . . . . . . . . . . . 30
94   Appendix A.  Change History  . . . . . . . . . . . . . . . . . . . 30
95   Appendix B.  Change History  . . . . . . . . . . . . . . . . . . . 30
96   Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 31
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111Daboo & York                                                    [Page 2]
112
113                      CalDAV Sharing and Publishing         October 2010
114
115
1161.  Introduction
117
118   CalDAV [RFC4791] provides a way for calendar users to store calendar
119   data and exchange this data via scheduling operations.  Based on the
120   WebDAV [RFC4918] protocol, it also includes the ability to manage
121   access to calendar data via the WebDAV ACL [RFC3744] extension.
122
123   WebDAV ACL [RFC3744] provides a way to manage fine-grained access
124   controls on WebDAV resources.  Whilst this could be used directly to
125   manage sharing of calendars, experience has shown that client
126   developers are averse to using it due to its complexity.  Instead a
127   simpler process for sharing calendars is preferred.
128
129   This extension defines a way for individual calendar users to share
130   calendars with other users.  This is done via an "opt-in" process in
131   which a sharing invite is sent from the sharer to a sharee, allowing
132   the sharee to accept or decline.  If the sharee accepts the sharing
133   invite, the shared calendar is made available to them in their own
134   calendar home collection (i.e., alongside their own personal
135   calendars).  HTTP POST operations are used to manage the sharing
136   invitations and replies, and WebDAV properties are used to expose the
137   state of shared calendars.
138
139
1402.  Conventions Used in This Document
141
142   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
143   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
144   document are to be interpreted as described in [RFC2119].
145
146   When XML element types in the namespaces "DAV:" and
147   "urn:ietf:params:xml:ns:caldav" are referenced in this document
148   outside of the context of an XML fragment, the string "DAV:" and
149   "CALDAV:" will be prefixed to the element type names respectively.
150
151   The namespace "http://calendarserver.org/ns/" is used for XML
152   elements defined in this specification.  When XML element types in
153   that namespace are referenced in this document outside of the context
154   of an XML fragment, the string "CS:" will be prefixed to the element
155   type names.
156
157   Terms Used:
158
159   Sharer  A calendar user who is sharing a calendar with other calendar
160      users.
161
162
163
164
165
166
167Daboo & York                                                    [Page 3]
168
169                      CalDAV Sharing and Publishing         October 2010
170
171
172   Sharee  A calendar user to whom a calendar has been shared.
173
174   Sharing Invite  A message sent by a sharer to a sharee to indicate
175      the status of a shared calendar.
176
177   Sharing Reply  A message sent by a sharee to a sharer to indicate the
178      status of a shared calendar.
179
180
1813.  Overview
182
183   This section provides a basic overview of this protocol by way of a
184   simple use case of a sharer sharing a calendar with a single sharee.
185
186   To share a calendar with another user, the sharer's client executes
187   an HTTP POST request against the calendar collection resource for the
188   calendar to be shared.  The POST request body will contain details of
189   the calendar user to whom the calendar is to be shared as well as the
190   access right to be granted to them.  If the request succeeds, a
191   notification is sent to the sharee with details of the calendar being
192   shared to them.
193
194   The sharer's client will show the notification to the sharee and
195   present them with the choice to accept or decline the invitation to
196   the shared calendar.  If the sharee chooses to decline, then nothing
197   changes for that sharee.  If the sharee chooses to accept, then the
198   server automatically creates a new calendar collection resource in
199   the sharee's calendar home collection, and ensures that calendar
200   provides a mapping to the actual shared calendar of the sharer.  Thus
201   the shared calendar is available to the sharee as just another
202   calendar in their calendar home.  The server enforces the appropriare
203   access privileges for the sharee.
204
205   At any time, the sharer can inspect properties on the calendar
206   collection being shared, and determine the accept/decline status of
207   each sharee.  Additional sharees can be added and existing ones
208   removed.  The access privileges for existing sharees can also be
209   changed.
210
211   Once a sharee has a shared calendar set to appear in their calendar
212   home collection, they can remove it and decline the sharing invite by
213   simply having their client issue an HTTP DELETE request on the shared
214   calendar collection.  That does not delete any calendar data, but
215   rather simply removes the "link" to the sharer's calendar collection
216   and sets the sharee's inviate status to declined.
217
218
219
220
221
222
223Daboo & York                                                    [Page 4]
224
225                      CalDAV Sharing and Publishing         October 2010
226
227
2284.  Notifications
229
230   In order to facilitate the process of sharing invitations, this
231   specification defines a new generic notification mechanism for CalDAV
232   servers.  When this feature is available, a CS:notification-URL
233   (Section 4.1.1) property appears on principal resources for those
234   principals who are able to receive notifications.  That property
235   specifies a single DAV:href element whose content refers to a WebDAV
236   collection resource.  Notification "messages" are deposited into this
237   collection and can be retrieved by clients and acted on accordingly.
238
239   The notification collection referenced by the CS:notification-URL
240   (Section 4.1.1) property MUST have a DAV:resourcetype property with
241   DAV:collection and CS:notification (Section 6.22) child elements.
242
243   Notification "messages" are XML documents stored as resources in the
244   notification collection.  Each XML document contains a CS:
245   notification (Section 6.22) element as its root.  The root element
246   contains a CS:dtstamp (Section 6.23) element, and one additional
247   element which represents the type of notification being conveyed in
248   the message.  That child element will typically contain additional
249   content that describes the notification.
250
251   Each notification resource has a CS:notificationtype (Section 4.2.1)
252   property which contains as its single child element an empty element
253   that matches the child element of the notification resource XML
254   document root.  Any attributes on the child element in the XML
255   document are also present in the property child element.
256
257   Notifications are automatically generated by the server (perhaps in
258   response to a client action) with an appropriate resource stored in
259   the notifications collection of the user to whom the notification is
260   targeted.  Clients SHOULD monitor the notification collection looking
261   for new notification resources.  When doing so, clients SHOULD look
262   at the CS:notificationtype (Section 4.2.1) property to ensure that
263   the notification is of a type that the client can handle.  Once a
264   client has handled the notification in whatever way is appropriate it
265   SHOULD delete the notification resource.  Servers MAY delete
266   notification resources on their own if they determine that the
267   notifications are no longer relevant or valid.  Servers MAY coalesce
268   notifications as appropriate.
269
2704.1.  Additional Principal Properties
271
272   This section defines new properties for WebDAV principal resources as
273   defined in RFC3744 [RFC3744].  These properties are likely to be
274   protected but the server MAY allow them to be written by appropriate
275   users.
276
277
278
279Daboo & York                                                    [Page 5]
280
281                      CalDAV Sharing and Publishing         October 2010
282
283
2844.1.1.  CS:notification-URL Property
285
286   Name:  notification-URL
287
288   Namespace:  http://calendarserver.org/ns/
289
290   Purpose:  Identify the URL of the notification collection owned by
291      the associated principal resource.
292
293   Protected:  This property SHOULD be protected.
294
295   PROPFIND behavior:  This property SHOULD NOT be returned by a
296      PROPFIND allprop request (as defined in Section 14.2 of
297      [RFC4918]).
298
299   COPY/MOVE behavior:  This property value SHOULD be preserved in COPY
300      and MOVE operations.
301
302   Description:  This property is needed for a client to determine where
303      the notification collection of the current user is located so that
304      processing of notification messages can occur.  If not present,
305      then the associated calendar user is not enabled for notification
306      messages on the server.
307
308   Definition:
309
310   <!ELEMENT notification-URL (DAV:href)>
311
3124.2.  Properties on Notification Resources
313
314   The following new WebDAV properties are defined for notification
315   resources.
316
3174.2.1.  CS:notificationtype Property
318
319   Name:  notificationtype
320
321   Namespace:  http://calendarserver.org/ns/
322
323   Purpose:  Identify the type of notification of the corresponding
324      resource.
325
326   Protected:  This property MUST be protected.
327
328   PROPFIND behavior:  This property SHOULD NOT be returned by a
329      PROPFIND allprop request (as defined in Section 14.2 of
330      [RFC4918]).
331
332
333
334
335Daboo & York                                                    [Page 6]
336
337                      CalDAV Sharing and Publishing         October 2010
338
339
340   COPY/MOVE behavior:  This property value MUST be preserved in COPY
341      and MOVE operations.
342
343   Description:  This property allows a client, via a PROPFIND Depth:1
344      request, to quickly find notification messages that the client can
345      handle in a notification collection.  The single child element is
346      the notification resource root element's child defining the
347      notification itself.  This element MUST be empty, though any
348      attributes on the element in the notification resource MUST be
349      present in the property element.
350
351   Definition:
352
353   <!ELEMENT notificationtype (invite-notification | invite-reply)>
354   <!-- Child elements are empty but will have appropriate attributes.
355        Any valid notification message child element can appear.-->
356
357
3585.  Shared Calendaring
359
3605.1.  Feature Discovery
361
362   A server that supports the features described in this document MUST
363   include "calendarserver-sharing" as a field in the DAV response
364   header from an OPTIONS request on any resource that supports these
365   features.
366
3675.2.  Additional Properties for Calendars
368
369   The following new or modified WebDAV properties are defined for
370   calendar collections and used to view or manipulate shared calendar
371   features.
372
3735.2.1.  DAV:resourcetype Property
374
375   Calendar collections that are shared have addition elements listed in
376   their DAV:resourcetype property in addition to DAV:collection and
377   CALDAV:calendar.
378
379   o  CS:shared-owner (Section 6.1): used to indicate that the calendar
380      is owned by the current user and is being shared by them.
381
382   o  CS:shared (Section 6.2): used to indicate that the calendar is
383      owned by another user and is being shared to the current user.
384
385
386
387
388
389
390
391Daboo & York                                                    [Page 7]
392
393                      CalDAV Sharing and Publishing         October 2010
394
395
3965.2.2.  CS:invite Property
397
398   Name:  invite
399
400   Namespace:  http://calendarserver.org/ns/
401
402   Purpose:  Used to show to whom a calendar has been shared.
403
404   Protected:  This property MUST be protected.
405
406   PROPFIND behavior:  This property SHOULD NOT be returned by a
407      PROPFIND allprop request (as defined in Section 14.2 of
408      [RFC4918]).
409
410   COPY/MOVE behavior:  This property value MUST be preserved in COPY
411      and MOVE operations.
412
413   Description:  This WebDAV property is present on a calendar
414      collection resource that has been shared by the owner.  It MUST
415      NOT appear on the calendar collection resources of the sharees of
416      the calendar.  It provides a list of users to whom the calendar
417      has been shared, along with the "status" of the sharing invites
418      sent to each user.
419
420   Definition:
421
422   <!ELEMENT invite (user*)>
423
4245.2.3.  CS:allowed-sharing-modes Property
425
426   Name:  allowed-sharing-modes
427
428   Namespace:  http://calendarserver.org/ns/
429
430   Purpose:  Used to show which modes of sharing are supported on a
431      calendar collection.
432
433   Protected:  This property MUST be protected.
434
435   PROPFIND behavior:  This property SHOULD NOT be returned by a
436      PROPFIND allprop request (as defined in Section 14.2 of
437      [RFC4918]).
438
439   COPY/MOVE behavior:  This property value MUST be preserved in COPY
440      and MOVE operations.
441
442
443
444
445
446
447Daboo & York                                                    [Page 8]
448
449                      CalDAV Sharing and Publishing         October 2010
450
451
452   Description:  This WebDAV property is present on a calendar
453      collection resource that can been shared or published.  It
454      provides a list of options indicating what sharing modes are
455      allowed as per Section 5.5.2.
456
457   Definition:
458
459   <!ELEMENT allowed-sharing-modes
460             (can-be-shared?, can-be-published?)>
461
4625.2.4.  CS:shared-url Property
463
464   Name:  shared-url
465
466   Namespace:  http://calendarserver.org/ns/
467
468   Purpose:  Indicates the URL of the owner's copy of a shared calendar.
469
470   Protected:  This property MUST be protected.
471
472   PROPFIND behavior:  This property SHOULD NOT be returned by a
473      PROPFIND allprop request (as defined in Section 14.2 of
474      [RFC4918]).
475
476   COPY/MOVE behavior:  This property value MUST be preserved in COPY
477      and MOVE operations.
478
479   Description:  This WebDAV property is present on a shared calendar
480      collection resource that appears in a sharee's calendar home
481      collection.  Its content is a single DAV:href element whose value
482      is the URL of the sharer's calendar being shared.
483
484   Definition:
485
486   <!ELEMENT shared-url (DAV:href)>
487
4885.3.  Sharer Actions on Shared Calendars
489
4905.3.1.  Creating a Shared Calendar
491
492   To create a shared calendar, clients use the MKCALENDAR [RFC4791] or
493   extended MKCOL [RFC5689] requests, and include a DAV:resourcetype
494   property to be set upon creation.  That property MUST contain DAV:
495   collection, CALDAV:calendar and CS:shared-owner child elements to
496   enable sharing.
497
498
499
500
501
502
503Daboo & York                                                    [Page 9]
504
505                      CalDAV Sharing and Publishing         October 2010
506
507
5085.3.1.1.  Example: Successful MKCALENDAR Request
509
510   This example shows how the MKCALENDAR request is used to create a
511   shared calendar collection.  The response body is empty as the
512   request completed successfully.
513
514   >> Request <<
515
516   MKCALENDAR /calendars/users/cyrus/shared/ HTTP/1.1
517   Host: calendar.example.com
518   Content-Type: application/xml; charset="utf-8"
519   Content-Length: xxxx
520
521   <?xml version="1.0" encoding="utf-8" ?>
522   <C:mkcalendar xmlns:D="DAV:"
523                 xmlns:C="urn:ietf:params:xml:ns:caldav"
524                 xmlns:CS="http://calendarserver.org/ns/">
525     <D:set>
526       <D:prop>
527         <D:resourcetype>
528           <D:collection/>
529           <C:calendar/>
530           <CS:shared-owner/>
531         </D:resourcetype>
532       </D:prop>
533     </D:set>
534   </C:mkcalendar>
535
536   >> Response <<
537
538   HTTP/1.1 201 Created
539   Cache-Control: no-cache
540   Date: Sat, 11 Nov 2006 09:32:12 GMT
541
5425.3.1.2.  Example: Successful Extended MKCOL Request
543
544   This example shows how the extended MKCOL request is used to create a
545   shared calendar collection.  The response body is empty as the
546   request completed successfully.
547
548
549
550
551
552
553
554
555
556
557
558
559Daboo & York                                                   [Page 10]
560
561                      CalDAV Sharing and Publishing         October 2010
562
563
564   >> Request <<
565
566   MKCOL /calendars/users/cyrus/shared/ HTTP/1.1
567   Host: calendar.example.com
568   Content-Type: application/xml; charset="utf-8"
569   Content-Length: xxxx
570
571   <?xml version="1.0" encoding="utf-8" ?>
572   <D:mkcol xmlns:D="DAV:"
573                 xmlns:C="urn:ietf:params:xml:ns:caldav"
574                 xmlns:CS="http://calendarserver.org/ns/">
575     <D:set>
576       <D:prop>
577         <D:resourcetype>
578           <D:collection/>
579           <C:calendar/>
580           <CS:shared-owner/>
581         </D:resourcetype>
582       </D:prop>
583     </D:set>
584   </D:mkcol>
585
586   >> Response <<
587
588   HTTP/1.1 201 Created
589   Cache-Control: no-cache
590   Date: Sat, 11 Nov 2006 09:32:12 GMT
591
5925.3.2.  Sharing an Existing Calendar
593
594   Sharing an existing calendar can be accomplished in two ways.  One
595   option is to use a PROPPATCH request to set the DAV:resourcetype
596   property to include CS:shared-owner as a child element.  Another
597   option is to add sharee's directly to the calendar collection (as
598   described in Section 5.3.3) - that action MUST upgrade a non-shared
599   calendar to a shared calendar when it completes successfully,
600   assuming that such an upgrade is allowed as per Section 5.5.2.
601
6025.3.2.1.  Example: Successful PROPPATCH Request
603
604   This example shows how the PROPPATCH request is used to upgrade to a
605   shared calendar collection.
606
607
608
609
610
611
612
613
614
615Daboo & York                                                   [Page 11]
616
617                      CalDAV Sharing and Publishing         October 2010
618
619
620   >> Request <<
621
622   PROPPATCH /calendars/users/cyrus/shared/ HTTP/1.1
623   Host: calendar.example.com
624   Content-Type: application/xml; charset="utf-8"
625   Content-Length: xxxx
626
627   <?xml version="1.0" encoding="utf-8" ?>
628   <D:propertyupdate xmlns:D="DAV:"
629                 xmlns:C="urn:ietf:params:xml:ns:caldav"
630                 xmlns:CS="http://calendarserver.org/ns/">
631     <D:set>
632       <D:prop>
633         <D:resourcetype>
634           <D:collection/>
635           <C:calendar/>
636           <CS:shared-owner/>
637         </D:resourcetype>
638       </D:prop>
639     </D:set>
640   </D:propertyupdate>
641
642   >> Response <<
643
644   HTTP/1.1 207 Multi-Status
645   Date: Sat, 11 Nov 2006 09:32:12 GMT
646   Content-Type: application/xml; charset="utf-8"
647   Content-Length: xxxx
648
649   <?xml version="1.0" encoding="utf-8" ?>
650   <D:multistatus xmlns:D="DAV:">
651     <D:response>
652       <D:href>/calendars/users/cyrus/shared/</D:href>
653       <D:propstat>
654         <D:prop>
655           <D:resourcetype/>
656         </D:prop>
657         <D:status>HTTP/1.1 200 OK</D:status>
658       </D:propstat>
659     </D:response>
660   </D:multistatus>
661
6625.3.3.  Manipulating Sharees of a Shared Calendar
663
664   The sharer of a shared calendar is able to manipulate the sharee list
665   by issuing a POST request targeted at the shared calendar collection
666   resource.  The POST request MUST contain an XML document as its body
667   with the root element being CS:share (Section 6.24).
668
669
670
671Daboo & York                                                   [Page 12]
672
673                      CalDAV Sharing and Publishing         October 2010
674
675
676   The CS:share (Section 6.24) element in the POST requests MUST contain
677   one or more CS:set (Section 6.25) or CS:remove (Section 6.26)
678   elements.  For each CS:set (Section 6.25) element, the server MUST
679   add the specified sharee access to the shared calendar.  For each CS:
680   remove (Section 6.26) element the server MUST remove the specified
681   sharee access from the shared calendar.  In each case the server MUST
682   send a notification message to any sharees whose status is changed
683   (added, modified or removed), indicating to them a change in status
684   for the shared calendar.  The server SHOULD NOT send notification
685   messages to sharees whose status is unchanged.
686
687   Sharee's are identified via a DAV:href element whose value is either
688   a principal-URL for a sharee hosted on the same server, a calendar
689   user address or email address.  In the case of the later two, the
690   sharee might not be a user on the same server - though in that case
691   how invitations are sent or access enabled is out of scope for this
692   specification.  A server MAY change the sharee's "address" to any
693   suitable alternative that it might prefer when returning the list of
694   sharees via the CS:invite property (Section 5.2.2).
695
696   The client MAY include a CS:common-name (Section 6.19) element in the
697   CS:set (Section 6.25) element.  When provided, the value represents
698   the common name for the sharee, and is returned in the list of
699   sharees via the CS:invite property (Section 5.2.2).  The server MAY
700   change this to a suitable alternative when it is able to match the
701   sharee to a known user.  If absent from the client request, the
702   server SHOULD add a CS:common-name when it is able to match the
703   sharee with a known user, and a common name for that user can be
704   determined.
705
706   When the sharee list on a shared calendar is changed, the server MUST
707   send notifications to each sharee to update them on their current
708   sharing status.  This is accomplished by sending a CS:invite-
709   notification (Section 6.15) notification to each sharee.
710
7115.3.3.1.  Example: Successful Sharee Add Request
712
713   This example shows how to add a single sharee (with calendar user
714   address "mailto:eric@example.com") to a shared calendar with CS:read-
715   write access.
716
717
718
719
720
721
722
723
724
725
726
727Daboo & York                                                   [Page 13]
728
729                      CalDAV Sharing and Publishing         October 2010
730
731
732   >> Request <<
733
734   POST /calendars/users/cyrus/shared/ HTTP/1.1
735   Host: calendar.example.com
736   Content-Type: application/xml; charset="utf-8"
737   Content-Length: xxxx
738
739   <?xml version="1.0" encoding="utf-8" ?>
740   <CS:share xmlns:D="DAV:"
741                 xmlns:CS="http://calendarserver.org/ns/">
742     <CS:set>
743       <D:href>mailto:eric@example.com</D:href>
744       <CS:common-name>Eric York</CS:common-name>
745       <CS:summary>Shared workspace</CS:summary>
746       <CS:read-write />
747     </CS:set>
748   </CS:share>
749
750   >> Response <<
751
752   HTTP/1.1 200 OK
753   Cache-Control: no-cache
754   Date: Sat, 11 Nov 2006 09:32:12 GMT
755
7565.3.3.2.  Example: Successful Multiple Sharee Change Request
757
758   This example shows how multiple sharee's can be manipulated in a
759   single request.  The sharee with calendar user address
760   "mailto:eric@example.com" has their access downgraded to CS:read,
761   whilst another sharee is removed from the access list entirely.
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783Daboo & York                                                   [Page 14]
784
785                      CalDAV Sharing and Publishing         October 2010
786
787
788   >> Request <<
789
790   POST /calendars/users/cyrus/shared/ HTTP/1.1
791   Host: calendar.example.com
792   Content-Type: application/xml; charset="utf-8"
793   Content-Length: xxxx
794
795   <?xml version="1.0" encoding="utf-8" ?>
796   <CS:share xmlns:D="DAV:"
797                 xmlns:CS="http://calendarserver.org/ns/">
798     <CS:set>
799       <D:href>mailto:eric@example.com</D:href>
800       <CS:summary>Shared workspace</CS:summary>
801       <CS:read-write />
802     </CS:set>
803     <CS:remove>
804       <D:href>mailto:wilfredo@example.com</D:href>
805     </CS:remove>
806   </CS:share>
807
808   >> Response <<
809
810   HTTP/1.1 200 OK
811   Cache-Control: no-cache
812   Date: Sat, 11 Nov 2006 09:32:12 GMT
813
8145.4.  Sharee Actions on Shared Calendars
815
8165.4.1.  Replying to a Sharing Invite
817
818   When a sharee is invited to a shared calendar they can accept or
819   decline the invite by issuing a POST request to the sharee's calendar
820   home collection resource.  The POST request MUST contain an XML
821   document as its body with the root element being CS:invite-reply
822   (Section 6.20).
823
824   The CS:invite-reply (Section 6.20) element in the POST request
825   specifies the sharee who is replying in the DAV:href element, the
826   accept or decline action via the CS:invite-accepted or CS:invite-
827   declined elements, the URL of the shared calendar in the CS:hosturl
828   element, the unique identifier of the invite to which it is a reply
829   in the CS:in-reply-to element, and an optional CS:summary element.
830
831   The response to a POST request that accepts a shared calendar invite
832   MUST be an XML document containing CS:shared-as (Section 6.27) as its
833   root element.  That root element contains a single DAV:href element
834   whose content is the URI of the shared calendar in the sharee's
835   calendar home created by the invite acceptance.
836
837
838
839Daboo & York                                                   [Page 15]
840
841                      CalDAV Sharing and Publishing         October 2010
842
843
844   When the sharee replies to an invite, the server SHOULD send a
845   notification to the sharer to update them on the change in the sharee
846   state.  This is accomplished by sending a CS:invite-reply
847   (Section 6.20) notification to the sharer.
848
8495.4.2.  Removing a Shared Calendar
850
851   To remove a shared calendar from a sharee's calendar home collection
852   a DELETE request is targeted at the shared calendar URI.  When such a
853   request is received the server MUST remove the shared calendar from
854   the sharee's calendar home and automatically update the sharee's
855   status in the sharer's calendar's CS:invite property.
856
8575.5.  General Considerations
858
8595.5.1.  Access Levels
860
861   Two levels of access ca be granted by a sharer to any sharee.  These
862   are governed by the CS:access element used in the CS:invite/CS:user
863   element that specifies a shared user invite.  CS:access contains a
864   single empty element that defines the type of access granted:
865
866   CS:read  When present this indicates that sharees can read calendar
867      data but cannot change it.
868
869   CS:read-write  When present this indicates that sharees can read and
870      write calendar data.
871
8725.5.2.  Allowing or Disallowing Sharing
873
874   Servers MAY support calendar sharing on a per-calendar basis - e.g.,
875   they could treat some calendars as always private (cannot be shared)
876   or always public (always shared).  As a result clients need a way to
877   determine which calendar could be shared so they can enable or
878   disable sharing options on a per-calendar basis.
879
880   This specification adds a CS:allowed-sharing-modes (Section 5.2.3)
881   WebDAV property which servers can return on calendar collection
882   resources.  This property contains XML elements that describe which
883   sharing or publishing capabilities can be supported by the
884   corresponding calendar collection:
885
886      CS:can-be-shared (Section 6.3): when present indicates that the
887      calendar collection can be shared.  When not present, the calendar
888      collection cannot be shared.
889
890      CS:can-be-published (Section 6.4): when present indicates that the
891      calendar collection can be published.  When not present, the
892
893
894
895Daboo & York                                                   [Page 16]
896
897                      CalDAV Sharing and Publishing         October 2010
898
899
900      calendar collection cannot be published.
901
902   When not present on a calendar collection, sharing or publishing of
903   that calendar is not allowed.  Clients SHOULD NOT attempt to use
904   requests to enable sharing or publishing targeted at those calendar
905   collections.
906
9075.5.3.  Per-user WebDAV Properties
908
909   Servers MUST support "per-user" WebDAV properties on shared calendar
910   collections and MAY support them on calendar object resources within
911   shared calendar collections.  A "per-user" WebDAV property is one
912   whose value can be set and retrieved independently by each user with
913   appropriate access rights. e.g., user "A" changes the DAV:displayname
914   property on a shared calendar in their calendar home to "My
915   calendar", and user "B" changes the same property to "Shared" on the
916   same shared calendar in their calendar home.  When each user
917   retrieves the property value they will see their own last stored
918   value and not the value of the other user.
919
920   For shared calendars, the server MUST allow all users to write "per-
921   user" WebDAV properties on the shared calendar collection and MAY
922   allow property writes on calendar object resources within the shared
923   calendar collection.  This is required even in the case where the
924   sharee has been granted read access only (i.e., the ability to change
925   calendar data is disallowed).  This requirement ensures that sharees
926   can always change "personal" properties such as calendar colors and
927   display names.
928
929   Servers MUST treat the following properties as "per-user":
930
931      DAV:displayname
932
933      CALDAV:calendar-description
934
935      CALDAV:schedule-calendar-transp
936
937      ICAL:calendar-color
938
939   Servers MAY treat any dead property as per-user.
940
941   Servers MUST NOT treat live properties as per-user.
942
9435.5.4.  Per-user Calendar Data
944
945   Servers MUST support "per-user" calendar data in calendar object
946   resources stored in shared calendars.  This allows each sharee and
947   the sharer to store their own alarms and free busy transparency
948
949
950
951Daboo & York                                                   [Page 17]
952
953                      CalDAV Sharing and Publishing         October 2010
954
955
956   status without "interfering" with other users who also have access to
957   the same calendar object resources.
958
959   For calendaring object resources in shared calendar collections, the
960   server MUST treat the following iCalendar data objects as per-user:
961
962      TRANSP property
963
964      VALARM component
965
966   Servers MAY treat any non-standard X- iCalendar properties as per-
967   user.
968
969   When handling per-user data in recurring components, servers SHOULD
970   eliminate overridden instances when returning iCalendar data to
971   clients in the case where there are no differences between the
972   overridden component and the instance that could be derived from the
973   "master" recurrence component.  For example, consider a daily
974   recurring event, Monday through Friday, initially defined without any
975   overridden instances, that is in a shared calendar.  If user "A"
976   overrides the Tuesday instance and adds their own "VALARM" component
977   only, then when user "A" later retrieves the data again they would
978   see that overridden instance, but when user "B" does so, they would
979   not.  This ensures that each user sees the most "compact"
980   representation of the calendar data.
981
9825.5.5.  Scheduling
983
984   CalDAV Scheduling [I-D.desruisseaux-caldav-sched] defines how a
985   CalDAV server carries out scheduling operations when calendar object
986   resources are created, modified or deleted and include "ORGANIZER"
987   and "ATTENDEE" iCalendar properties.
988
989   When calendar object resources are created, modified or deleted in
990   shared calendars by sharees, the following restrictions apply:
991
992   1.  The "ORGANIZER" iCalendar property value in the iCalendar data
993       MUST match a calendar user address of the sharer (owner) of the
994       shared calendar.  The DAV:owner WebDAV property MUST be present
995       on a shared calendar and MUST provide a reference to a principal-
996       URL of the sharer (owner) of the shared calendar.  Clients can
997       use this value to determine what the allowed "ORGANIZER"
998       iCalendar property values are.  The server MUST reject any
999       attempt by a sharee to create an iCalendar component with an
1000       "ORGANIZER" property value other than the sharer (owner) of the
1001       shared calendar.
1002
1003
1004
1005
1006
1007Daboo & York                                                   [Page 18]
1008
1009                      CalDAV Sharing and Publishing         October 2010
1010
1011
1012   2.  The server MUST reject any attempt by a sharee to MOVE a calendar
1013       object resource in a shared calendar to some other collection.
1014
1015   3.  When a sharee is listed as an Attendee in a calendar object
1016       resource in a shared calendar, and write access is granted, the
1017       sharee is allowed to change not only iCalendar data related to
1018       the Organizer, but also data related to the Attendee. i.e., a
1019       sharee can change their own participation status on the
1020       "ATTENDEE" iCalendar property referring to them.  Additionally,
1021       if the sharee is not listed as an Attendee, and write access is
1022       granted, the sharee can add themselves as an Attendee.
1023
1024   4.  The default calendar collection defined in Section 6.3 of
1025       [I-D.desruisseaux-caldav-sched] MUST NOT be a calendar shared to
1026       the corresponding calendar user.
1027
1028   Following are additional considerations for scheduling with shared
1029   calendars:
1030
1031   1.  A scheduled iCalendar component could appear in more than one
1032       calendar collection within a sharee's calendar home if the sharee
1033       is an Attendee and the Organizer or other Attendees have shared a
1034       calendar with the sharee that includes their copies of the
1035       iCalendar component.  It is important to note that the scheduled
1036       component in the shared calendar could have different access
1037       rights than the one in the sharee's owned calendar.
1038
1039   2.  A scheduled iCalendar component appearing in a sharee's shared
1040       calendar could include the sharee as an Attendee.  For recurring
1041       events, it is possible for the sharee to only be listed as an
1042       Attendee in some instances, as opposed to all.  Clients will need
1043       to be aware of this when allowing sharee's to set their own
1044       participation status.
1045
1046   In addition, when a shared calendar is first accepted by a sharee,
1047   the server SHOULD set the CALDAV:schedule-calendar-transp property to
1048   the value CALDAV:transparent to ensure newly accepted shared
1049   calendars do not contribute to the sharee's freebusy time until the
1050   sharee explicitly requests it.
1051
1052
10536.  XML Element Definitions
1054
10556.1.  CS:shared-owner
1056
1057
1058
1059
1060
1061
1062
1063Daboo & York                                                   [Page 19]
1064
1065                      CalDAV Sharing and Publishing         October 2010
1066
1067
1068   Name:  shared-owner
1069
1070   Namespace:  http://calendarserver.org/ns/
1071
1072   Purpose:  Used to indicate that a calendar is being shared by the
1073      owner.
1074
1075   Description:  This property appears in the DAV:resourcetype property
1076      on the calendar collection resource shared by a sharer.  See
1077      Section 5.2.
1078
1079   Definition:
1080
1081   <!ELEMENT shared-owner EMPTY>
1082
10836.2.  CS:shared
1084
1085   Name:  shared
1086
1087   Namespace:  http://calendarserver.org/ns/
1088
1089   Purpose:  Used to indicate that a calendar is being shared to a
1090      sharee.
1091
1092   Description:  This property appears in the DAV:resourcetype property
1093      on a calendar collection resource that is shared to a sharee and
1094      appears in the sharee's calendar home collection.  See
1095      Section 5.2.
1096
1097   Definition:
1098
1099   <!ELEMENT shared EMPTY>
1100
11016.3.  CS:can-be-shared
1102
1103   Name:  can-be-shared
1104
1105   Namespace:  http://calendarserver.org/ns/
1106
1107   Purpose:  Used to indicate that a calendar can be shared.
1108
1109   Description:  This element indicates that a calendar can be shared
1110      with other users.  See Section 5.2.3
1111
1112   Definition:
1113
1114   <!ELEMENT can-be-shared EMPTY>
1115
1116
1117
1118
1119Daboo & York                                                   [Page 20]
1120
1121                      CalDAV Sharing and Publishing         October 2010
1122
1123
11246.4.  CS:can-be-published
1125
1126   Name:  can-be-published
1127
1128   Namespace:  http://calendarserver.org/ns/
1129
1130   Purpose:  Used to indicate that a calendar can be published.
1131
1132   Description:  This element indicates that a calendar can be published
1133      to anyone.  See Section 5.2.3
1134
1135   Definition:
1136
1137   <!ELEMENT can-be-published EMPTY>
1138
11396.5.  CS:user
1140
1141   Name:  user
1142
1143   Namespace:  http://calendarserver.org/ns/
1144
1145   Purpose:  Used to show status of sharing invites sent to sharees.
1146
1147   Description:  This element provides the "status" of a sharing invite
1148      sent to a particular user.  See Section 5.2.2.
1149
1150   Definition:
1151
1152   <!ELEMENT user (DAV:href, common-name?, (invite-noresponse |
1153                   invite-accepted | invite-declined | invite-invalid),
1154                   access, summary?)>
1155
11566.6.  CS:invite-noresponse
1157
1158   Name:  invite-noresponse
1159
1160   Namespace:  http://calendarserver.org/ns/
1161
1162   Purpose:  Sharing invite status.
1163
1164   Description:  When used in a CS:user (Section 6.5) element, this
1165      element is used to indicate that the sharee has never replied to
1166      the corresponding sharing invite.  When used in a CS:invite-
1167      notification (Section 6.15) element, this element is used to
1168      indicate to the sharee that a sharing reply is needed.
1169
1170
1171
1172
1173
1174
1175Daboo & York                                                   [Page 21]
1176
1177                      CalDAV Sharing and Publishing         October 2010
1178
1179
1180   Definition:
1181
1182   <!ELEMENT invite-noresponse EMPTY>
1183
11846.7.  CS:invite-deleted
1185
1186   Name:  invite-deleted
1187
1188   Namespace:  http://calendarserver.org/ns/
1189
1190   Purpose:  Sharing invite status.
1191
1192   Description:  When used in a CS:invite-notification (Section 6.15)
1193      element, this element is used to indicate to the sharee that a
1194      shared calendar has been unshared by the sharer.
1195
1196   Definition:
1197
1198   <!ELEMENT invite-deleted EMPTY>
1199
12006.8.  CS:invite-accepted
1201
1202   Name:  invite-accepted
1203
1204   Namespace:  http://calendarserver.org/ns/
1205
1206   Purpose:  Sharing invite status.
1207
1208   Description:  When used in a CS:user (Section 6.5) element, this
1209      element is used to indicate that the sharee has accepted the
1210      corresponding sharing invite.  When used in a CS:invite-
1211      notification (Section 6.15) element, this element is used to
1212      indicate to the sharee that the sharing invite is an update for
1213      one they previously accepted.
1214
1215   Definition:
1216
1217   <!ELEMENT invite-accepted EMPTY>
1218
12196.9.  CS:invite-declined
1220
1221   Name:  invite-declined
1222
1223   Namespace:  http://calendarserver.org/ns/
1224
1225
1226
1227
1228
1229
1230
1231Daboo & York                                                   [Page 22]
1232
1233                      CalDAV Sharing and Publishing         October 2010
1234
1235
1236   Purpose:  Sharing invite status.
1237
1238   Description:  When used in a CS:user (Section 6.5) element, this
1239      element is used to indicate that the sharee has declined the
1240      corresponding sharing invite.  When used in a CS:invite-
1241      notification (Section 6.15) element, this element is used to
1242      indicate to the sharee that the sharing invite is an update for
1243      one they previously declined.
1244
1245   Definition:
1246
1247   <!ELEMENT invite-declined EMPTY>
1248
12496.10.  CS:invite-invalid
1250
1251   Name:  invite-invalid
1252
1253   Namespace:  http://calendarserver.org/ns/
1254
1255   Purpose:  Sharing invite status.
1256
1257   Description:  When used in a CS:user (Section 6.5) element, this
1258      element is used to indicate that the corresponding sharee is not a
1259      valid calendar user known to the server.
1260
1261   Definition:
1262
1263   <!ELEMENT invite-invalid EMPTY>
1264
12656.11.  CS:access
1266
1267   Name:  access
1268
1269   Namespace:  http://calendarserver.org/ns/
1270
1271   Purpose:  Shared calendar access level.
1272
1273   Description:  When used in a CS:user (Section 6.5) element, this
1274      element is used to indicate the sharing access level granted to
1275      the corresponding sharee.
1276
1277   Definition:
1278
1279   <!ELEMENT invite-invalid (read | read-write)>
1280
1281
1282
1283
1284
1285
1286
1287Daboo & York                                                   [Page 23]
1288
1289                      CalDAV Sharing and Publishing         October 2010
1290
1291
12926.12.  CS:read
1293
1294   Name:  read
1295
1296   Namespace:  http://calendarserver.org/ns/
1297
1298   Purpose:  Shared calendar access level privilege.
1299
1300   Description:  Indicates that the access level granted only allows
1301      sharees to read data in the shared calendar (though they can write
1302      per-user data (Section 5.5.4)).
1303
1304   Definition:
1305
1306   <!ELEMENT read EMPTY>
1307
13086.13.  CS:read-write
1309
1310   Name:  read-write
1311
1312   Namespace:  http://calendarserver.org/ns/
1313
1314   Purpose:  Shared calendar access level privilege.
1315
1316   Description:  Indicates that the access level granted allows sharees
1317      to read and write all data in the shared calendar, with the
1318      exception of components that would trigger scheduling.
1319
1320   Definition:
1321
1322   <!ELEMENT read-write EMPTY>
1323
13246.14.  CS:summary
1325
1326   Name:  summary
1327
1328   Namespace:  http://calendarserver.org/ns/
1329
1330   Purpose:  Summary or title of shared calendar.
1331
1332   Description:  A brief description of a shared calendar.  This can be
1333      used by sharers to communicate the nature of a shared calendar to
1334      sharees, as well as used by sharees to indicate back to the sharer
1335      how each sharee is refering to the shared calendar.
1336
1337
1338
1339
1340
1341
1342
1343Daboo & York                                                   [Page 24]
1344
1345                      CalDAV Sharing and Publishing         October 2010
1346
1347
1348   Definition:
1349
1350   <!ELEMENT summary (#PCDATA)>
1351
13526.15.  CS:invite-notification
1353
1354   Name:  invite-notification
1355
1356   Namespace:  http://calendarserver.org/ns/
1357
1358   Purpose:  A notification used as a shared calendar invite.
1359
1360   Description:  Defines a notification message sent automatically by
1361      the server when a sharer adds, changes or removes a sharee from a
1362      shared calendar.  The DAV:href element specifies the calendar user
1363      address of the sharee to whom the message was sent.
1364
1365   Definition:
1366
1367   <!ELEMENT invite-notification (uid, DAV:href,
1368                                  (invite-noresponse | invite-deleted |
1369                                   invite-accepted | invite-declined),
1370                                  access, hosturl, organizer, summary?>
1371
13726.16.  CS:uid
1373
1374   Name:  uid
1375
1376   Namespace:  http://calendarserver.org/ns/
1377
1378   Purpose:  Unique identifier.
1379
1380   Description:  A unique identifier for an invitation to a shared
1381      calendar.
1382
1383   Definition:
1384
1385   <!ELEMENT uid (#PCDATA)>
1386
13876.17.  CS:hosturl
1388
1389   Name:  hosturl
1390
1391   Namespace:  http://calendarserver.org/ns/
1392
1393
1394
1395
1396
1397
1398
1399Daboo & York                                                   [Page 25]
1400
1401                      CalDAV Sharing and Publishing         October 2010
1402
1403
1404   Purpose:  Identifies the source URL of a shared calendar.
1405
1406   Description:  Contains a single DAV:href element that refers to the
1407      source of a shared calendar - i.e., the URL of the calendar shared
1408      by the sharer.
1409
1410   Definition:
1411
1412   <!ELEMENT hosturl (DAV:href)>
1413
14146.18.  CS:organizer
1415
1416   Name:  organizer
1417
1418   Namespace:  http://calendarserver.org/ns/
1419
1420   Purpose:  Identifies the sharer of a shared calendar.
1421
1422   Description:  Contains a single DAV:href element that identifies the
1423      calendar user address of the sharer of a shared calendar, and an
1424      optional CS:common-name element that matches that user.
1425
1426   Definition:
1427
1428   <!ELEMENT organizer (DAV:href, CS:common-name?)>
1429
14306.19.  CS:common-name
1431
1432   Name:  common-name
1433
1434   Namespace:  http://calendarserver.org/ns/
1435
1436   Purpose:  The common name of a sharer or sharee.
1437
1438   Description:  The common name is optionally provided by a client when
1439      adding a sharee and optionally included (or modified) by the
1440      server when returning results for sharers or sharees and in
1441      notifications.
1442
1443   Definition:
1444
1445   <!ELEMENT common-name (#PCDATA)>
1446
14476.20.  CS:invite-reply
1448
1449
1450
1451
1452
1453
1454
1455Daboo & York                                                   [Page 26]
1456
1457                      CalDAV Sharing and Publishing         October 2010
1458
1459
1460   Name:  invite-reply
1461
1462   Namespace:  http://calendarserver.org/ns/
1463
1464   Purpose:  A notification used as a reply to a shared calendar invite.
1465
1466   Description:  Defines a notification message sent automatically by
1467      the server when a sharee replies to a shared calendar invite.  The
1468      DAV:href element specifies the calendar user address of the sharee
1469      to whom the original invite message was sent.
1470
1471   Definition:
1472
1473   <!ELEMENT invite-reply (DAV:href,
1474                           (invite-accepted | invite-declined),
1475                           hosturl, in-reply-to, summary?>
1476
14776.21.  CS:in-reply-to
1478
1479   Name:  in-reply-to
1480
1481   Namespace:  http://calendarserver.org/ns/
1482
1483   Purpose:  Unique identifier.
1484
1485   Description:  Specifies the unique identifier of the inviate message
1486      that this notification message is a reply to.
1487
1488   Definition:
1489
1490   <!ELEMENT in-reply-to (#PCDATA)>
1491
14926.22.  CS:notification
1493
1494   Name:  notification
1495
1496   Namespace:  http://calendarserver.org/ns/
1497
1498   Purpose:  Notification message root element.
1499
1500   Description:  The root element used in notification resources.
1501
1502   Definition:
1503
1504   <!ELEMENT notification (CS:dtstamp,
1505                           (invite-notification | invite-reply)>
1506   <!-- Any notification type element can appear after CS:dtstamp,
1507        this specification defines only the two listed above -->
1508
1509
1510
1511Daboo & York                                                   [Page 27]
1512
1513                      CalDAV Sharing and Publishing         October 2010
1514
1515
15166.23.  CS:dtstamp
1517
1518   Name:  dtstamp
1519
1520   Namespace:  http://calendarserver.org/ns/
1521
1522   Purpose:  Date-time stamp.
1523
1524   Description:  Contains the date-time stamp corresponding to the
1525      creation of a notification message.
1526
1527   Definition:
1528
1529   <!ELEMENT dtstamp (#PCDATA)>
1530
15316.24.  CS:share
1532
1533   Name:  share
1534
1535   Namespace:  http://calendarserver.org/ns/
1536
1537   Purpose:  Describes changes to sharees.
1538
1539   Description:  The root element used in POST requests on calendars by
1540      sharers to manipulate the sharee list of a shared calendar.
1541
1542   Definition:
1543
1544   <!ELEMENT share (set | remove)*>
1545
15466.25.  CS:set
1547
1548   Name:  set
1549
1550   Namespace:  http://calendarserver.org/ns/
1551
1552   Purpose:  Sets access for a sharee.
1553
1554   Description:  Used to add or modify sharee access to a shared
1555      calendar.  The specified access to the shared calendar is given to
1556      the sharee.
1557
1558   Definition:
1559
1560   <!ELEMENT set (DAV:href, common-name?, summary?,
1561                  (read | read-write)>
1562
1563
1564
1565
1566
1567Daboo & York                                                   [Page 28]
1568
1569                      CalDAV Sharing and Publishing         October 2010
1570
1571
15726.26.  CS:remove
1573
1574   Name:  remove
1575
1576   Namespace:  http://calendarserver.org/ns/
1577
1578   Purpose:  Removes access for a sharee.
1579
1580   Description:  Used to remove sharee access to a shared calendar.  All
1581      access to the shared calendar is removed for the sharee.
1582
1583   Definition:
1584
1585   <!ELEMENT remove (DAV:href)>
1586
15876.27.  CS:shared-as
1588
1589   Name:  shared-as
1590
1591   Namespace:  http://calendarserver.org/ns/
1592
1593   Purpose:  Identifies a shared calendar.
1594
1595   Description:  Returned by the server for a POST request by a sharee
1596      accepting a shared calendar invite.  The DAV:href element
1597      specifies the URI of the calendar created by the acceptance.
1598
1599   Definition:
1600
1601   <!ELEMENT shared-as (DAV:href)>
1602
1603
16047.  Security Considerations
1605
1606   Per-user WebDAV properties and iCalendar data MUST only be accessible
1607   by the user that created them.
1608
1609   Alarms set by the sharer SHOULD NOT be propagated to sharees by
1610   default.  Clients SHOULD NOT automatically enable triggering of
1611   alarms on shared calendars that have just been accepted without
1612   confirmation by the user.
1613
1614   TBD
1615
1616
16178.  IANA Considerations
1618
1619   This document does not require any actions on the part of IANA.
1620
1621
1622
1623Daboo & York                                                   [Page 29]
1624
1625                      CalDAV Sharing and Publishing         October 2010
1626
1627
16289.  Acknowledgments
1629
1630   This specification is the result of discussions between the Apple
1631   calendar server and client teams.
1632
1633
163410.  Normative References
1635
1636   [I-D.desruisseaux-caldav-sched]
1637              Daboo, C. and B. Desruisseaux, "CalDAV Scheduling
1638              Extensions to WebDAV", draft-desruisseaux-caldav-sched-08
1639              (work in progress), August 2009.
1640
1641   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
1642              Requirement Levels", BCP 14, RFC 2119, March 1997.
1643
1644   [RFC3744]  Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
1645              Distributed Authoring and Versioning (WebDAV)
1646              Access Control Protocol", RFC 3744, May 2004.
1647
1648   [RFC4791]  Daboo, C., Desruisseaux, B., and L. Dusseault,
1649              "Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
1650              March 2007.
1651
1652   [RFC4918]  Dusseault, L., "HTTP Extensions for Web Distributed
1653              Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
1654
1655   [RFC5689]  Daboo, C., "Extended MKCOL for Web Distributed Authoring
1656              and Versioning (WebDAV)", RFC 5689, September 2009.
1657
1658
1659Appendix A.  Change History
1660
1661   Changes in -02:
1662
1663   1.  Removed read-write-shared access mode - now a server that does
1664       not support shared scheduling should advertise that via a DAV
1665       header
1666
1667
1668Appendix B.  Change History
1669
1670   Changes in -01:
1671
1672   1.  Added CS:shared-url property
1673
1674   2.  Clarified that notifications are only required to be sent when
1675       sharee status is changed
1676
1677
1678
1679Daboo & York                                                   [Page 30]
1680
1681                      CalDAV Sharing and Publishing         October 2010
1682
1683
1684Authors' Addresses
1685
1686   Cyrus Daboo
1687   Apple Inc.
1688   1 Infinite Loop
1689   Cupertino, CA  95014
1690   USA
1691
1692   Email: cyrus@daboo.name
1693   URI:   http://www.apple.com/
1694
1695
1696   Eric York
1697   Apple Inc.
1698   1 Infinite Loop
1699   Cupertino, CA  95014
1700   USA
1701
1702   Email:
1703   URI:   http://www.apple.com/
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735Daboo & York                                                   [Page 31]
1736
+
+
+ Note: See TracBrowser + for help on using the browser. +
+
+
+
+ + + + + +
+
+
+
+ +
+
+
+
+ + +
+ + \ No newline at end of file diff --git a/dav/SabreDAV/docs/rfc5785.txt b/dav/SabreDAV/docs/rfc5785.txt new file mode 100644 index 000000000..c28ccf6bf --- /dev/null +++ b/dav/SabreDAV/docs/rfc5785.txt @@ -0,0 +1,451 @@ + + + + + + +Internet Engineering Task Force (IETF) M. Nottingham +Request for Comments: 5785 E. Hammer-Lahav +Updates: 2616, 2818 April 2010 +Category: Standards Track +ISSN: 2070-1721 + + + Defining Well-Known Uniform Resource Identifiers (URIs) + +Abstract + + This memo defines a path prefix for "well-known locations", + "/.well-known/", in selected Uniform Resource Identifier (URI) + schemes. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc5785. + +Copyright Notice + + Copyright (c) 2010 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 1] + +RFC 5785 Defining Well-Known URIs April 2010 + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2 + 1.1. Appropriate Use of Well-Known URIs . . . . . . . . . . . . 3 + 2. Notational Conventions . . . . . . . . . . . . . . . . . . . . 3 + 3. Well-Known URIs . . . . . . . . . . . . . . . . . . . . . . . . 3 + 4. Security Considerations . . . . . . . . . . . . . . . . . . . . 4 + 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 4 + 5.1. The Well-Known URI Registry . . . . . . . . . . . . . . . . 4 + 5.1.1. Registration Template . . . . . . . . . . . . . . . . . 5 + 6. References . . . . . . . . . . . . . . . . . . . . . . . . . . 5 + 6.1. Normative References . . . . . . . . . . . . . . . . . . . 5 + 6.2. Informative References . . . . . . . . . . . . . . . . . . 5 + Appendix A. Acknowledgements . . . . . . . . . . . . . . . . . . . 7 + Appendix B. Frequently Asked Questions . . . . . . . . . . . . . . 7 + +1. Introduction + + It is increasingly common for Web-based protocols to require the + discovery of policy or other information about a host ("site-wide + metadata") before making a request. For example, the Robots + Exclusion Protocol specifies a way for + automated processes to obtain permission to access resources; + likewise, the Platform for Privacy Preferences [W3C.REC-P3P-20020416] + tells user-agents how to discover privacy policy beforehand. + + While there are several ways to access per-resource metadata (e.g., + HTTP headers, WebDAV's PROPFIND [RFC4918]), the perceived overhead + (either in terms of client-perceived latency and/or deployment + difficulties) associated with them often precludes their use in these + scenarios. + + When this happens, it is common to designate a "well-known location" + for such data, so that it can be easily located. However, this + approach has the drawback of risking collisions, both with other such + designated "well-known locations" and with pre-existing resources. + + To address this, this memo defines a path prefix in HTTP(S) URIs for + these "well-known locations", "/.well-known/". Future specifications + that need to define a resource for such site-wide metadata can + register their use to avoid collisions and minimise impingement upon + sites' URI space. + + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 2] + +RFC 5785 Defining Well-Known URIs April 2010 + + +1.1. Appropriate Use of Well-Known URIs + + There are a number of possible ways that applications could use Well- + known URIs. However, in keeping with the Architecture of the World- + Wide Web [W3C.REC-webarch-20041215], well-known URIs are not intended + for general information retrieval or establishment of large URI + namespaces on the Web. Rather, they are designed to facilitate + discovery of information on a site when it isn't practical to use + other mechanisms; for example, when discovering policy that needs to + be evaluated before a resource is accessed, or when using multiple + round-trips is judged detrimental to performance. + + As such, the well-known URI space was created with the expectation + that it will be used to make site-wide policy information and other + metadata available directly (if sufficiently concise), or provide + references to other URIs that provide such metadata. + +2. Notational Conventions + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [RFC2119]. + +3. Well-Known URIs + + A well-known URI is a URI [RFC3986] whose path component begins with + the characters "/.well-known/", and whose scheme is "HTTP", "HTTPS", + or another scheme that has explicitly been specified to use well- + known URIs. + + Applications that wish to mint new well-known URIs MUST register + them, following the procedures in Section 5.1. + + For example, if an application registers the name 'example', the + corresponding well-known URI on 'http://www.example.com/' would be + 'http://www.example.com/.well-known/example'. + + Registered names MUST conform to the segment-nz production in + [RFC3986]. + + Note that this specification defines neither how to determine the + authority to use for a particular context, nor the scope of the + metadata discovered by dereferencing the well-known URI; both should + be defined by the application itself. + + Typically, a registration will reference a specification that defines + the format and associated media type to be obtained by dereferencing + the well-known URI. + + + +Nottingham & Hammer-Lahav Standards Track [Page 3] + +RFC 5785 Defining Well-Known URIs April 2010 + + + It MAY also contain additional information, such as the syntax of + additional path components, query strings and/or fragment identifiers + to be appended to the well-known URI, or protocol-specific details + (e.g., HTTP [RFC2616] method handling). + + Note that this specification does not define a format or media-type + for the resource located at "/.well-known/" and clients should not + expect a resource to exist at that location. + +4. Security Considerations + + This memo does not specify the scope of applicability of metadata or + policy obtained from a well-known URI, and does not specify how to + discover a well-known URI for a particular application. Individual + applications using this mechanism must define both aspects. + + Applications minting new well-known URIs, as well as administrators + deploying them, will need to consider several security-related + issues, including (but not limited to) exposure of sensitive data, + denial-of-service attacks (in addition to normal load issues), server + and client authentication, vulnerability to DNS rebinding attacks, + and attacks where limited access to a server grants the ability to + affect how well-known URIs are served. + +5. IANA Considerations + +5.1. The Well-Known URI Registry + + This document establishes the well-known URI registry. + + Well-known URIs are registered on the advice of one or more + Designated Experts (appointed by the IESG or their delegate), with a + Specification Required (using terminology from [RFC5226]). However, + to allow for the allocation of values prior to publication, the + Designated Expert(s) may approve registration once they are satisfied + that such a specification will be published. + + Registration requests should be sent to the + wellknown-uri-review@ietf.org mailing list for review and comment, + with an appropriate subject (e.g., "Request for well-known URI: + example"). + + Before a period of 14 days has passed, the Designated Expert(s) will + either approve or deny the registration request, communicating this + decision both to the review list and to IANA. Denials should include + an explanation and, if applicable, suggestions as to how to make the + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 4] + +RFC 5785 Defining Well-Known URIs April 2010 + + + request successful. Registration requests that are undetermined for + a period longer than 21 days can be brought to the IESG's attention + (using the iesg@iesg.org mailing list) for resolution. + +5.1.1. Registration Template + + URI suffix: The name requested for the well-known URI, relative to + "/.well-known/"; e.g., "example". + + Change controller: For Standards-Track RFCs, state "IETF". For + others, give the name of the responsible party. Other details + (e.g., postal address, e-mail address, home page URI) may also be + included. + + Specification document(s): Reference to the document that specifies + the field, preferably including a URI that can be used to retrieve + a copy of the document. An indication of the relevant sections + may also be included, but is not required. + + Related information: Optionally, citations to additional documents + containing further relevant information. + +6. References + +6.1. Normative References + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform + Resource Identifier (URI): Generic Syntax", STD 66, + RFC 3986, January 2005. + + [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an + IANA Considerations Section in RFCs", BCP 26, RFC 5226, + May 2008. + +6.2. Informative References + + [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, + L., Leach, P., and T. Berners-Lee, "Hypertext Transfer + Protocol -- HTTP/1.1", RFC 2616, June 1999. + + [RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed + Authoring and Versioning (WebDAV)", RFC 4918, June 2007. + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 5] + +RFC 5785 Defining Well-Known URIs April 2010 + + + [W3C.REC-P3P-20020416] + Marchiori, M., "The Platform for Privacy Preferences 1.0 + (P3P1.0) Specification", World Wide Web Consortium + Recommendation REC-P3P-20020416, April 2002, + . + + [W3C.REC-webarch-20041215] + Jacobs, I. and N. Walsh, "Architecture of the World Wide + Web, Volume One", World Wide Web Consortium + Recommendation REC- webarch-20041215, December 2004, + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 6] + +RFC 5785 Defining Well-Known URIs April 2010 + + +Appendix A. Acknowledgements + + We would like to acknowledge the contributions of everyone who + provided feedback and use cases for this document; in particular, + Phil Archer, Dirk Balfanz, Adam Barth, Tim Bray, Brian Eaton, Brad + Fitzpatrick, Joe Gregorio, Paul Hoffman, Barry Leiba, Ashok Malhotra, + Breno de Medeiros, John Panzer, and Drummond Reed. However, they are + not responsible for errors and omissions. + +Appendix B. Frequently Asked Questions + + 1. Aren't well-known locations bad for the Web? + + They are, but for various reasons -- both technical and social -- + they are commonly used and their use is increasing. This memo + defines a "sandbox" for them, to reduce the risks of collision and + to minimise the impact upon pre-existing URIs on sites. + + 2. Why /.well-known? + + It's short, descriptive, and according to search indices, not + widely used. + + 3. What impact does this have on existing mechanisms, such as P3P and + robots.txt? + + None, until they choose to use this mechanism. + + 4. Why aren't per-directory well-known locations defined? + + Allowing every URI path segment to have a well-known location + (e.g., "/images/.well-known/") would increase the risks of + colliding with a pre-existing URI on a site, and generally these + solutions are found not to scale well, because they're too + "chatty". + + + + + + + + + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 7] + +RFC 5785 Defining Well-Known URIs April 2010 + + +Authors' Addresses + + Mark Nottingham + + EMail: mnot@mnot.net + URI: http://www.mnot.net/ + + + Eran Hammer-Lahav + + EMail: eran@hueniverse.com + URI: http://hueniverse.com/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Nottingham & Hammer-Lahav Standards Track [Page 8] + diff --git a/dav/SabreDAV/examples/sql/mysql.locks.sql b/dav/SabreDAV/examples/sql/mysql.locks.sql index 2fafac9cb..cf3caf4f7 100644 --- a/dav/SabreDAV/examples/sql/mysql.locks.sql +++ b/dav/SabreDAV/examples/sql/mysql.locks.sql @@ -6,5 +6,8 @@ CREATE TABLE locks ( token VARCHAR(100), scope TINYINT, depth TINYINT, - uri text + uri VARCHAR(1000), + INDEX(token), + INDEX(uri) ); + diff --git a/dav/SabreDAV/examples/webserver/apache2_vhost.conf b/dav/SabreDAV/examples/webserver/apache2_vhost.conf index 5a816e1f5..bb374eb0f 100644 --- a/dav/SabreDAV/examples/webserver/apache2_vhost.conf +++ b/dav/SabreDAV/examples/webserver/apache2_vhost.conf @@ -7,27 +7,27 @@ # settings as well. - # Don't forget to change the server name - # ServerName dav.example.org + # Don't forget to change the server name + # ServerName dav.example.org - # The DocumentRoot is also required + # The DocumentRoot is also required # DocumentRoot /home/sabredav/ - RewriteEngine On - # This makes every request go to server.php - RewriteRule ^/(.*)$ /server.php [L] + RewriteEngine On + # This makes every request go to server.php + RewriteRule ^/(.*)$ /server.php [L] - # Output buffering needs to be off, to prevent high memory usage - php_flag output_buffering off + # Output buffering needs to be off, to prevent high memory usage + php_flag output_buffering off - # This is also to prevent high memory usage - php_flag always_populate_raw_post_data off + # This is also to prevent high memory usage + php_flag always_populate_raw_post_data off - # This is almost a given, but magic quotes is *still* on on some - # linux distributions - php_flag magic_quotes_gpc off + # This is almost a given, but magic quotes is *still* on on some + # linux distributions + php_flag magic_quotes_gpc off - # SabreDAV is not compatible with mbstring function overloading - php_flag mbstring.func_overload off + # SabreDAV is not compatible with mbstring function overloading + php_flag mbstring.func_overload off diff --git a/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf b/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf index a034d7fca..607254c6e 100644 --- a/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf +++ b/dav/SabreDAV/examples/webserver/apache2_vhost_cgi.conf @@ -6,16 +6,16 @@ # This configuration assumes CGI or FastCGI is used. - # Don't forget to change the server name - # ServerName dav.example.org + # Don't forget to change the server name + # ServerName dav.example.org - # The DocumentRoot is also required + # The DocumentRoot is also required # DocumentRoot /home/sabredav/ - # This makes every request go to server.php. This also makes sure - # the Authentication information is available. If your server script is - # not called server.php, be sure to change it. - RewriteEngine On - RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + # This makes every request go to server.php. This also makes sure + # the Authentication information is available. If your server script is + # not called server.php, be sure to change it. + RewriteEngine On + RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php index 064cad7ce..480e6329f 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/Abstract.php @@ -3,45 +3,15 @@ /** * Abstract Calendaring backend. Extend this class to create your own backends. * + * Checkout the BackendInterface for all the methods that must be implemented. + * * @package Sabre * @subpackage CalDAV * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ -abstract class Sabre_CalDAV_Backend_Abstract { - - /** - * Returns a list of calendars for a principal. - * - * Every project is an array with the following keys: - * * id, a unique id that will be used by other functions to modify the - * calendar. This can be the same as the uri or a database key. - * * uri, which the basename of the uri with which the calendar is - * accessed. - * * principaluri. The owner of the calendar. Almost always the same as - * principalUri passed to this method. - * - * Furthermore it can contain webdav properties in clark notation. A very - * common one is '{DAV:}displayname'. - * - * @param string $principalUri - * @return array - */ - abstract function getCalendarsForUser($principalUri); - - /** - * Creates a new calendar for a principal. - * - * If the creation was a success, an id must be returned that can be used to reference - * this calendar in other methods, such as updateCalendar. - * - * @param string $principalUri - * @param string $calendarUri - * @param array $properties - * @return void - */ - abstract function createCalendar($principalUri,$calendarUri,array $properties); +abstract class Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_BackendInterface { /** * Updates properties for a calendar. @@ -85,102 +55,6 @@ abstract class Sabre_CalDAV_Backend_Abstract { } - /** - * Delete a calendar and all it's objects - * - * @param mixed $calendarId - * @return void - */ - abstract function deleteCalendar($calendarId); - - /** - * Returns all calendar objects within a calendar. - * - * Every item contains an array with the following keys: - * * id - unique identifier which will be used for subsequent updates - * * calendardata - The iCalendar-compatible calendar data - * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. - * * lastmodified - a timestamp of the last modification time - * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: - * ' "abcdef"') - * * calendarid - The calendarid as it was passed to this function. - * * size - The size of the calendar objects, in bytes. - * - * Note that the etag is optional, but it's highly encouraged to return for - * speed reasons. - * - * The calendardata is also optional. If it's not returned - * 'getCalendarObject' will be called later, which *is* expected to return - * calendardata. - * - * If neither etag or size are specified, the calendardata will be - * used/fetched to determine these numbers. If both are specified the - * amount of times this is needed is reduced by a great degree. - * - * @param mixed $calendarId - * @return array - */ - abstract function getCalendarObjects($calendarId); - - /** - * Returns information from a single calendar object, based on it's object - * uri. - * - * The returned array must have the same keys as getCalendarObjects. The - * 'calendardata' object is required here though, while it's not required - * for getCalendarObjects. - * - * @param mixed $calendarId - * @param string $objectUri - * @return array - */ - abstract function getCalendarObject($calendarId,$objectUri); - - /** - * Creates a new calendar object. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - abstract function createCalendarObject($calendarId,$objectUri,$calendarData); - - /** - * Updates an existing calendarobject, based on it's uri. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - abstract function updateCalendarObject($calendarId,$objectUri,$calendarData); - - /** - * Deletes an existing calendar object. - * - * @param mixed $calendarId - * @param string $objectUri - * @return void - */ - abstract function deleteCalendarObject($calendarId,$objectUri); - /** * Performs a calendar-query on the contents of this calendar. * diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php new file mode 100644 index 000000000..881538ab6 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/BackendInterface.php @@ -0,0 +1,231 @@ + array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param mixed $calendarId + * @param array $mutations + * @return bool|array + */ + public function updateCalendar($calendarId, array $mutations); + + /** + * Delete a calendar and all it's objects + * + * @param mixed $calendarId + * @return void + */ + public function deleteCalendar($calendarId); + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * id - unique identifier which will be used for subsequent updates + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * calendarid - The calendarid as it was passed to this function. + * * size - The size of the calendar objects, in bytes. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param mixed $calendarId + * @return array + */ + public function getCalendarObjects($calendarId); + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * @param mixed $calendarId + * @param string $objectUri + * @return array + */ + public function getCalendarObject($calendarId,$objectUri); + + /** + * Creates a new calendar object. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + public function createCalendarObject($calendarId,$objectUri,$calendarData); + + /** + * Updates an existing calendarobject, based on it's uri. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + public function updateCalendarObject($calendarId,$objectUri,$calendarData); + + /** + * Deletes an existing calendar object. + * + * @param mixed $calendarId + * @param string $objectUri + * @return void + */ + public function deleteCalendarObject($calendarId,$objectUri); + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by Sabre_CalDAV_CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on either VEVENT or VTODO. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interprete all these filters can also simply + * be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * @param mixed $calendarId + * @param array $filters + * @return array + */ + public function calendarQuery($calendarId, array $filters); + +} diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php new file mode 100644 index 000000000..ad905b1a3 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Backend/NotificationSupport.php @@ -0,0 +1,44 @@ +caldavBackend = $caldavBackend; $this->principalBackend = $principalBackend; diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php index 1adf8689c..318a4fb52 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarObject.php @@ -12,7 +12,7 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV_ICalendarObject, Sabre_DAVACL_IACL { /** - * Sabre_CalDAV_Backend_Abstract + * Sabre_CalDAV_Backend_BackendInterface * * @var array */ @@ -35,11 +35,11 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV /** * Constructor * - * @param Sabre_CalDAV_Backend_Abstract $caldavBackend + * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend * @param array $calendarInfo * @param array $objectData */ - public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,array $calendarInfo,array $objectData) { + public function __construct(Sabre_CalDAV_Backend_BackendInterface $caldavBackend,array $calendarInfo,array $objectData) { $this->caldavBackend = $caldavBackend; diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php index bd0d34338..098edccca 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php @@ -68,7 +68,7 @@ class Sabre_CalDAV_CalendarQueryParser { $this->xpath = new DOMXPath($dom); $this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV); - $this->xpath->registerNameSpace('dav','urn:DAV'); + $this->xpath->registerNameSpace('dav','DAV:'); } diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php index 4bcd32cdf..8f674840e 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php @@ -304,28 +304,29 @@ class Sabre_CalDAV_CalendarQueryValidator { // one is the first to trigger. Based on this, we can // determine if we can 'give up' expanding events. $firstAlarm = null; - foreach($expandedEvent->VALARM as $expandedAlarm) { + if ($expandedEvent->VALARM !== null) { + foreach($expandedEvent->VALARM as $expandedAlarm) { - $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); - if ($expandedAlarm->isInTimeRange($start, $end)) { - return true; - } + $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); + if ($expandedAlarm->isInTimeRange($start, $end)) { + return true; + } - if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { - // This is an alarm with a non-relative trigger - // time, likely created by a buggy client. The - // implication is that every alarm in this - // recurring event trigger at the exact same - // time. It doesn't make sense to traverse - // further. - } else { - // We store the first alarm as a means to - // figure out when we can stop traversing. - if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { - $firstAlarm = $effectiveTrigger; + if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { + // This is an alarm with a non-relative trigger + // time, likely created by a buggy client. The + // implication is that every alarm in this + // recurring event trigger at the exact same + // time. It doesn't make sense to traverse + // further. + } else { + // We store the first alarm as a means to + // figure out when we can stop traversing. + if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { + $firstAlarm = $effectiveTrigger; + } } } - } if (is_null($firstAlarm)) { // No alarm was found. diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php index 5c4265c51..eb62eea75 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/CalendarRootNode.php @@ -17,7 +17,7 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec /** * CalDAV backend * - * @var Sabre_CalDAV_Backend_Abstract + * @var Sabre_CalDAV_Backend_BackendInterface */ protected $caldavBackend; @@ -33,10 +33,10 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec * * * @param Sabre_DAVACL_IPrincipalBackend $principalBackend - * @param Sabre_CalDAV_Backend_Abstract $caldavBackend + * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend * @param string $principalPrefix */ - public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend, $principalPrefix = 'principals') { + public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $principalPrefix = 'principals') { parent::__construct($principalBackend, $principalPrefix); $this->caldavBackend = $caldavBackend; diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php b/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php new file mode 100644 index 000000000..4ac617d22 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Exception/InvalidComponentType.php @@ -0,0 +1,32 @@ +ownerDocument; + + $np = $doc->createElementNS(Sabre_CalDAV_Plugin::NS_CALDAV,'cal:supported-calendar-component'); + $errorNode->appendChild($np); + + } + +} \ No newline at end of file diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php new file mode 100644 index 000000000..8f6cb2601 --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Collection.php @@ -0,0 +1,80 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns all notifications for a principal + * + * @return array + */ + public function getChildren() { + + $children = array(); + $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri); + + foreach($notifications as $notification) { + + $children[] = new Sabre_CalDAV_Notifications_Node( + $this->caldavBackend, + $notification + ); + } + + return $children; + + } + + /** + * Returns the name of this object + * + * @return string + */ + public function getName() { + + return 'notifications'; + + } + +} diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php new file mode 100644 index 000000000..eb873af3f --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/ICollection.php @@ -0,0 +1,22 @@ +caldavBackend = $caldavBackend; + $this->notification = $notification; + + } + + /** + * Returns the path name for this notification + * + * @return id + */ + public function getName() { + + return $this->notification->getId(); + + } + + /** + * This method must return an xml element, using the + * Sabre_CalDAV_Notifications_INotificationType classes. + * + * @return Sabre_DAVNotification_INotificationType + */ + public function getNotificationType() { + + return $this->notification; + + } + +} diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php new file mode 100644 index 000000000..21c86632b --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php @@ -0,0 +1,158 @@ +id = $id; + $this->type = $type; + $this->description = $description; + $this->href = $href; + + } + + /** + * Serializes the notification as a single property. + * + * You should usually just encode the single top-level element of the + * notification. + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node + * @return void + */ + public function serialize(Sabre_DAV_Server $server, \DOMElement $node) { + + switch($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $prop = $node->ownerDocument->createElement('cs:systemstatus'); + $prop->setAttribute('type', $type); + + $node->appendChild($prop); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Sabre_DAV_Server $server + * @param DOMElement $node + * @return void + */ + public function serializeBody(Sabre_DAV_Server $server, \DOMElement $node) { + + switch($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $prop = $node->ownerDocument->createElement('cs:systemstatus'); + $prop->setAttribute('type', $type); + + if ($this->description) { + $text = $node->ownerDocument->createTextNode($this->description); + $desc = $node->ownerDocument->createElement('cs:description'); + $desc->appendChild($text); + $prop->appendChild($desc); + } + if ($this->href) { + $text = $node->ownerDocument->createTextNode($this->href); + $href = $node->ownerDocument->createElement('d:href'); + $href->appendChild($text); + $prop->appendChild($href); + } + + $node->appendChild($prop); + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + public function getId() { + + return $this->id; + + } + +} diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php b/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php index faf426f81..0b0978f14 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/Plugin.php @@ -162,6 +162,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent')); $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile')); + $server->subscribeEvent('beforeMethod', array($this,'beforeMethod')); $server->xmlNamespaces[self::NS_CALDAV] = 'cal'; $server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs'; @@ -172,6 +173,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { $server->resourceTypeMapping['Sabre_CalDAV_Schedule_IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox'; $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read'; $server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write'; + $server->resourceTypeMapping['Sabre_CalDAV_Notifications_ICollection'] = '{' . self::NS_CALENDARSERVER . '}notifications'; + $server->resourceTypeMapping['Sabre_CalDAV_Notifications_INode'] = '{' . self::NS_CALENDARSERVER . '}notification'; array_push($server->protectedProperties, @@ -195,7 +198,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { // CalendarServer extensions '{' . self::NS_CALENDARSERVER . '}getctag', '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for', - '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for' + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for', + '{' . self::NS_CALENDARSERVER . '}notification-URL', + '{' . self::NS_CALENDARSERVER . '}notificationtype' ); } @@ -380,8 +385,31 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } + // notification-URL property + $notificationUrl = '{' . self::NS_CALENDARSERVER . '}notification-URL'; + if (($index = array_search($notificationUrl, $requestedProperties)) !== false) { + $principalId = $node->getName(); + $calendarHomePath = 'calendars/' . $principalId . '/notifications/'; + unset($requestedProperties[$index]); + $returnedProperties[200][$notificationUrl] = new Sabre_DAV_Property_Href($calendarHomePath); + } + } // instanceof IPrincipal + if ($node instanceof Sabre_CalDAV_Notifications_INode) { + + $propertyName = '{' . self::NS_CALENDARSERVER . '}notificationtype'; + if (($index = array_search($propertyName, $requestedProperties)) !== false) { + + $returnedProperties[200][$propertyName] = + $node->getNotificationType(); + + unset($requestedProperties[$index]); + + } + + } // instanceof Notifications_INode + if ($node instanceof Sabre_CalDAV_ICalendarObject) { // The calendar-data property is not supposed to be a 'real' @@ -414,11 +442,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { public function calendarMultiGetReport($dom) { $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); + $hrefElems = $dom->getElementsByTagNameNS('DAV:','href'); $xpath = new DOMXPath($dom); $xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV); - $xpath->registerNameSpace('dav','urn:DAV'); + $xpath->registerNameSpace('dav','DAV:'); $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); if ($expand->length>0) { @@ -648,7 +676,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { if (!$node instanceof Sabre_CalDAV_ICalendarObject) return; - $this->validateICalendar($data); + $this->validateICalendar($data, $path); } @@ -668,7 +696,49 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { if (!$parentNode instanceof Sabre_CalDAV_Calendar) return; - $this->validateICalendar($data); + $this->validateICalendar($data, $path); + + } + + /** + * This event is triggered before any HTTP request is handled. + * + * We use this to intercept GET calls to notification nodes, and return the + * proper response. + * + * @param string $method + * @param string $path + * @return void + */ + public function beforeMethod($method, $path) { + + if ($method!=='GET') return; + + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (Sabre_DAV_Exception_NotFound $e) { + return; + } + + if (!$node instanceof Sabre_CalDAV_Notifications_INode) + return; + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $root = $dom->createElement('cs:notification'); + foreach($this->server->xmlNamespaces as $namespace => $prefix) { + $root->setAttribute('xmlns:' . $prefix, $namespace); + } + + $dom->appendChild($root); + $node->getNotificationType()->serializeBody($this->server, $root); + + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->sendBody($dom->saveXML()); + + return false; } @@ -678,9 +748,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { * An exception is thrown if it's not. * * @param resource|string $data + * @param string $path * @return void */ - protected function validateICalendar(&$data) { + protected function validateICalendar(&$data, $path) { // If it's a stream, we convert it to a string first. if (is_resource($data)) { @@ -704,6 +775,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.'); } + // Get the Supported Components for the target calendar + list($parentPath,$object) = Sabre_Dav_URLUtil::splitPath($path); + $calendarProperties = $this->server->getProperties($parentPath,array('{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set')); + $supportedComponents = $calendarProperties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue(); + $foundType = null; $foundUID = null; foreach($vobj->getComponents() as $component) { @@ -715,6 +791,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { case 'VJOURNAL' : if (is_null($foundType)) { $foundType = $component->name; + if (!in_array($foundType, $supportedComponents)) { + throw new Sabre_CalDAV_Exception_InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType); + } if (!isset($component->UID)) { throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID'); } @@ -756,7 +835,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests'); } - if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) { + if (!preg_match('/^mailto:(.*)@(.*)$/i', $originator)) { throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address'); } $originator = substr($originator,7); @@ -765,7 +844,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { foreach($recipients as $k=>$recipient) { $recipient = trim($recipient); - if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) { + if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) { throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address'); } $recipient = substr($recipient, 7); @@ -813,9 +892,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') { - $this->iMIPMessage($originator, $recipients, $vObject, $principal); + $result = $this->iMIPMessage($originator, $recipients, $vObject, $principal); $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody('Messages sent'); + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendBody($this->generateScheduleResponse($result)); } else { throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented'); } @@ -825,18 +905,81 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * Sends an iMIP message by email. * + * This method must return an array with status codes per recipient. + * This should look something like: + * + * array( + * 'user1@example.org' => '2.0;Success' + * ) + * + * Formatting for this status code can be found at: + * https://tools.ietf.org/html/rfc5545#section-3.8.8.3 + * + * A list of valid status codes can be found at: + * https://tools.ietf.org/html/rfc5546#section-3.6 + * * @param string $originator * @param array $recipients * @param Sabre_VObject_Component $vObject - * @param string $principal Principal url - * @return void + * @return array */ protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) { if (!$this->imipHandler) { - throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.'); + $resultStatus = '5.2;This server does not support this operation'; + } else { + $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal); + $resultStatus = '2.0;Success'; } - $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal); + + $result = array(); + foreach($recipients as $recipient) { + $result[$recipient] = $resultStatus; + } + + return $result; + + } + + /** + * Generates a schedule-response XML body + * + * The recipients array is a key->value list, containing email addresses + * and iTip status codes. See the iMIPMessage method for a description of + * the value. + * + * @param array $recipients + * @return string + */ + public function generateScheduleResponse(array $recipients) { + + $dom = new DOMDocument('1.0','utf-8'); + $dom->formatOutput = true; + $xscheduleResponse = $dom->createElement('cal:schedule-response'); + $dom->appendChild($xscheduleResponse); + + foreach($this->server->xmlNamespaces as $namespace=>$prefix) { + + $xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); + + } + + foreach($recipients as $recipient=>$status) { + $xresponse = $dom->createElement('cal:response'); + + $xrecipient = $dom->createElement('cal:recipient'); + $xrecipient->appendChild($dom->createTextNode($recipient)); + $xresponse->appendChild($xrecipient); + + $xrequestStatus = $dom->createElement('cal:request-status'); + $xrequestStatus->appendChild($dom->createTextNode($status)); + $xresponse->appendChild($xrequestStatus); + + $xscheduleResponse->appendChild($xresponse); + + } + + return $dom->saveXML(); } diff --git a/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php b/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php index b8d3f0573..da8c3b60d 100644 --- a/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php +++ b/dav/SabreDAV/lib/Sabre/CalDAV/UserCalendars.php @@ -21,7 +21,7 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre /** * CalDAV backend * - * @var Sabre_CalDAV_Backend_Abstract + * @var Sabre_CalDAV_Backend_BackendInterface */ protected $caldavBackend; @@ -36,10 +36,10 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre * Constructor * * @param Sabre_DAVACL_IPrincipalBackend $principalBackend - * @param Sabre_CalDAV_Backend_Abstract $caldavBackend + * @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend * @param mixed $userUri */ - public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) { + public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $userUri) { $this->principalBackend = $principalBackend; $this->caldavBackend = $caldavBackend; @@ -171,6 +171,11 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre $objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar); } $objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']); + + // We're adding a notifications node, if it's supported by the backend. + if ($this->caldavBackend instanceof Sabre_CalDAV_Backend_NotificationSupport) { + $objs[] = new Sabre_CalDAV_Notifications_Collection($this->caldavBackend, $this->principalInfo['uri']); + } return $objs; } diff --git a/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php b/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php index 1ec584bb6..413a77f3b 100644 --- a/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php +++ b/dav/SabreDAV/lib/Sabre/CardDAV/Backend/PDO.php @@ -238,7 +238,7 @@ class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract { * Creates a new card. * * The addressbook id will be passed as the first argument. This is the - * same id as it is returned from the getCards method. + * same id as it is returned from the getAddressbooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. diff --git a/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php b/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php index 66f91af3b..6335e2f27 100644 --- a/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/CardDAV/Plugin.php @@ -154,8 +154,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { if (is_resource($val)) $val = stream_get_contents($val); - // Taking out \r to not screw up the xml output - $returnedProperties[200][$addressDataProp] = str_replace("\r","", $val); + $returnedProperties[200][$addressDataProp] = $val; } } @@ -270,7 +269,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); + $hrefElems = $dom->getElementsByTagNameNS('DAV:','href'); $propertyList = array(); foreach($hrefElems as $elem) { @@ -358,6 +357,10 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.'); } + if (!isset($vobj->UID)) { + throw new Sabre_DAV_Exception_BadRequest('Every vcard must have an UID.'); + } + } @@ -440,6 +443,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { $vcard = Sabre_VObject_Reader::read($vcardData); + if (!$filters) return true; + foreach($filters as $filter) { $isDefined = isset($vcard->{$filter['name']}); diff --git a/dav/SabreDAV/lib/Sabre/DAV/Client.php b/dav/SabreDAV/lib/Sabre/DAV/Client.php index 98126f8e5..8f3dcc78f 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Client.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Client.php @@ -485,19 +485,17 @@ class Sabre_DAV_Client { */ public function parseMultiStatus($body) { - $body = Sabre_DAV_XMLUtil::convertDAVNamespace($body); - $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA); if ($responseXML===false) { throw new InvalidArgumentException('The passed data is not valid XML'); } - $responseXML->registerXPathNamespace('d', 'urn:DAV'); + $responseXML->registerXPathNamespace('d', 'DAV:'); $propResult = array(); foreach($responseXML->xpath('d:response') as $response) { - $response->registerXPathNamespace('d', 'urn:DAV'); + $response->registerXPathNamespace('d', 'DAV:'); $href = $response->xpath('d:href'); $href = (string)$href[0]; @@ -505,7 +503,7 @@ class Sabre_DAV_Client { foreach($response->xpath('d:propstat') as $propStat) { - $propStat->registerXPathNamespace('d', 'urn:DAV'); + $propStat->registerXPathNamespace('d', 'DAV:'); $status = $propStat->xpath('d:status'); list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3); diff --git a/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php b/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php index 3719d0951..957ac506a 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Locks/Plugin.php @@ -293,7 +293,10 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { $this->server->tree->getNodeForPath($uri); // We need to call the beforeWriteContent event for RFC3744 - $this->server->broadcastEvent('beforeWriteContent',array($uri)); + // Edit: looks like this is not used, and causing problems now. + // + // See Issue 222 + // $this->server->broadcastEvent('beforeWriteContent',array($uri)); } catch (Sabre_DAV_Exception_NotFound $e) { diff --git a/dav/SabreDAV/lib/Sabre/DAV/Property.php b/dav/SabreDAV/lib/Sabre/DAV/Property.php index 1cfada323..26f2c1d08 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Property.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Property.php @@ -11,9 +11,7 @@ * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ -abstract class Sabre_DAV_Property { - - abstract function serialize(Sabre_DAV_Server $server, DOMElement $prop); +abstract class Sabre_DAV_Property implements Sabre_DAV_PropertyInterface { static function unserialize(DOMElement $prop) { diff --git a/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php b/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php index 88afbcfb2..9f21163d1 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Property/Response.php @@ -138,7 +138,7 @@ class Sabre_DAV_Property_Response extends Sabre_DAV_Property implements Sabre_DA if (is_scalar($propertyValue)) { $text = $document->createTextNode($propertyValue); $currentProperty->appendChild($text); - } elseif ($propertyValue instanceof Sabre_DAV_Property) { + } elseif ($propertyValue instanceof Sabre_DAV_PropertyInterface) { $propertyValue->serialize($server,$currentProperty); } elseif (!is_null($propertyValue)) { throw new Sabre_DAV_Exception('Unknown property value type: ' . gettype($propertyValue) . ' for property: ' . $propertyName); diff --git a/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php b/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php new file mode 100644 index 000000000..515072cbd --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/DAV/PropertyInterface.php @@ -0,0 +1,21 @@ +broadcastEvent('exception', array($e)); + } catch (Exception $ignore) { + } $DOM = new DOMDocument('1.0','utf-8'); $DOM->formatOutput = true; @@ -508,7 +512,7 @@ class Sabre_DAV_Server { if (!$this->checkPreconditions(true)) return false; - if (!($node instanceof Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects'); + if (!$node instanceof Sabre_DAV_IFile) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects'); $body = $node->get(); // Converting string into stream, if needed. @@ -1995,7 +1999,7 @@ class Sabre_DAV_Server { if (!$body) return array(); $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); - $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); + $elem = $dom->getElementsByTagNameNS('DAV:','propfind')->item(0); return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); } diff --git a/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php b/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php index 85a9ee317..40580ae36 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php +++ b/dav/SabreDAV/lib/Sabre/DAV/Tree/Filesystem.php @@ -42,9 +42,9 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree { $realPath = $this->getRealPath($path); if (!file_exists($realPath)) throw new Sabre_DAV_Exception_NotFound('File at location ' . $realPath . ' not found'); if (is_dir($realPath)) { - return new Sabre_DAV_FS_Directory($path); + return new Sabre_DAV_FS_Directory($realPath); } else { - return new Sabre_DAV_FS_File($path); + return new Sabre_DAV_FS_File($realPath); } } diff --git a/dav/SabreDAV/lib/Sabre/DAV/XMLUtil.php b/dav/SabreDAV/lib/Sabre/DAV/XMLUtil.php index 60eff3b15..712fa3fc0 100644 --- a/dav/SabreDAV/lib/Sabre/DAV/XMLUtil.php +++ b/dav/SabreDAV/lib/Sabre/DAV/XMLUtil.php @@ -20,9 +20,6 @@ class Sabre_DAV_XMLUtil { * {http://www.example.org}myelem * * This format is used throughout the SabreDAV sourcecode. - * Elements encoded with the urn:DAV namespace will - * be returned as if they were in the DAV: namespace. This is to avoid - * compatibility problems. * * This function will return null if a nodetype other than an Element is passed. * @@ -33,8 +30,7 @@ class Sabre_DAV_XMLUtil { if ($dom->nodeType !== XML_ELEMENT_NODE) return null; - // Mapping back to the real namespace, in case it was dav - if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI; + $ns = $dom->namespaceURI; // Mapping to clark notation return '{' . $ns . '}' . $dom->localName; @@ -64,29 +60,11 @@ class Sabre_DAV_XMLUtil { } - /** - * This method takes an XML document (as string) and converts all instances of the - * DAV: namespace to urn:DAV - * - * This is unfortunately needed, because the DAV: namespace violates the xml namespaces - * spec, and causes the DOM to throw errors - * - * @param string $xmlDocument - * @return array|string|null - */ - static function convertDAVNamespace($xmlDocument) { - - // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV: - // namespace is actually a violation of the XML namespaces specification, and will cause errors - return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument); - - } - /** * This method provides a generic way to load a DOMDocument for WebDAV use. * * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors. - * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV. + * It does not preserve whitespace. * * @param string $xml * @throws Sabre_DAV_Exception_BadRequest @@ -118,10 +96,11 @@ class Sabre_DAV_XMLUtil { libxml_clear_errors(); $dom = new DOMDocument(); - $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR); // We don't generally care about any whitespace $dom->preserveWhiteSpace = false; + + $dom->loadXML($xml,LIBXML_NOWARNING | LIBXML_NOERROR); if ($error = libxml_get_last_error()) { libxml_clear_errors(); diff --git a/dav/SabreDAV/lib/Sabre/DAVACL/Property/Acl.php b/dav/SabreDAV/lib/Sabre/DAVACL/Property/Acl.php index 05e1a690b..3f79a8d53 100644 --- a/dav/SabreDAV/lib/Sabre/DAVACL/Property/Acl.php +++ b/dav/SabreDAV/lib/Sabre/DAVACL/Property/Acl.php @@ -88,11 +88,11 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property { static public function unserialize(DOMElement $dom) { $privileges = array(); - $xaces = $dom->getElementsByTagNameNS('urn:DAV','ace'); + $xaces = $dom->getElementsByTagNameNS('DAV:','ace'); for($ii=0; $ii < $xaces->length; $ii++) { $xace = $xaces->item($ii); - $principal = $xace->getElementsByTagNameNS('urn:DAV','principal'); + $principal = $xace->getElementsByTagNameNS('DAV:','principal'); if ($principal->length !== 1) { throw new Sabre_DAV_Exception_BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); } @@ -116,17 +116,17 @@ class Sabre_DAVACL_Property_Acl extends Sabre_DAV_Property { $protected = false; - if ($xace->getElementsByTagNameNS('urn:DAV','protected')->length > 0) { + if ($xace->getElementsByTagNameNS('DAV:','protected')->length > 0) { $protected = true; } - $grants = $xace->getElementsByTagNameNS('urn:DAV','grant'); + $grants = $xace->getElementsByTagNameNS('DAV:','grant'); if ($grants->length < 1) { throw new Sabre_DAV_Exception_NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported'); } $grant = $grants->item(0); - $xprivs = $grant->getElementsByTagNameNS('urn:DAV','privilege'); + $xprivs = $grant->getElementsByTagNameNS('DAV:','privilege'); for($jj=0; $jj<$xprivs->length; $jj++) { $xpriv = $xprivs->item($jj); diff --git a/dav/SabreDAV/lib/Sabre/HTTP/BasicAuth.php b/dav/SabreDAV/lib/Sabre/HTTP/BasicAuth.php index a747cc6a3..f90ed24f5 100644 --- a/dav/SabreDAV/lib/Sabre/HTTP/BasicAuth.php +++ b/dav/SabreDAV/lib/Sabre/HTTP/BasicAuth.php @@ -46,7 +46,7 @@ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth { if (strpos(strtolower($auth),'basic')!==0) return false; - return explode(':', base64_decode(substr($auth, 6))); + return explode(':', base64_decode(substr($auth, 6)),2); } diff --git a/dav/SabreDAV/lib/Sabre/HTTP/Version.php b/dav/SabreDAV/lib/Sabre/HTTP/Version.php index 23dc7f8a7..e6b4f7e53 100644 --- a/dav/SabreDAV/lib/Sabre/HTTP/Version.php +++ b/dav/SabreDAV/lib/Sabre/HTTP/Version.php @@ -14,7 +14,7 @@ class Sabre_HTTP_Version { /** * Full version number */ - const VERSION = '1.6.2'; + const VERSION = '1.6.4'; /** * Stability : alpha, beta, stable diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component.php b/dav/SabreDAV/lib/Sabre/VObject/Component.php index b78a26133..ced593848 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/Component.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Component.php @@ -30,7 +30,7 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { public $children = array(); /** - * If coponents are added to this map, they will be automatically mapped + * If components are added to this map, they will be automatically mapped * to their respective classes, if parsed by the reader or constructed with * the 'create' method. * @@ -94,40 +94,54 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { * * This is solely used by the childrenSort method. * - * A higher score means the item will be higher in the list + * A higher score means the item will be lower in the list. + * To avoid score collisions, each "score category" has a reasonable + * space to accomodate elements. The $key is added to the $score to + * preserve the original relative order of elements. * - * @param Sabre_VObject_Node $n + * @param int $key + * @param Sabre_VObject $array * @return int */ - $sortScore = function($n) { + $sortScore = function($key, $array) { - if ($n instanceof Sabre_VObject_Component) { + if ($array[$key] instanceof Sabre_VObject_Component) { // We want to encode VTIMEZONE first, this is a personal // preference. - if ($n->name === 'VTIMEZONE') { - return 1; + if ($array[$key]->name === 'VTIMEZONE') { + $score=300000000; + return $score+$key; } else { - return 0; + $score=400000000; + return $score+$key; } } else { + // Properties get encoded first // VCARD version 4.0 wants the VERSION property to appear first - if ($n->name === 'VERSION') { - return 3; - } else { - return 2; + if ($array[$key] instanceof Sabre_VObject_Property) { + if ($array[$key]->name === 'VERSION') { + $score=100000000; + return $score+$key; + } else { + // All other properties + $score=200000000; + return $score+$key; + } } } + next($children); }; - usort($this->children, function($a, $b) use ($sortScore) { + $tmp = $this->children; + uksort($this->children, function($a, $b) use ($sortScore, $tmp) { - $sA = $sortScore($a); - $sB = $sortScore($b); + $sA = $sortScore($a, $tmp); + $sB = $sortScore($b, $tmp); if ($sA === $sB) return 0; - return ($sA > $sB) ? -1 : 1; + return ($sA < $sB) ? -1 : 1; }); @@ -250,6 +264,27 @@ class Sabre_VObject_Component extends Sabre_VObject_Element { } + /** + * Validates the node for correctness. + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @return array + */ + public function validate() { + + $result = array(); + foreach($this->children as $child) { + $result = array_merge($result, $child->validate()); + } + return $result; + + } + /* Magic property accessors {{{ */ /** diff --git a/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php b/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php index f3be29afd..35dd90f23 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Component/VCalendar.php @@ -129,5 +129,110 @@ class Sabre_VObject_Component_VCalendar extends Sabre_VObject_Component { } + /** + * Validates the node for correctness. + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @return array + */ + public function validate() { + + $warnings = array(); + + $version = $this->select('VERSION'); + if (count($version)!==1) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The VERSION property must appear in the VCALENDAR component exactly 1 time', + 'node' => $this, + ); + } else { + if ((string)$this->VERSION !== '2.0') { + $warnings[] = array( + 'level' => 1, + 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', + 'node' => $this, + ); + } + } + $version = $this->select('PRODID'); + if (count($version)!==1) { + $warnings[] = array( + 'level' => 2, + 'message' => 'The PRODID property must appear in the VCALENDAR component exactly 1 time', + 'node' => $this, + ); + } + if (count($this->CALSCALE) > 1) { + $warnings[] = array( + 'level' => 2, + 'message' => 'The CALSCALE property must not be specified more than once.', + 'node' => $this, + ); + } + if (count($this->METHOD) > 1) { + $warnings[] = array( + 'level' => 2, + 'message' => 'The METHOD property must not be specified more than once.', + 'node' => $this, + ); + } + + $allowedComponents = array( + 'VEVENT', + 'VTODO', + 'VJOURNAL', + 'VFREEBUSY', + 'VTIMEZONE', + ); + $allowedProperties = array( + 'PRODID', + 'VERSION', + 'CALSCALE', + 'METHOD', + ); + $componentsFound = 0; + foreach($this->children as $child) { + if($child instanceof Sabre_VObject_Component) { + $componentsFound++; + if (!in_array($child->name, $allowedComponents)) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The ' . $child->name . " component is not allowed in the VCALENDAR component", + 'node' => $this, + ); + } + } + if ($child instanceof Sabre_VObject_Property) { + if (!in_array($child->name, $allowedProperties)) { + $warnings[] = array( + 'level' => 2, + 'message' => 'The ' . $child->name . " property is not allowed in the VCALENDAR component", + 'node' => $this, + ); + } + } + } + + if ($componentsFound===0) { + $warnings[] = array( + 'level' => 1, + 'message' => 'An iCalendar object must have at least 1 component.', + 'node' => $this, + ); + } + + return array_merge( + $warnings, + parent::validate() + ); + + } + } diff --git a/dav/SabreDAV/lib/Sabre/VObject/Node.php b/dav/SabreDAV/lib/Sabre/VObject/Node.php index 7701309be..6c8319f76 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/Node.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Node.php @@ -32,6 +32,23 @@ abstract class Sabre_VObject_Node implements IteratorAggregate, ArrayAccess, Cou */ public $parent = null; + /** + * Validates the node for correctness. + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @return array + */ + public function validate() { + + return array(); + + } + /* {{{ IteratorAggregator interface */ /** diff --git a/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php b/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php old mode 100755 new mode 100644 index fe2372caa..ff2c867a3 --- a/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Property/DateTime.php @@ -204,49 +204,17 @@ class Sabre_VObject_Property_DateTime extends Sabre_VObject_Property { ); } - try { - // tzid an Olson identifier? - $tz = new DateTimeZone($tzid->value); - } catch (Exception $e) { - - // Not an Olson id, we're going to try to find the information - // through the time zone name map. - $newtzid = Sabre_VObject_WindowsTimezoneMap::lookup($tzid->value); - if (is_null($newtzid)) { - - // Not a well known time zone name either, we're going to try - // to find the information through the VTIMEZONE object. - - // First we find the root object - $root = $property; - while($root->parent) { - $root = $root->parent; - } - - if (isset($root->VTIMEZONE)) { - foreach($root->VTIMEZONE as $vtimezone) { - if (((string)$vtimezone->TZID) == $tzid) { - if (isset($vtimezone->{'X-LIC-LOCATION'})) { - $newtzid = (string)$vtimezone->{'X-LIC-LOCATION'}; - } else { - // No libical location specified. As a last resort we could - // try matching $vtimezone's DST rules against all known - // time zones returned by DateTimeZone::list* - - // TODO - } - } - } - } - } - - try { - $tz = new DateTimeZone($newtzid); - } catch (Exception $e) { - // If all else fails, we use the default PHP timezone - $tz = new DateTimeZone(date_default_timezone_get()); - } + // To look up the timezone, we must first find the VCALENDAR component. + $root = $property; + while($root->parent) { + $root = $root->parent; } + if ($root->name === 'VCALENDAR') { + $tz = Sabre_VObject_TimeZoneUtil::getTimeZone((string)$tzid, $root); + } else { + $tz = Sabre_VObject_TimeZoneUtil::getTimeZone((string)$tzid); + } + $dt = new DateTime($dateStr, $tz); $dt->setTimeZone($tz); diff --git a/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php b/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php index 39d1b6990..7ccd2049b 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php +++ b/dav/SabreDAV/lib/Sabre/VObject/RecurrenceIterator.php @@ -337,6 +337,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { $this->endDate = clone $this->startDate; if (isset($this->baseEvent->DURATION)) { $this->endDate->add(Sabre_VObject_DateTimeParser::parse($this->baseEvent->DURATION->value)); + } elseif ($this->baseEvent->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) { + $this->endDate->modify('+1 day'); } } $this->currentDate = clone $this->startDate; @@ -565,7 +567,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { */ public function fastForward(DateTime $dt) { - while($this->valid() && $this->getDTEnd() < $dt) { + while($this->valid() && $this->getDTEnd() <= $dt) { $this->next(); } @@ -838,9 +840,40 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { */ protected function nextYearly() { + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + // No sub-rules, so we just advance by year if (!$this->byMonth) { + + // Unless it was a leap day! + if ($currentMonth==2 && $currentDayOfMonth==29) { + + $counter = 0; + do { + $counter++; + // Here we increase the year count by the interval, until + // we hit a date that's also in a leap year. + // + // We could just find the next interval that's dividable by + // 4, but that would ignore the rule that there's no leap + // year every year that's dividable by a 100, but not by + // 400. (1800, 1900, 2100). So we just rely on the datetime + // functions instead. + $nextDate = clone $this->currentDate; + $nextDate->modify('+ ' . ($this->interval*$counter) . ' years'); + } while ($nextDate->format('n')!=2); + $this->currentDate = $nextDate; + + return; + + } + + // The easiest form $this->currentDate->modify('+' . $this->interval . ' years'); return; + } $currentMonth = $this->currentDate->format('n'); @@ -892,8 +925,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { } else { - // no byDay or byMonthDay, so we can just loop through the - // months. + // These are the 'byMonth' rules, if there are no byDay or + // byMonthDay sub-rules. do { $currentMonth++; @@ -903,6 +936,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { } } while (!in_array($currentMonth, $this->byMonth)); $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + return; } diff --git a/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php b/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php new file mode 100644 index 000000000..276288aaa --- /dev/null +++ b/dav/SabreDAV/lib/Sabre/VObject/TimeZoneUtil.php @@ -0,0 +1,351 @@ +'Australia/Darwin', + 'AUS Eastern Standard Time'=>'Australia/Sydney', + 'Afghanistan Standard Time'=>'Asia/Kabul', + 'Alaskan Standard Time'=>'America/Anchorage', + 'Arab Standard Time'=>'Asia/Riyadh', + 'Arabian Standard Time'=>'Asia/Dubai', + 'Arabic Standard Time'=>'Asia/Baghdad', + 'Argentina Standard Time'=>'America/Buenos_Aires', + 'Armenian Standard Time'=>'Asia/Yerevan', + 'Atlantic Standard Time'=>'America/Halifax', + 'Azerbaijan Standard Time'=>'Asia/Baku', + 'Azores Standard Time'=>'Atlantic/Azores', + 'Bangladesh Standard Time'=>'Asia/Dhaka', + 'Canada Central Standard Time'=>'America/Regina', + 'Cape Verde Standard Time'=>'Atlantic/Cape_Verde', + 'Caucasus Standard Time'=>'Asia/Yerevan', + 'Cen. Australia Standard Time'=>'Australia/Adelaide', + 'Central America Standard Time'=>'America/Guatemala', + 'Central Asia Standard Time'=>'Asia/Almaty', + 'Central Brazilian Standard Time'=>'America/Cuiaba', + 'Central Europe Standard Time'=>'Europe/Budapest', + 'Central European Standard Time'=>'Europe/Warsaw', + 'Central Pacific Standard Time'=>'Pacific/Guadalcanal', + 'Central Standard Time'=>'America/Chicago', + 'Central Standard Time (Mexico)'=>'America/Mexico_City', + 'China Standard Time'=>'Asia/Shanghai', + 'Dateline Standard Time'=>'Etc/GMT+12', + 'E. Africa Standard Time'=>'Africa/Nairobi', + 'E. Australia Standard Time'=>'Australia/Brisbane', + 'E. Europe Standard Time'=>'Europe/Minsk', + 'E. South America Standard Time'=>'America/Sao_Paulo', + 'Eastern Standard Time'=>'America/New_York', + 'Egypt Standard Time'=>'Africa/Cairo', + 'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg', + 'FLE Standard Time'=>'Europe/Kiev', + 'Fiji Standard Time'=>'Pacific/Fiji', + 'GMT Standard Time'=>'Europe/London', + 'GTB Standard Time'=>'Europe/Istanbul', + 'Georgian Standard Time'=>'Asia/Tbilisi', + 'Greenland Standard Time'=>'America/Godthab', + 'Greenwich Standard Time'=>'Atlantic/Reykjavik', + 'Hawaiian Standard Time'=>'Pacific/Honolulu', + 'India Standard Time'=>'Asia/Calcutta', + 'Iran Standard Time'=>'Asia/Tehran', + 'Israel Standard Time'=>'Asia/Jerusalem', + 'Jordan Standard Time'=>'Asia/Amman', + 'Kamchatka Standard Time'=>'Asia/Kamchatka', + 'Korea Standard Time'=>'Asia/Seoul', + 'Magadan Standard Time'=>'Asia/Magadan', + 'Mauritius Standard Time'=>'Indian/Mauritius', + 'Mexico Standard Time'=>'America/Mexico_City', + 'Mexico Standard Time 2'=>'America/Chihuahua', + 'Mid-Atlantic Standard Time'=>'Etc/GMT+2', + 'Middle East Standard Time'=>'Asia/Beirut', + 'Montevideo Standard Time'=>'America/Montevideo', + 'Morocco Standard Time'=>'Africa/Casablanca', + 'Mountain Standard Time'=>'America/Denver', + 'Mountain Standard Time (Mexico)'=>'America/Chihuahua', + 'Myanmar Standard Time'=>'Asia/Rangoon', + 'N. Central Asia Standard Time'=>'Asia/Novosibirsk', + 'Namibia Standard Time'=>'Africa/Windhoek', + 'Nepal Standard Time'=>'Asia/Katmandu', + 'New Zealand Standard Time'=>'Pacific/Auckland', + 'Newfoundland Standard Time'=>'America/St_Johns', + 'North Asia East Standard Time'=>'Asia/Irkutsk', + 'North Asia Standard Time'=>'Asia/Krasnoyarsk', + 'Pacific SA Standard Time'=>'America/Santiago', + 'Pacific Standard Time'=>'America/Los_Angeles', + 'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel', + 'Pakistan Standard Time'=>'Asia/Karachi', + 'Paraguay Standard Time'=>'America/Asuncion', + 'Romance Standard Time'=>'Europe/Paris', + 'Russian Standard Time'=>'Europe/Moscow', + 'SA Eastern Standard Time'=>'America/Cayenne', + 'SA Pacific Standard Time'=>'America/Bogota', + 'SA Western Standard Time'=>'America/La_Paz', + 'SE Asia Standard Time'=>'Asia/Bangkok', + 'Samoa Standard Time'=>'Pacific/Apia', + 'Singapore Standard Time'=>'Asia/Singapore', + 'South Africa Standard Time'=>'Africa/Johannesburg', + 'Sri Lanka Standard Time'=>'Asia/Colombo', + 'Syria Standard Time'=>'Asia/Damascus', + 'Taipei Standard Time'=>'Asia/Taipei', + 'Tasmania Standard Time'=>'Australia/Hobart', + 'Tokyo Standard Time'=>'Asia/Tokyo', + 'Tonga Standard Time'=>'Pacific/Tongatapu', + 'US Eastern Standard Time'=>'America/Indianapolis', + 'US Mountain Standard Time'=>'America/Phoenix', + 'UTC'=>'Etc/GMT', + 'UTC+12'=>'Etc/GMT-12', + 'UTC-02'=>'Etc/GMT+2', + 'UTC-11'=>'Etc/GMT+11', + 'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar', + 'Venezuela Standard Time'=>'America/Caracas', + 'Vladivostok Standard Time'=>'Asia/Vladivostok', + 'W. Australia Standard Time'=>'Australia/Perth', + 'W. Central Africa Standard Time'=>'Africa/Lagos', + 'W. Europe Standard Time'=>'Europe/Berlin', + 'West Asia Standard Time'=>'Asia/Tashkent', + 'West Pacific Standard Time'=>'Pacific/Port_Moresby', + 'Yakutsk Standard Time'=>'Asia/Yakutsk', + + // Microsoft exchange timezones + // Source: + // http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx + // + // Correct timezones deduced with help from: + // http://en.wikipedia.org/wiki/List_of_tz_database_time_zones + 'Universal Coordinated Time' => 'UTC', + 'Casablanca, Monrovia' => 'Africa/Casablanca', + 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon', + 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London', + 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', + 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague', + 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', + 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris', + 'Prague, Central Europe' => 'Europe/Prague', + 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo', + 'West Central Africa' => 'Africa/Luanda', // This was a best guess + 'Athens, Istanbul, Minsk' => 'Europe/Athens', + 'Bucharest' => 'Europe/Bucharest', + 'Cairo' => 'Africa/Cairo', + 'Harare, Pretoria' => 'Africa/Harare', + 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki', + 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem', + 'Baghdad' => 'Asia/Baghdad', + 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait', + 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', + 'East Africa, Nairobi' => 'Africa/Nairobi', + 'Tehran' => 'Asia/Tehran', + 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess + 'Baku, Tbilisi, Yerevan' => 'Asia/Baku', + 'Kabul' => 'Asia/Kabul', + 'Ekaterinburg' => 'Asia/Yekaterinburg', + 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi', + 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta', + 'Kathmandu, Nepal' => 'Asia/Kathmandu', + 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty', + 'Astana, Dhaka' => 'Asia/Dhaka', + 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo', + 'Rangoon' => 'Asia/Rangoon', + 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', + 'Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai', + 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk', + 'Kuala Lumpur, Singapore' => 'Asia/Singapore', + 'Perth, Western Australia' => 'Australia/Perth', + 'Taipei' => 'Asia/Taipei', + 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', + 'Seoul, Korea Standard time' => 'Asia/Seoul', + 'Yakutsk' => 'Asia/Yakutsk', + 'Adelaide, Central Australia' => 'Australia/Adelaide', + 'Darwin' => 'Australia/Darwin', + 'Brisbane, East Australia' => 'Australia/Brisbane', + 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney', + 'Guam, Port Moresby' => 'Pacific/Guam', + 'Hobart, Tasmania' => 'Australia/Hobart', + 'Vladivostok' => 'Asia/Vladivostok', + 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan', + 'Auckland, Wellington' => 'Pacific/Auckland', + 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji', + 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu', + 'Azores' => 'Atlantic/Azores', + 'Cape Verde Is.' => 'Atlantic/Cape_Verde', + 'Mid-Atlantic' => 'America/Noronha', + 'Brasilia' => 'America/Sao_Paulo', // Best guess + 'Buenos Aires' => 'America/Argentina/Buenos_Aires', + 'Greenland' => 'America/Godthab', + 'Newfoundland' => 'America/St_Johns', + 'Atlantic Time (Canada)' => 'America/Halifax', + 'Caracas, La Paz' => 'America/Caracas', + 'Santiago' => 'America/Santiago', + 'Bogota, Lima, Quito' => 'America/Bogota', + 'Eastern Time (US & Canada)' => 'America/New_York', + 'Indiana (East)' => 'America/Indiana/Indianapolis', + 'Central America' => 'America/Guatemala', + 'Central Time (US & Canada)' => 'America/Chicago', + 'Mexico City, Tegucigalpa' => 'America/Mexico_City', + 'Saskatchewan' => 'America/Edmonton', + 'Arizona' => 'America/Phoenix', + 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess + 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess + 'Alaska' => 'America/Anchorage', + 'Hawaii' => 'Pacific/Honolulu', + 'Midway Island, Samoa' => 'Pacific/Midway', + 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', + + ); + + public static $microsoftExchangeMap = array( + 0 => 'UTC', + 31 => 'Africa/Casablanca', + 2 => 'Europe/Lisbon', + 1 => 'Europe/London', + 4 => 'Europe/Berlin', + 6 => 'Europe/Prague', + 3 => 'Europe/Paris', + 69 => 'Africa/Luanda', // This was a best guess + 7 => 'Europe/Athens', + 5 => 'Europe/Bucharest', + 49 => 'Africa/Cairo', + 50 => 'Africa/Harare', + 59 => 'Europe/Helsinki', + 27 => 'Asia/Jerusalem', + 26 => 'Asia/Baghdad', + 74 => 'Asia/Kuwait', + 51 => 'Europe/Moscow', + 56 => 'Africa/Nairobi', + 25 => 'Asia/Tehran', + 24 => 'Asia/Muscat', // Best guess + 54 => 'Asia/Baku', + 48 => 'Asia/Kabul', + 58 => 'Asia/Yekaterinburg', + 47 => 'Asia/Karachi', + 23 => 'Asia/Calcutta', + 62 => 'Asia/Kathmandu', + 46 => 'Asia/Almaty', + 71 => 'Asia/Dhaka', + 66 => 'Asia/Colombo', + 61 => 'Asia/Rangoon', + 22 => 'Asia/Bangkok', + 64 => 'Asia/Krasnoyarsk', + 45 => 'Asia/Shanghai', + 63 => 'Asia/Irkutsk', + 21 => 'Asia/Singapore', + 73 => 'Australia/Perth', + 75 => 'Asia/Taipei', + 20 => 'Asia/Tokyo', + 72 => 'Asia/Seoul', + 70 => 'Asia/Yakutsk', + 19 => 'Australia/Adelaide', + 44 => 'Australia/Darwin', + 18 => 'Australia/Brisbane', + 76 => 'Australia/Sydney', + 43 => 'Pacific/Guam', + 42 => 'Australia/Hobart', + 68 => 'Asia/Vladivostok', + 41 => 'Asia/Magadan', + 17 => 'Pacific/Auckland', + 40 => 'Pacific/Fiji', + 67 => 'Pacific/Tongatapu', + 29 => 'Atlantic/Azores', + 53 => 'Atlantic/Cape_Verde', + 30 => 'America/Noronha', + 8 => 'America/Sao_Paulo', // Best guess + 32 => 'America/Argentina/Buenos_Aires', + 69 => 'America/Godthab', + 28 => 'America/St_Johns', + 9 => 'America/Halifax', + 33 => 'America/Caracas', + 65 => 'America/Santiago', + 35 => 'America/Bogota', + 10 => 'America/New_York', + 34 => 'America/Indiana/Indianapolis', + 55 => 'America/Guatemala', + 11 => 'America/Chicago', + 37 => 'America/Mexico_City', + 36 => 'America/Edmonton', + 38 => 'America/Phoenix', + 12 => 'America/Denver', // Best guess + 13 => 'America/Los_Angeles', // Best guess + 14 => 'America/Anchorage', + 15 => 'Pacific/Honolulu', + 16 => 'Pacific/Midway', + 39 => 'Pacific/Kwajalein', + ); + + /** + * This method will try to find out the correct timezone for an iCalendar + * date-time value. + * + * You must pass the contents of the TZID parameter, as well as the full + * calendar. + * + * If the lookup fails, this method will return UTC. + * + * @param string $tzid + * @param Sabre_VObject_Component $vcalendar + * @return DateTimeZone + */ + static public function getTimeZone($tzid, Sabre_VObject_Component $vcalendar = null) { + + // First we will just see if the tzid is a support timezone identifier. + try { + return new DateTimeZone($tzid); + } catch (\Exception $e) { + } + + // Next, we check if the tzid is somewhere in our tzid map. + if (isset(self::$map[$tzid])) { + return new DateTimeZone(self::$map[$tzid]); + } + + if ($vcalendar) { + + // If that didn't work, we will scan VTIMEZONE objects + foreach($vcalendar->select('VTIMEZONE') as $vtimezone) { + + if ((string)$vtimezone->TZID === $tzid) { + + // Some clients add 'X-LIC-LOCATION' with the olson name. + if (isset($vtimezone->{'X-LIC-LOCATION'})) { + try { + return new DateTimeZone($vtimezone->{'X-LIC-LOCATION'}); + } catch (\Exception $e) { + } + + } + // Microsoft may add a magic number, which we also have an + // answer for. + if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { + if (isset(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value])) { + return new DateTimeZone(self::$microsoftExchangeMap[(int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->value]); + } + } + } + + } + + } + + // If we got all the way here, we default to UTC. + return new DateTimeZone(date_default_timezone_get()); + + + } + + +} diff --git a/dav/SabreDAV/lib/Sabre/VObject/Version.php b/dav/SabreDAV/lib/Sabre/VObject/Version.php index 2617c7b12..9ee03d871 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/Version.php +++ b/dav/SabreDAV/lib/Sabre/VObject/Version.php @@ -14,7 +14,7 @@ class Sabre_VObject_Version { /** * Full version number */ - const VERSION = '1.3.3'; + const VERSION = '1.3.4'; /** * Stability : alpha, beta, stable diff --git a/dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php b/dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php deleted file mode 100644 index 5e1cc5d47..000000000 --- a/dav/SabreDAV/lib/Sabre/VObject/WindowsTimezoneMap.php +++ /dev/null @@ -1,128 +0,0 @@ -'Australia/Darwin', - 'AUS Eastern Standard Time'=>'Australia/Sydney', - 'Afghanistan Standard Time'=>'Asia/Kabul', - 'Alaskan Standard Time'=>'America/Anchorage', - 'Arab Standard Time'=>'Asia/Riyadh', - 'Arabian Standard Time'=>'Asia/Dubai', - 'Arabic Standard Time'=>'Asia/Baghdad', - 'Argentina Standard Time'=>'America/Buenos_Aires', - 'Armenian Standard Time'=>'Asia/Yerevan', - 'Atlantic Standard Time'=>'America/Halifax', - 'Azerbaijan Standard Time'=>'Asia/Baku', - 'Azores Standard Time'=>'Atlantic/Azores', - 'Bangladesh Standard Time'=>'Asia/Dhaka', - 'Canada Central Standard Time'=>'America/Regina', - 'Cape Verde Standard Time'=>'Atlantic/Cape_Verde', - 'Caucasus Standard Time'=>'Asia/Yerevan', - 'Cen. Australia Standard Time'=>'Australia/Adelaide', - 'Central America Standard Time'=>'America/Guatemala', - 'Central Asia Standard Time'=>'Asia/Almaty', - 'Central Brazilian Standard Time'=>'America/Cuiaba', - 'Central Europe Standard Time'=>'Europe/Budapest', - 'Central European Standard Time'=>'Europe/Warsaw', - 'Central Pacific Standard Time'=>'Pacific/Guadalcanal', - 'Central Standard Time'=>'America/Chicago', - 'Central Standard Time (Mexico)'=>'America/Mexico_City', - 'China Standard Time'=>'Asia/Shanghai', - 'Dateline Standard Time'=>'Etc/GMT+12', - 'E. Africa Standard Time'=>'Africa/Nairobi', - 'E. Australia Standard Time'=>'Australia/Brisbane', - 'E. Europe Standard Time'=>'Europe/Minsk', - 'E. South America Standard Time'=>'America/Sao_Paulo', - 'Eastern Standard Time'=>'America/New_York', - 'Egypt Standard Time'=>'Africa/Cairo', - 'Ekaterinburg Standard Time'=>'Asia/Yekaterinburg', - 'FLE Standard Time'=>'Europe/Kiev', - 'Fiji Standard Time'=>'Pacific/Fiji', - 'GMT Standard Time'=>'Europe/London', - 'GTB Standard Time'=>'Europe/Istanbul', - 'Georgian Standard Time'=>'Asia/Tbilisi', - 'Greenland Standard Time'=>'America/Godthab', - 'Greenwich Standard Time'=>'Atlantic/Reykjavik', - 'Hawaiian Standard Time'=>'Pacific/Honolulu', - 'India Standard Time'=>'Asia/Calcutta', - 'Iran Standard Time'=>'Asia/Tehran', - 'Israel Standard Time'=>'Asia/Jerusalem', - 'Jordan Standard Time'=>'Asia/Amman', - 'Kamchatka Standard Time'=>'Asia/Kamchatka', - 'Korea Standard Time'=>'Asia/Seoul', - 'Magadan Standard Time'=>'Asia/Magadan', - 'Mauritius Standard Time'=>'Indian/Mauritius', - 'Mexico Standard Time'=>'America/Mexico_City', - 'Mexico Standard Time 2'=>'America/Chihuahua', - 'Mid-Atlantic Standard Time'=>'Etc/GMT+2', - 'Middle East Standard Time'=>'Asia/Beirut', - 'Montevideo Standard Time'=>'America/Montevideo', - 'Morocco Standard Time'=>'Africa/Casablanca', - 'Mountain Standard Time'=>'America/Denver', - 'Mountain Standard Time (Mexico)'=>'America/Chihuahua', - 'Myanmar Standard Time'=>'Asia/Rangoon', - 'N. Central Asia Standard Time'=>'Asia/Novosibirsk', - 'Namibia Standard Time'=>'Africa/Windhoek', - 'Nepal Standard Time'=>'Asia/Katmandu', - 'New Zealand Standard Time'=>'Pacific/Auckland', - 'Newfoundland Standard Time'=>'America/St_Johns', - 'North Asia East Standard Time'=>'Asia/Irkutsk', - 'North Asia Standard Time'=>'Asia/Krasnoyarsk', - 'Pacific SA Standard Time'=>'America/Santiago', - 'Pacific Standard Time'=>'America/Los_Angeles', - 'Pacific Standard Time (Mexico)'=>'America/Santa_Isabel', - 'Pakistan Standard Time'=>'Asia/Karachi', - 'Paraguay Standard Time'=>'America/Asuncion', - 'Romance Standard Time'=>'Europe/Paris', - 'Russian Standard Time'=>'Europe/Moscow', - 'SA Eastern Standard Time'=>'America/Cayenne', - 'SA Pacific Standard Time'=>'America/Bogota', - 'SA Western Standard Time'=>'America/La_Paz', - 'SE Asia Standard Time'=>'Asia/Bangkok', - 'Samoa Standard Time'=>'Pacific/Apia', - 'Singapore Standard Time'=>'Asia/Singapore', - 'South Africa Standard Time'=>'Africa/Johannesburg', - 'Sri Lanka Standard Time'=>'Asia/Colombo', - 'Syria Standard Time'=>'Asia/Damascus', - 'Taipei Standard Time'=>'Asia/Taipei', - 'Tasmania Standard Time'=>'Australia/Hobart', - 'Tokyo Standard Time'=>'Asia/Tokyo', - 'Tonga Standard Time'=>'Pacific/Tongatapu', - 'US Eastern Standard Time'=>'America/Indianapolis', - 'US Mountain Standard Time'=>'America/Phoenix', - 'UTC'=>'Etc/GMT', - 'UTC+12'=>'Etc/GMT-12', - 'UTC-02'=>'Etc/GMT+2', - 'UTC-11'=>'Etc/GMT+11', - 'Ulaanbaatar Standard Time'=>'Asia/Ulaanbaatar', - 'Venezuela Standard Time'=>'America/Caracas', - 'Vladivostok Standard Time'=>'Asia/Vladivostok', - 'W. Australia Standard Time'=>'Australia/Perth', - 'W. Central Africa Standard Time'=>'Africa/Lagos', - 'W. Europe Standard Time'=>'Europe/Berlin', - 'West Asia Standard Time'=>'Asia/Tashkent', - 'West Pacific Standard Time'=>'Pacific/Port_Moresby', - 'Yakutsk Standard Time'=>'Asia/Yakutsk', - ); - - static public function lookup($tzid) { - return isset(self::$map[$tzid]) ? self::$map[$tzid] : null; - } -} diff --git a/dav/SabreDAV/lib/Sabre/VObject/includes.php b/dav/SabreDAV/lib/Sabre/VObject/includes.php index e7f57a06f..76497d6ff 100644 --- a/dav/SabreDAV/lib/Sabre/VObject/includes.php +++ b/dav/SabreDAV/lib/Sabre/VObject/includes.php @@ -24,8 +24,8 @@ include __DIR__ . '/Parameter.php'; include __DIR__ . '/ParseException.php'; include __DIR__ . '/Reader.php'; include __DIR__ . '/RecurrenceIterator.php'; +include __DIR__ . '/TimeZoneUtil.php'; include __DIR__ . '/Version.php'; -include __DIR__ . '/WindowsTimezoneMap.php'; include __DIR__ . '/Element.php'; include __DIR__ . '/Property.php'; include __DIR__ . '/Component.php'; diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php b/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php index 511dd9fd2..d9a5a7a0f 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Backend/Mock.php @@ -1,14 +1,16 @@ calendars = $calendars; $this->calendarData = $calendarData; + $this->notifications = $notifications; } @@ -58,7 +60,15 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract { */ function createCalendar($principalUri,$calendarUri,array $properties) { - throw new Exception('Not implemented'); + $id = Sabre_DAV_UUIDUtil::getUUID(); + $this->calendars[] = array_merge(array( + 'id' => $id, + 'principaluri' => $principalUri, + 'uri' => $calendarUri, + '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')), + ), $properties); + + return $id; } @@ -112,7 +122,11 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract { */ public function deleteCalendar($calendarId) { - throw new Exception('Not implemented'); + foreach($this->calendars as $k=>$calendar) { + if ($calendar['id'] === $calendarId) { + unset($this->calendars[$k]); + } + } } @@ -227,4 +241,37 @@ class Sabre_CalDAV_Backend_Mock extends Sabre_CalDAV_Backend_Abstract { } + /** + * Returns a list of notifications for a given principal url. + * + * The returned array should only consist of implementations of + * Sabre_CalDAV_Notifications_INotificationType. + * + * @param string $principalUri + * @return array + */ + public function getNotificationsForPrincipal($principalUri) { + + if (isset($this->notifications[$principalUri])) { + return $this->notifications[$principalUri]; + } + return array(); + + } + + /** + * This deletes a specific notifcation. + * + * This may be called by a client once it deems a notification handled. + * + * @param string $principalUri + * @param Sabre_CalDAV_Notifications_INotificationType $notification + * @return void + */ + public function deleteNotification($principalUri, Sabre_CalDAV_Notifications_INotificationType $notification) { + + throw new Sabre_DAV_Exception_NotImplemented('This doesn\'t work!'); + + } + } diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php index b75c398ab..18c8330db 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php @@ -343,6 +343,14 @@ DURATION:PT1H RRULE:FREQ=YEARLY END:VEVENT END:VCALENDAR +yow; + $blob33 = << null, ); - // Time-range with RRULE - + $filter38 = array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new DateTime('2012-07-01 00:00:00', new DateTimeZone('UTC')), + 'end' => new DateTime('2012-08-01 00:00:00', new DateTimeZone('UTC')), + ) + ); return array( // Component check @@ -741,6 +757,9 @@ yow; array($blob31, $filter20, 1), array($blob32, $filter20, 0), + // Bug reported on mailing list, related to all-day events. + array($blob33, $filter38, 1), + ); } diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php index 137b7d3ca..d23b81231 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/ICSExportPluginTest.php @@ -55,6 +55,56 @@ class Sabre_CalDAV_ICSExportPluginTest extends PHPUnit_Framework_TestCase { $this->assertEquals(1,count($obj->VERSION)); $this->assertEquals(1,count($obj->CALSCALE)); $this->assertEquals(1,count($obj->PRODID)); + $this->assertTrue(strpos((string)$obj->PRODID, Sabre_DAV_Version::VERSION)!==false); + $this->assertEquals(1,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + + } + function testBeforeMethodNoVersion() { + + if (!SABRE_HASSQLITE) $this->markTestSkipped('SQLite driver is not available'); + $cbackend = Sabre_CalDAV_TestUtil::getBackend(); + $pbackend = new Sabre_DAVACL_MockPrincipalBackend(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Sabre_CalDAV_Calendar($pbackend,$cbackend,$props), + ); + + $p = new Sabre_CalDAV_ICSExportPlugin(); + + $s = new Sabre_DAV_Server($tree); + + $s->addPlugin($p); + $s->addPlugin(new Sabre_CalDAV_Plugin()); + + $h = new Sabre_HTTP_Request(array( + 'QUERY_STRING' => 'export', + )); + + $s->httpRequest = $h; + $s->httpResponse = new Sabre_HTTP_ResponseMock(); + + Sabre_DAV_Server::$exposeVersion = false; + $this->assertFalse($p->beforeMethod('GET','UUID-123467?export')); + Sabre_DAV_Server::$exposeVersion = true; + + $this->assertEquals('HTTP/1.1 200 OK',$s->httpResponse->status); + $this->assertEquals(array( + 'Content-Type' => 'text/calendar', + ), $s->httpResponse->headers); + + $obj = Sabre_VObject_Reader::read($s->httpResponse->body); + + $this->assertEquals(5,count($obj->children())); + $this->assertEquals(1,count($obj->VERSION)); + $this->assertEquals(1,count($obj->CALSCALE)); + $this->assertEquals(1,count($obj->PRODID)); + $this->assertFalse(strpos((string)$obj->PRODID, Sabre_DAV_Version::VERSION)!==false); $this->assertEquals(1,count($obj->VTIMEZONE)); $this->assertEquals(1,count($obj->VEVENT)); diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php b/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php new file mode 100644 index 000000000..f4f875355 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Issue220Test.php @@ -0,0 +1,96 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120601T180000 +SUMMARY:Brot backen +RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO +TRANSP:OPAQUE +DURATION:PT20M +LAST-MODIFIED:20120601T064634Z +CREATED:20120601T064634Z +DTSTAMP:20120601T064634Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT5M +ACTION:DISPLAY +DESCRIPTION:Default Event Notification +X-WR-ALARMUID:cd952c1b-b3d6-41fb-b0a6-ec3a1a5bdd58 +END:VALARM +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120606T180000 +SUMMARY:Brot backen +TRANSP:OPAQUE +STATUS:CANCELLED +DTEND;TZID=Europe/Berlin:20120606T182000 +LAST-MODIFIED:20120605T094310Z +SEQUENCE:1 +RECURRENCE-ID:20120606T160000Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue220() { + + $request = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'PHPUnit_Framework_Error_Warning'), 'Error Warning occurred: ' . $response->body); + $this->assertFalse(strpos($response->body, 'Invalid argument supplied for foreach()'), 'Invalid argument supplied for foreach(): ' . $response->body); + + $this->assertEquals('HTTP/1.1 207 Multi-Status', $response->status); + } +} diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php new file mode 100644 index 000000000..1d396d6da --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/CollectionTest.php @@ -0,0 +1,27 @@ + array( + $systemStatus + ) + )); + + + $col = new Sabre_CalDAV_Notifications_Collection($caldavBackend, $principalUri); + $this->assertEquals('notifications', $col->getName()); + + $this->assertEquals(array( + new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus) + ), $col->getChildren()); + + } + +} diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php new file mode 100644 index 000000000..dba636e98 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/NodeTest.php @@ -0,0 +1,23 @@ + array( + $systemStatus + ) + )); + + + $node = new Sabre_CalDAV_Notifications_Node($caldavBackend, $systemStatus); + $this->assertEquals($systemStatus->getId(), $node->getName()); + + } + +} diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php new file mode 100644 index 000000000..ddaf5ce49 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/CalDAV/Notifications/Notification/SystemStatusTest.php @@ -0,0 +1,55 @@ +assertEquals('foo', $notification->getId()); + + + $dom = new DOMDocument('1.0','UTF-8'); + $elem = $dom->createElement('cs:root'); + $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER); + $dom->appendChild($elem); + $notification->serialize(new Sabre_DAV_Server(), $elem); + $this->assertEquals($expected1, $dom->saveXML()); + + $dom = new DOMDocument('1.0','UTF-8'); + $elem = $dom->createElement('cs:root'); + $elem->setAttribute('xmlns:cs',Sabre_CalDAV_Plugin::NS_CALENDARSERVER); + $dom->appendChild($elem); + $notification->serializeBody(new Sabre_DAV_Server(), $elem); + $this->assertEquals($expected2, $dom->saveXML()); + + + } + + function dataProvider() { + + return array( + + array( + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo'), + '' . "\n" . '' . "\n", + '' . "\n" . '' . "\n", + ), + + array( + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_MEDIUM,'bar'), + '' . "\n" . '' . "\n", + '' . "\n" . 'bar' . "\n", + ), + + array( + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo',Sabre_CalDAV_Notifications_Notification_SystemStatus::TYPE_LOW,null,'http://example.org/'), + '' . "\n" . '' . "\n", + '' . "\n" . 'http://example.org/' . "\n", + ) + ); + + } + +} diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php index 4d45c8ae0..6c618cac0 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/OutboxPostTest.php @@ -191,7 +191,17 @@ class Sabre_CalDAV_OutboxPostTest extends Sabre_DAVServerTest { $req->setBody(implode("\r\n",$body)); - $this->assertHTTPStatus(501, $req); + + $response = $this->request($req); + $this->assertEquals('HTTP/1.1 200 OK', $response->status); + $this->assertEquals(array( + 'Content-Type' => 'application/xml', + ), $response->headers); + + // Lazily checking the body for a few expected values. + $this->assertTrue(strpos($response->body, '5.2;')!==false); + $this->assertTrue(strpos($response->body,'user2@example.org')!==false); + } @@ -218,7 +228,66 @@ class Sabre_CalDAV_OutboxPostTest extends Sabre_DAVServerTest { $handler = new Sabre_CalDAV_Schedule_IMip_Mock('server@example.org'); $this->caldavPlugin->setIMIPhandler($handler); - $this->assertHTTPStatus(200, $req); + + $response = $this->request($req); + $this->assertEquals('HTTP/1.1 200 OK', $response->status); + $this->assertEquals(array( + 'Content-Type' => 'application/xml', + ), $response->headers); + + // Lazily checking the body for a few expected values. + $this->assertTrue(strpos($response->body, '2.0;')!==false); + $this->assertTrue(strpos($response->body,'user2@example.org')!==false); + + $this->assertEquals(array( + array( + 'to' => 'user2@example.org', + 'subject' => 'Invitation for: An invitation', + 'body' => implode("\r\n", $body) . "\r\n", + 'headers' => array( + 'Reply-To: user1.sabredav@sabredav.org', + 'From: server@example.org', + 'Content-Type: text/calendar; method=REQUEST; charset=utf-8', + 'X-Sabre-Version: ' . Sabre_DAV_Version::VERSION . '-' . Sabre_DAV_Version::STABILITY, + ), + ) + ), $handler->getSentEmails()); + + } + + function testSuccessRequestUpperCased() { + + $req = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'MAILTO:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'MAILTO:user2@example.org', + )); + + $body = array( + 'BEGIN:VCALENDAR', + 'METHOD:REQUEST', + 'BEGIN:VEVENT', + 'SUMMARY:An invitation', + 'END:VEVENT', + 'END:VCALENDAR', + ); + + $req->setBody(implode("\r\n",$body)); + + $handler = new Sabre_CalDAV_Schedule_IMip_Mock('server@example.org'); + + $this->caldavPlugin->setIMIPhandler($handler); + + $response = $this->request($req); + $this->assertEquals('HTTP/1.1 200 OK', $response->status); + $this->assertEquals(array( + 'Content-Type' => 'application/xml', + ), $response->headers); + + // Lazily checking the body for a few expected values. + $this->assertTrue(strpos($response->body, '2.0;')!==false); + $this->assertTrue(strpos($response->body,'user2@example.org')!==false); $this->assertEquals(array( array( diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php index eb097c9e8..6ff57285d 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/PluginTest.php @@ -23,8 +23,34 @@ class Sabre_CalDAV_PluginTest extends PHPUnit_Framework_TestCase { function setup() { - if (!SABRE_HASSQLITE) $this->markTestSkipped('No PDO SQLite support'); - $this->caldavBackend = Sabre_CalDAV_TestUtil::getBackend(); + $this->caldavBackend = new Sabre_CalDAV_Backend_Mock(array( + array( + 'id' => 1, + 'uri' => 'UUID-123467', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')), + ), + array( + 'id' => 2, + 'uri' => 'UUID-123468', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar2', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array('VEVENT','VTODO')), + ) + ), array( + 1 => array( + 'UUID-2345' => array( + 'calendardata' => Sabre_CalDAV_TestUtil::getTestCalendarData(), + ) + ) + )); $principalBackend = new Sabre_DAVACL_MockPrincipalBackend(); $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-read',array('principals/user1')); $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-write',array('principals/user1')); @@ -398,6 +424,7 @@ END:VCALENDAR'; '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}calendar-proxy-read-for', '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}calendar-proxy-write-for', + '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notification-URL', )); $this->assertArrayHasKey(0,$props); @@ -414,6 +441,12 @@ END:VCALENDAR'; $this->assertTrue($prop instanceof Sabre_DAV_Property_Href); $this->assertEquals('calendars/user1/outbox',$prop->getHref()); + $this->assertArrayHasKey('{'.Sabre_CalDAV_Plugin::NS_CALENDARSERVER .'}notification-URL',$props[0][200]); + $prop = $props[0][200]['{'.Sabre_CalDAV_Plugin::NS_CALENDARSERVER .'}notification-URL']; + $this->assertTrue($prop instanceof Sabre_DAV_Property_Href); + $this->assertEquals('calendars/user1/notifications/',$prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',$props[0][200]); $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; $this->assertTrue($prop instanceof Sabre_DAV_Property_HrefList); @@ -429,6 +462,7 @@ END:VCALENDAR'; $this->assertInstanceOf('Sabre_DAV_Property_HrefList', $prop); $this->assertEquals(array('principals/admin'), $prop->getHrefs()); + } function testSupportedReportSetPropertyNonCalendar() { @@ -502,9 +536,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -564,9 +598,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -629,9 +663,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -688,9 +722,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -755,7 +789,7 @@ END:VCALENDAR'; '' . ' ' . ' ' . - ' ' . + ' ' . ' ' . '' . '' . @@ -777,9 +811,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -837,9 +871,9 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); - $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); + $xml = simplexml_load_string($this->response->body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:caldav'); $check = array( @@ -991,4 +1025,60 @@ END:VCALENDAR'; $this->assertEquals('HTTP/1.1 400 Bad request',$this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); } + + function testNotificationProperties() { + + $request = array( + '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype', + ); + $result = array(); + $notification = new Sabre_CalDAV_Notifications_Node( + $this->caldavBackend, + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo') + ); + $this->plugin->beforeGetProperties('foo', $notification, $request, $result); + + $this->assertEquals( + array( + 200 => array( + '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}notificationtype' => $notification->getNotificationType() + ) + ), $result); + + } + + function testNotificationGet() { + + $notification = new Sabre_CalDAV_Notifications_Node( + $this->caldavBackend, + new Sabre_CalDAV_Notifications_Notification_SystemStatus('foo') + ); + + $server = new Sabre_DAV_Server(array($notification)); + $caldav = new Sabre_CalDAV_Plugin(); + + $httpResponse = new Sabre_HTTP_ResponseMock(); + $server->httpResponse = $httpResponse; + + $server->addPlugin($caldav); + + $caldav->beforeMethod('GET','foo'); + + $this->assertEquals('HTTP/1.1 200 OK', $httpResponse->status); + $this->assertEquals(array( + 'Content-Type' => 'application/xml', + ), $httpResponse->headers); + + $expected = +' + + + +'; + + $this->assertEquals($expected, $httpResponse->body); + + + } + } diff --git a/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php b/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php index f2a5e5563..42e8aebb2 100644 --- a/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php +++ b/dav/SabreDAV/tests/Sabre/CalDAV/ValidateICalTest.php @@ -22,6 +22,13 @@ class Sabre_CalDAV_ValidateICalTest extends PHPUnit_Framework_TestCase { 'id' => 'calendar1', 'principaluri' => 'principals/admin', 'uri' => 'calendar1', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet( array('VEVENT','VTODO','VJOURNAL') ), + ), + array( + 'id' => 'calendar2', + 'principaluri' => 'principals/admin', + 'uri' => 'calendar2', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet( array('VTODO','VJOURNAL') ), ) ); @@ -207,4 +214,33 @@ class Sabre_CalDAV_ValidateICalTest extends PHPUnit_Framework_TestCase { $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1','blabla.ics')); } + + function testCreateFileInvalidComponent() { + + $request = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals('HTTP/1.1 403 Forbidden', $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFileInvalidComponent() { + + $this->calBackend->createCalendarObject('calendar2','blabla.ics','foo'); + $request = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals('HTTP/1.1 403 Forbidden', $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } } diff --git a/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php b/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php index 80a5d081c..a4c8e8015 100644 --- a/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php +++ b/dav/SabreDAV/tests/Sabre/CardDAV/ValidateVCardTest.php @@ -65,20 +65,35 @@ class Sabre_CardDAV_ValidateVCardTest extends PHPUnit_Framework_TestCase { 'REQUEST_METHOD' => 'PUT', 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', )); - $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n"); + $request->setBody("BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n"); $response = $this->request($request); $this->assertEquals('HTTP/1.1 201 Created', $response->status, 'Incorrect status returned! Full response body: ' . $response->body); $expected = array( 'uri' => 'blabla.vcf', - 'carddata' => "BEGIN:VCARD\r\nEND:VCARD\r\n", + 'carddata' => "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n", ); $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1','blabla.vcf')); } + function testCreateFileNoUID() { + + $request = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n"); + + $response = $this->request($request); + + $this->assertEquals('HTTP/1.1 400 Bad request', $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileVCalendar() { $request = new Sabre_HTTP_Request(array( @@ -114,7 +129,7 @@ class Sabre_CardDAV_ValidateVCardTest extends PHPUnit_Framework_TestCase { 'REQUEST_METHOD' => 'PUT', 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', )); - $body = "BEGIN:VCARD\r\nEND:VCARD\r\n"; + $body = "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n"; $request->setBody($body); $response = $this->request($request); diff --git a/dav/SabreDAV/tests/Sabre/DAV/Locks/PluginTest.php b/dav/SabreDAV/tests/Sabre/DAV/Locks/PluginTest.php index 758a492bf..0ce0eb3a0 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Locks/PluginTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Locks/PluginTest.php @@ -92,9 +92,9 @@ class Sabre_DAV_Locks_PluginTest extends Sabre_DAV_AbstractServer { $this->assertEquals('HTTP/1.1 200 OK',$this->response->status,'Got an incorrect status back. Response body: ' . $this->response->body); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $elements = array( '/d:prop', diff --git a/dav/SabreDAV/tests/Sabre/DAV/Property/HrefListTest.php b/dav/SabreDAV/tests/Sabre/DAV/Property/HrefListTest.php index ef8c1a093..c420f22df 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Property/HrefListTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Property/HrefListTest.php @@ -60,7 +60,7 @@ class Sabre_DAV_Property_HrefListTest extends PHPUnit_Framework_TestCase { function testUnserialize() { $xml = ' -/bla/foo/bla/bar +/bla/foo/bla/bar '; $dom = new DOMDocument(); @@ -74,7 +74,7 @@ class Sabre_DAV_Property_HrefListTest extends PHPUnit_Framework_TestCase { function testUnserializeIncompatible() { $xml = ' -/bla/foo +/bla/foo '; $dom = new DOMDocument(); diff --git a/dav/SabreDAV/tests/Sabre/DAV/Property/HrefTest.php b/dav/SabreDAV/tests/Sabre/DAV/Property/HrefTest.php index 8dcec751e..12f0a5943 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Property/HrefTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Property/HrefTest.php @@ -60,7 +60,7 @@ class Sabre_DAV_Property_HrefTest extends PHPUnit_Framework_TestCase { function testUnserialize() { $xml = ' -/bla/path +/bla/path '; $dom = new DOMDocument(); @@ -74,7 +74,7 @@ class Sabre_DAV_Property_HrefTest extends PHPUnit_Framework_TestCase { function testUnserializeIncompatible() { $xml = ' -/bla/path +/bla/path '; $dom = new DOMDocument(); diff --git a/dav/SabreDAV/tests/Sabre/DAV/Property/SupportedReportSetTest.php b/dav/SabreDAV/tests/Sabre/DAV/Property/SupportedReportSetTest.php index 27d5825e6..086d59a9c 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Property/SupportedReportSetTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Property/SupportedReportSetTest.php @@ -37,9 +37,9 @@ class Sabre_DAV_Property_SupportedReportSetTest extends Sabre_DAV_AbstractServer $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We expected a multi-status response. Full response body: ' . $this->response->body); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); $this->assertEquals(1,count($data),'We expected 1 \'d:prop\' element'); @@ -74,9 +74,9 @@ class Sabre_DAV_Property_SupportedReportSetTest extends Sabre_DAV_AbstractServer $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We expected a multi-status response. Full response body: ' . $this->response->body); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('x','http://www.rooftopsolutions.nl/testnamespace'); $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); diff --git a/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php b/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php index a5e88f3a8..7208fed2b 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/ServerEventsTest.php @@ -6,6 +6,8 @@ class Sabre_DAV_ServerEventsTest extends Sabre_DAV_AbstractServer { private $tempPath; + private $exception; + function testAfterBind() { $this->server->subscribeEvent('afterBind',array($this,'afterBindHandler')); @@ -47,5 +49,25 @@ class Sabre_DAV_ServerEventsTest extends Sabre_DAV_AbstractServer { } + function testException() { + + $this->server->subscribeEvent('exception', array($this, 'exceptionHandler')); + + $req = new Sabre_HTTP_Request(array( + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/not/exisitng', + )); + $this->server->httpRequest = $req; + $this->server->exec(); + + $this->assertInstanceOf('Sabre_DAV_Exception_NotFound', $this->exception); + + } + + function exceptionHandler(Exception $exception) { + + $this->exception = $exception; + + } } diff --git a/dav/SabreDAV/tests/Sabre/DAV/ServerMKCOLTest.php b/dav/SabreDAV/tests/Sabre/DAV/ServerMKCOLTest.php index 0a181dd70..a9abc1814 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/ServerMKCOLTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/ServerMKCOLTest.php @@ -192,7 +192,6 @@ class Sabre_DAV_ServerMKCOLTest extends Sabre_DAV_AbstractServer { } - /** * @depends testMKCOLIncorrectResourceType2 */ @@ -224,6 +223,38 @@ class Sabre_DAV_ServerMKCOLTest extends Sabre_DAV_AbstractServer { } + /** + * @depends testMKCOLIncorrectResourceType2 + */ + function testMKCOLWhiteSpaceResourceType() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = new Sabre_HTTP_Request($serverVars); + $request->setBody(' + + + + + + + + +'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'Content-Length' => '0', + ),$this->response->headers); + + $this->assertEquals('HTTP/1.1 201 Created',$this->response->status,'Wrong statuscode received. Full response body: ' .$this->response->body); + + } /** * @depends testMKCOLIncorrectResourceType2 diff --git a/dav/SabreDAV/tests/Sabre/DAV/ServerPropsTest.php b/dav/SabreDAV/tests/Sabre/DAV/ServerPropsTest.php index 484b038d3..63a204cf9 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/ServerPropsTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/ServerPropsTest.php @@ -58,9 +58,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { $this->response->headers ); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); $this->assertEquals('/',(string)$data,'href element should have been /'); @@ -81,9 +81,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { $this->sendRequest($xml); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry'); $this->assertEquals(2,count($data),'We expected two \'d:lockentry\' tags'); @@ -115,9 +115,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { $this->sendRequest($xml); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery'); $this->assertEquals(1,count($data),'We expected a \'d:lockdiscovery\' tag'); @@ -134,9 +134,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { '; $this->sendRequest($xml); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $pathTests = array( '/d:multistatus', '/d:multistatus/d:response', @@ -312,9 +312,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { $this->assertEquals('HTTP/1.1 207 Multi-Status',$this->response->status,'We got the wrong status. Full XML response: ' . $this->response->body); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('bla','http://www.rooftopsolutions.nl/testnamespace'); $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); @@ -345,9 +345,9 @@ class Sabre_DAV_ServerPropsTest extends Sabre_DAV_AbstractServer { $this->sendRequest($xml); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); $xml->registerXPathNamespace('bla','http://www.rooftopsolutions.nl/testnamespace'); $xpath='//bla:someprop'; diff --git a/dav/SabreDAV/tests/Sabre/DAV/TemporaryFileFilterTest.php b/dav/SabreDAV/tests/Sabre/DAV/TemporaryFileFilterTest.php index 4ac0b1ae0..acd64380f 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/TemporaryFileFilterTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/TemporaryFileFilterTest.php @@ -233,9 +233,9 @@ class Sabre_DAV_TemporaryFileFilterTest extends Sabre_DAV_AbstractServer { 'Content-Type' => 'application/xml; charset=utf-8', ),$this->response->headers); - $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"DAV:\"",$this->response->body); $xml = simplexml_load_string($body); - $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('d','DAV:'); list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); $this->assertEquals('/._testput.txt',(string)$data,'href element should have been /._testput.txt'); diff --git a/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php b/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php index cf310ec89..67d5bbeb1 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/Tree/FilesystemTest.php @@ -37,6 +37,8 @@ class Sabre_DAV_Tree_FilesystemTest extends PHPUnit_Framework_TestCase { $fs = new Sabre_DAV_Tree_Filesystem(SABRE_TEMPDIR); $node = $fs->getNodeForPath('dir'); $this->assertTrue($node instanceof Sabre_DAV_FS_Directory); + $this->assertEquals('dir', $node->getName()); + $this->assertInternalType('array', $node->getChildren()); } diff --git a/dav/SabreDAV/tests/Sabre/DAV/XMLUtilTest.php b/dav/SabreDAV/tests/Sabre/DAV/XMLUtilTest.php index d70fe9136..f7fcbb681 100644 --- a/dav/SabreDAV/tests/Sabre/DAV/XMLUtilTest.php +++ b/dav/SabreDAV/tests/Sabre/DAV/XMLUtilTest.php @@ -29,7 +29,7 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase { function testToClarkNotationDAVNamespace() { $dom = new DOMDocument(); - $dom->loadXML('Testdoc'); + $dom->loadXML('Testdoc'); $this->assertEquals( '{DAV:}test1', @@ -41,7 +41,7 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase { function testToClarkNotationNoElem() { $dom = new DOMDocument(); - $dom->loadXML('Testdoc'); + $dom->loadXML('Testdoc'); $this->assertNull( Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild->firstChild) @@ -49,59 +49,6 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase { } - function testConvertDAVNamespace() { - - $xml='blablabla'; - $this->assertEquals( - 'blablabla', - Sabre_DAV_XMLUtil::convertDAVNamespace($xml) - ); - - } - - function testConvertDAVNamespace2() { - - $xml='blablabla'; - $this->assertEquals( - 'blablabla', - Sabre_DAV_XMLUtil::convertDAVNamespace($xml) - ); - - } - - function testConvertDAVNamespace3() { - - $xml='blablabla'; - $this->assertEquals( - 'blablabla', - Sabre_DAV_XMLUtil::convertDAVNamespace($xml) - ); - - } - - function testConvertDAVNamespace4() { - - $xml='blablabla'; - $this->assertEquals( - 'blablabla', - Sabre_DAV_XMLUtil::convertDAVNamespace($xml) - ); - - } - - function testConvertDAVNamespaceMixedQuotes() { - - $xml=''; @@ -121,7 +68,6 @@ class Sabre_DAV_XMLUtilTest extends PHPUnit_Framework_TestCase { } /** - * @depends testConvertDAVNamespace * @expectedException Sabre_DAV_Exception_BadRequest */ function testLoadDOMDocumentInvalid() { diff --git a/dav/SabreDAV/tests/Sabre/HTTP/BasicAuthTest.php b/dav/SabreDAV/tests/Sabre/HTTP/BasicAuthTest.php index b5dd51445..1bebcf85e 100644 --- a/dav/SabreDAV/tests/Sabre/HTTP/BasicAuthTest.php +++ b/dav/SabreDAV/tests/Sabre/HTTP/BasicAuthTest.php @@ -60,6 +60,25 @@ class Sabre_HTTP_BasicAuthTest extends PHPUnit_Framework_TestCase { } + function testGetUserPassWithColon() { + + $server = array( + 'HTTP_AUTHORIZATION' => 'Basic ' . base64_encode('admin:1234:5678'), + ); + + $request = new Sabre_HTTP_Request($server); + $this->basicAuth->setHTTPRequest($request); + + $userPass = $this->basicAuth->getUserPass(); + + $this->assertEquals( + array('admin','1234:5678'), + $userPass, + 'We did not get the username and password we expected' + ); + + } + function testGetUserPassApacheEdgeCase() { $server = array( diff --git a/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php b/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php index a5a855f60..90579cb41 100644 --- a/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php +++ b/dav/SabreDAV/tests/Sabre/VObject/Component/VEventTest.php @@ -53,9 +53,14 @@ class Sabre_VObject_Component_VEventTest extends PHPUnit_Framework_TestCase { $vevent6->DTEND['VALUE'] = 'DATE'; $tests[] = array($vevent6, new DateTime('2011-01-01'), new DateTime('2012-01-01'), true); $tests[] = array($vevent6, new DateTime('2011-01-01'), new DateTime('2011-11-01'), false); - // Event with no end date should be treated as lasting the entire day. - $tests[] = array($vevent6, new DateTime('2011-12-25 16:00:00'), new DateTime('2011-12-25 17:00:00'), true); + // Added this test to ensure that recurrence rules with no DTEND also + // get checked for the entire day. + $vevent7 = clone $vevent; + $vevent7->DTSTART = '20120101'; + $vevent7->DTSTART['VALUE'] = 'DATE'; + $vevent7->RRULE = 'FREQ=MONTHLY'; + $tests[] = array($vevent7, new DateTime('2012-02-01 15:00:00'), new DateTime('2012-02-02'), true); return $tests; } diff --git a/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php b/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php index 71b33c079..42f836dbd 100644 --- a/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php +++ b/dav/SabreDAV/tests/Sabre/VObject/ComponentTest.php @@ -340,11 +340,14 @@ class Sabre_VObject_ComponentTest extends PHPUnit_Framework_TestCase { new Sabre_VObject_Component('VEVENT'), new Sabre_VObject_Component('VTODO') ); - $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $comp->serialize()); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str); } - function testSerializeOrder() { + function testSerializeOrderCompAndProp() { $comp = new Sabre_VObject_Component('VCALENDAR'); $comp->add(new Sabre_VObject_Component('VEVENT')); @@ -358,4 +361,30 @@ class Sabre_VObject_ComponentTest extends PHPUnit_Framework_TestCase { } + function testAnotherSerializeOrderProp() { + + $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'); + + $comp = new Sabre_VObject_Component('VCARD'); + $comp->__set('SOMEPROP','FOO'); + $comp->__set('ANOTHERPROP','FOO'); + $comp->__set('THIRDPROP','FOO'); + foreach ($prop4s as $prop4) { + $comp->add('PROP4', 'FOO '.$prop4); + } + $comp->__set('PROPNUMBERFIVE', 'FOO'); + $comp->__set('PROPNUMBERSIX', 'FOO'); + $comp->__set('PROPNUMBERSEVEN', 'FOO'); + $comp->__set('PROPNUMBEREIGHT', 'FOO'); + $comp->__set('PROPNUMBERNINE', 'FOO'); + $comp->__set('PROPNUMBERTEN', 'FOO'); + $comp->__set('VERSION','2.0'); + $comp->__set('UID', 'FOO'); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str); + + } + } diff --git a/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php b/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php index a004eb081..f5136be12 100644 --- a/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php +++ b/dav/SabreDAV/tests/Sabre/VObject/Issue154Test.php @@ -6,9 +6,9 @@ class Sabre_VObject_Issue154Test extends PHPUnit_Framework_TestCase { $vcard = new Sabre_VObject_Component('VCARD'); $vcard->VERSION = '3.0'; - $vcard->UID = 'foo-bar'; $vcard->PHOTO = base64_encode('random_stuff'); $vcard->PHOTO->add('BASE64',null); + $vcard->UID = 'foo-bar'; $result = $vcard->serialize(); $expected = array( diff --git a/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php b/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php index d1ef2da60..0bb42bb87 100644 --- a/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php +++ b/dav/SabreDAV/tests/Sabre/VObject/RecurrenceIteratorTest.php @@ -707,6 +707,51 @@ class Sabre_VObject_RecurrenceIteratorTest extends PHPUnit_Framework_TestCase { } + /** + * @depends testValues + */ + function testYearlyLeapYear() { + + $ev = new Sabre_VObject_Component('VEVENT'); + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=3'; + $dtStart = new Sabre_VObject_Property_DateTime('DTSTART'); + $dtStart->setDateTime(new DateTime('2012-02-29'),Sabre_VObject_Property_DateTime::UTC); + + $ev->add($dtStart); + + $vcal = Sabre_VObject_Component::create('VCALENDAR'); + $vcal->add($ev); + + $it = new Sabre_VObject_RecurrenceIterator($vcal,(string)$ev->uid); + + $this->assertEquals('yearly', $it->frequency); + $this->assertEquals(3, $it->count); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2012-02-29', $tz), + new DateTime('2016-02-29', $tz), + new DateTime('2020-02-29', $tz), + ), + $result + ); + + } + /** * @depends testValues */ diff --git a/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php b/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php new file mode 100644 index 000000000..be8cd49c3 --- /dev/null +++ b/dav/SabreDAV/tests/Sabre/VObject/TimeZoneUtilTest.php @@ -0,0 +1,153 @@ +assertEquals(new DateTimeZone('Europe/Sarajevo'), $tz); + + } + + function testUnknownExchangeId() { + + $vobj = <<assertEquals(new DateTimeZone(date_default_timezone_get()), $tz); + + } + + function testWindowsTimeZone() { + + $tz = Sabre_VObject_TimeZoneUtil::getTimeZone('Eastern Standard Time'); + $this->assertEquals(new DateTimeZone('America/New_York'), $tz); + + } + + function testFallBack() { + + $vobj = <<assertEquals(new DateTimeZone(date_default_timezone_get()), $tz); + + } + +} diff --git a/dav/calendar.friendica.fnk.php b/dav/calendar.friendica.fnk.php index af4a0175f..fff3447e4 100644 --- a/dav/calendar.friendica.fnk.php +++ b/dav/calendar.friendica.fnk.php @@ -1,9 +1,9 @@ get_baseurl()); +$a = get_app(); +$uri = parse_url($a->get_baseurl()); $path = "/"; -if (strlen($uri["path"]) > 1) { +if (isset($uri["path"]) && strlen($uri["path"]) > 1) { $path = $uri["path"] . "/"; } @@ -12,33 +12,36 @@ define("CALDAV_SQL_PREFIX", "dav_"); define("CALDAV_URL_PREFIX", $path . "dav/"); define("CALDAV_NAMESPACE_PRIVATE", 1); -define("CALDAV_NAMESPACE_FRIENDICA_NATIVE", 2); -define("CALDAV_FRIENDICA_MINE", 1); -define("CALDAV_FRIENDICA_CONTACTS", 2); +define("CALDAV_FRIENDICA_MINE", "friendica-mine"); +define("CALDAV_FRIENDICA_CONTACTS", "friendica-contacts"); + +$GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"] = array(CALDAV_FRIENDICA_MINE, CALDAV_FRIENDICA_CONTACTS); define("CARDDAV_NAMESPACE_COMMUNITYCONTACTS", 1); define("CARDDAV_NAMESPACE_PHONECONTACTS", 2); -define("CALDAV_DB_VERSION", 1); +define("CALDAV_MAX_YEAR", date("Y") + 5); /** * @return int */ -function getCurMicrotime () { - list($usec, $sec) = explode(" ", microtime()); - return sprintf("%14.0f", $sec * 10000 + $usec * 10000); +function getCurMicrotime() +{ + list($usec, $sec) = explode(" ", microtime()); + return sprintf("%14.0f", $sec * 10000 + $usec * 10000); } // function getCurMicrotime /** * */ -function debug_time() { - $cur = getCurMicrotime(); - if ($GLOBALS["debug_time_last"] > 0) { - echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "
\n"; - } - $GLOBALS["debug_time_last"] = $cur; +function debug_time() +{ + $cur = getCurMicrotime(); + if ($GLOBALS["debug_time_last"] > 0) { + echo "Zeit: " . ($cur - $GLOBALS["debug_time_last"]) . "
\n"; + } + $GLOBALS["debug_time_last"] = $cur; } @@ -48,7 +51,7 @@ function debug_time() { */ function dav_compat_username2id($username = "") { - $x = q("SELECT `uid` FROM user WHERE nickname='%s' AND account_removed = 0 AND account_expired = 0", dbesc($username)); + $x = q("SELECT `uid` FROM `user` WHERE `nickname`='%s' AND `account_removed` = 0 AND `account_expired` = 0", dbesc($username)); if (count($x) == 1) return $x[0]["uid"]; return null; } @@ -59,7 +62,7 @@ function dav_compat_username2id($username = "") */ function dav_compat_id2username($id = 0) { - $x = q("SELECT `nickname` FROM user WHERE uid = %i AND account_removed = 0 AND account_expired = 0", IntVal($id)); + $x = q("SELECT `nickname` FROM `user` WHERE `uid` = %i AND `account_removed` = 0 AND `account_expired` = 0", IntVal($id)); if (count($x) == 1) return $x[0]["nickname"]; return ""; } @@ -67,7 +70,8 @@ function dav_compat_id2username($id = 0) /** * @return int */ -function dav_compat_get_curr_user_id() { +function dav_compat_get_curr_user_id() +{ $a = get_app(); return IntVal($a->user["uid"]); } @@ -86,13 +90,38 @@ function dav_compat_principal2uid($principalUri = "") return dav_compat_username2id($username); } +/** + * @param string $principalUri + * @return array|null + */ +function dav_compat_principal2namespace($principalUri = "") +{ + if (strlen($principalUri) == 0) return null; + if ($principalUri[0] == "/") $principalUri = substr($principalUri, 1); + + if (strpos($principalUri, "principals/users/") !== 0) return null; + $username = substr($principalUri, strlen("principals/users/")); + return array("namespace" => CALDAV_NAMESPACE_PRIVATE, "namespace_id" => dav_compat_username2id($username)); +} + + +/** + * @return string + */ +function dav_compat_currentUserPrincipal() +{ + $a = get_app(); + return "principals/users/" . strtolower($a->user["nickname"]); +} + /** * @param string $name * @return null|string */ -function dav_compat_getRequestVar($name = "") { - if (x($_REQUEST, $name)) return $_REQUEST[$name]; +function dav_compat_getRequestVar($name = "") +{ + if (isset($_REQUEST[$name])) return $_REQUEST[$name]; else return null; } @@ -108,28 +137,73 @@ function dav_compat_parse_text_serverside($text) /** * @param string $uri */ -function dav_compat_redirect($uri = "") { +function dav_compat_redirect($uri = "") +{ goaway($uri); } + +/** + * @return null|int + */ +function dav_compat_get_max_private_calendars() +{ + return null; +} + /** - * @param int $user_id * @param int $namespace * @param int $namespace_id - * @return AnimexxCalSource + * @param string $uri + * @param array $calendar + * @return Sabre_CalDAV_Backend_Common * @throws Exception */ -function wdcal_calendar_factory($user_id, $namespace, $namespace_id) +function wdcal_calendar_factory($namespace, $namespace_id, $uri, $calendar = null) { switch ($namespace) { case CALDAV_NAMESPACE_PRIVATE: - return new AnimexxCalSourcePrivate($user_id, $namespace_id); - case CALDAV_NAMESPACE_FRIENDICA_NATIVE: - return new FriendicaCalSourceEvents($user_id, $namespace_id); + if ($uri == CALDAV_FRIENDICA_MINE || $uri == CALDAV_FRIENDICA_CONTACTS) return Sabre_CalDAV_Backend_Friendica::getInstance(); + else return Sabre_CalDAV_Backend_Private::getInstance(); } throw new Exception("Calendar Namespace not found"); } +/** + * @param int $calendar_id + * @return Sabre_CalDAV_Backend_Common + * @throws Exception + */ +function wdcal_calendar_factory_by_id($calendar_id) +{ + $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendar_id); + return wdcal_calendar_factory($calendar["namespace"], $calendar["namespace_id"], $calendar["uri"], $calendar); +} + +/** + * @param int $user_id + * @param bool $withcheck + * @return array + */ +function wdcal_create_std_calendars_get_statements($user_id, $withcheck = true) +{ + $stms = array(); + $a = get_app(); + $uris = array( + 'private' => t("Private Calendar"), + CALDAV_FRIENDICA_MINE => t("Friendica Events: Mine"), + CALDAV_FRIENDICA_CONTACTS => t("Friendica Events: Contacts"), + ); + foreach ($uris as $uri => $name) { + $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d AND `uri` = '%s'", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($uri)); + if (count($cals) == 0 || !$withcheck) $stms[] = + sprintf("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `displayname`, `timezone`, `ctag`, `uri`, `has_vevent`, `has_vtodo`) + VALUES (%d, %d, '%s', '%s', 1, '%s', 1, 0)", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($user_id), dbesc($name), dbesc($a->timezone), dbesc($uri)); + } + return $stms; +} /** */ @@ -138,27 +212,9 @@ function wdcal_create_std_calendars() $a = get_app(); if (!local_user()) return; - $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_PRIVATE); - if (count($cals) == 0) { - $maxid = q("SELECT MAX(`namespace_id`) maxid FROM %s%scalendars WHERE `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE); - if (!$maxid) { - notification("Something went wrong when trying to create your calendar."); - goaway("/"); - killme(); - } - $nextid = IntVal($maxid[0]["maxid"]) + 1; - q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, $nextid, $a->user["uid"], dbesc(t("Private Calendar")), dbesc($a->timezone) - ); - } + $privates = q("SELECT COUNT(*) num FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"])); + if ($privates[0]["num"] > 0) return; - $cals = q("SELECT * FROM %s%scalendars WHERE `uid` = %d AND `namespace` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], CALDAV_NAMESPACE_FRIENDICA_NATIVE); - if (count($cals) < 2) { - q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_MINE, $a->user["uid"], dbesc(t("Friendica Events: Mine")), dbesc($a->timezone) - ); - q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `uid`, `displayname`, `timezone`, `ctag`) VALUES (%d, %d, %d, '%s', '%s', 1)", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_FRIENDICA_NATIVE, CALDAV_FRIENDICA_CONTACTS, $a->user["uid"], dbesc(t("Friendica Events: Contacts")), dbesc($a->timezone) - ); - } + $stms = wdcal_create_std_calendars_get_statements($a->user["uid"]); + foreach ($stms as $stmt) q($stmt); } diff --git a/dav/colorpicker/demo.html b/dav/colorpicker/demo.html index 38975f8ed..8b5aad465 100644 --- a/dav/colorpicker/demo.html +++ b/dav/colorpicker/demo.html @@ -6,8 +6,8 @@ Really Simple Color Picker - - + + "; + + $out .= "
"; + + return $out; +} + + +/** + * @param Sabre_VObject_Component_VEvent $component + * @param wdcal_local $localization + * @return int + */ +function wdcal_set_component_date(&$component, &$localization) +{ + if (isset($_REQUEST["allday"])) { + $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " 00:00"); + $ts_end = $localization->date_local2timestamp($_REQUEST["end_date"] . " 00:00"); + $type = Sabre_VObject_Property_DateTime::DATE; + } else { + $ts_start = $localization->date_local2timestamp($_REQUEST["start_date"] . " " . $_REQUEST["start_time"]); + $ts_end = $localization->date_local2timestamp($_REQUEST["end_date"] . " " . $_REQUEST["end_time"]); + $type = Sabre_VObject_Property_DateTime::LOCALTZ; + } + $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART"); + $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type); + $datetime_end = new Sabre_VObject_Property_DateTime("DTEND"); + $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type); + + $component->__unset("DTSTART"); + $component->__unset("DTEND"); + $component->add($datetime_start); + $component->add($datetime_end); + + return $ts_start; +} + +/** + * @param Sabre_VObject_Component_VEvent $component + * @param string $str + * @return string + */ + +function wdcal_set_component_recurrence_special(&$component, $str) { + $ret = ""; + + /** @var Sabre_VObject_Property_DateTime $start */ + $start = $component->__get("DTSTART"); + $dayMap = array( + 0 => 'SU', + 1 => 'MO', + 2 => 'TU', + 3 => 'WE', + 4 => 'TH', + 5 => 'FR', + 6 => 'SA', + ); + + switch ($str) { + case "bymonthday": + $day = $start->getDateTime()->format("j"); + $ret = ";BYMONTHDAY=" . $day; + break; + case "bymonthday_neg": + $day = $start->getDateTime()->format("j"); + $day_max = $start->getDateTime()->format("t"); + $ret = ";BYMONTHDAY=" . (-1 * ($day_max - $day + 1)); + break; + case "byday": + $day = $start->getDateTime()->format("j"); + $weekday = $dayMap[$start->getDateTime()->format("w")]; + $num = IntVal(ceil($day / 7)); + $ret = ";BYDAY=${num}${weekday}"; + break; + case "byday_neg": + $day = $start->getDateTime()->format("j"); + $weekday = $dayMap[$start->getDateTime()->format("w")]; + $day_max = $start->getDateTime()->format("t"); + $day_last = ($day_max - $day + 1); + $num = IntVal(ceil($day_last / 7)); + $ret = ";BYDAY=-${num}${weekday}"; + break; + } + return $ret; +} + +/** + * @param Sabre_VObject_Component_VEvent $component + * @param wdcal_local $localization + */ +function wdcal_set_component_recurrence(&$component, &$localization) +{ + $component->__unset("RRULE"); + $component->__unset("EXRULE"); + $component->__unset("EXDATE"); + $component->__unset("RDATE"); + + $part_until = ""; + switch ($_REQUEST["rec_until_type"]) { + case "date": + $date = $localization->date_local2timestamp($_REQUEST["rec_until_date"]); + $part_until = ";UNTIL=" . date("Ymd", $date); + $datetime_until = new Sabre_VObject_Property_DateTime("UNTIL"); + $datetime_until->setDateTime(new DateTime(date("Y-m-d H:i:s", $date)), Sabre_VObject_Property_DateTime::DATE); + break; + case "count": + $part_until = ";COUNT=" . IntVal($_REQUEST["rec_until_count"]); + break; + } + + switch ($_REQUEST["rec_frequency"]) { + case "daily": + $part_freq = "FREQ=DAILY"; + if (isset($_REQUEST["rec_daily_byday"])) { + $days = array(); + foreach ($_REQUEST["rec_daily_byday"] as $x) if (in_array($x, array("MO", "TU", "WE", "TH", "FR", "SA", "SU"))) $days[] = $x; + if (count($days) > 0) $part_freq .= ";BYDAY=" . implode(",", $days); + } + break; + case "weekly": + $part_freq = "FREQ=WEEKLY"; + if (isset($_REQUEST["rec_weekly_wkst"]) && in_array($_REQUEST["rec_weekly_wkst"], array("MO", "SU"))) $part_freq .= ";WKST=" . $_REQUEST["rec_weekly_wkst"]; + if (isset($_REQUEST["rec_weekly_byday"])) { + $days = array(); + foreach ($_REQUEST["rec_weekly_byday"] as $x) if (in_array($x, array("MO", "TU", "WE", "TH", "FR", "SA", "SU"))) $days[] = $x; + if (count($days) > 0) $part_freq .= ";BYDAY=" . implode(",", $days); + } + break; + case "monthly": + $part_freq = "FREQ=MONTHLY"; + $part_freq .= wdcal_set_component_recurrence_special($component, $_REQUEST["rec_monthly_day"]); + break; + case "yearly": + /** @var Sabre_VObject_Property_DateTime $start */ + $start = $component->__get("DTSTART"); + $part_freq = "FREQ=YEARLY"; + $part_freq .= ";BYMONTH=" . $start->getDateTime()->format("n"); + $part_freq .= wdcal_set_component_recurrence_special($component, $_REQUEST["rec_yearly_day"]); + break; + default: + $part_freq = ""; + } + + if ($part_freq == "") return; + + if (isset($_REQUEST["rec_interval"])) $part_freq .= ";INTERVAL=" . InTVal($_REQUEST["rec_interval"]); + + if (isset($_REQUEST["rec_exceptions"])) { + $arr = array(); + foreach ($_REQUEST["rec_exceptions"] as $except) { + $arr[] = new DateTime(date("Y-m-d H:i:s", $except)); + } + /** @var Sabre_VObject_Property_MultiDateTime $prop */ + $prop = Sabre_VObject_Property::create("EXDATE"); + $prop->setDateTimes($arr); + $component->add($prop); + } + + $rrule = $part_freq . $part_until; + $component->add(new Sabre_VObject_Property("RRULE", $rrule)); + +} + + + /** + * @param Sabre_VObject_Component_VEvent $component + * @param wdcal_local $localization + * @param string $summary + * @param int $dtstart + */ +function wdcal_set_component_alerts(&$component, &$localization, $summary, $dtstart) +{ + $a = get_app(); + + $prev_alarms = $component->select("VALARM"); + $component->__unset("VALARM"); + + foreach ($prev_alarms as $al) { + /** @var Sabre_VObject_Component_VAlarm $al */ + // @TODO Parse notifications that have been there before; e.g. from Lightning + } + + foreach (array_keys($_REQUEST["noti_type"]) as $key) if (is_numeric($key) || ($key == "new" && $_REQUEST["new_alarm"] == 1)) { + $alarm = new Sabre_VObject_Component_VAlarm("VALARM"); + + switch ($_REQUEST["noti_type"][$key]) { + case "email": + $mailtext = str_replace(array( + "#date#", "#name", + ), array( + $localization->date_timestamp2local($dtstart), $summary, + ), t("The event #name# will start at #date")); + + $alarm->add(new Sabre_VObject_Property("ACTION", "EMAIL")); + $alarm->add(new Sabre_VObject_Property("SUMMARY", $summary)); + $alarm->add(new Sabre_VObject_Property("DESCRIPTION", $mailtext)); + $alarm->add(new Sabre_VObject_Property("ATTENDEE", "MAILTO:" . $a->user["email"])); + break; + case "display": + $alarm->add(new Sabre_VObject_Property("ACTION", "DISPLAY")); + $text = str_replace("#name#", $summary, t("#name# is about to begin.")); + $alarm->add(new Sabre_VObject_Property("DESCRIPTION", $text)); + break; + default: + continue; + } + + $trigger_name = "TRIGGER"; + $trigger_val = ""; // @TODO Bugfix : und ; sind evtl. vertauscht vgl. http://www.kanzaki.com/docs/ical/trigger.html + if ($_REQUEST["noti_ref"][$key] == "end") $trigger_name .= ";RELATED=END"; + $trigger_val .= "-P"; + if (in_array($_REQUEST["noti_unit"][$key], array("H", "M", "S"))) $trigger_val .= "T"; + $trigger_val .= IntVal($_REQUEST["noti_value"][$key]) . $_REQUEST["noti_unit"][$key]; + $alarm->add(new Sabre_VObject_Property($trigger_name, $trigger_val)); + + $component->add($alarm); + } + +} + + /** + * @param string $uri + * @param int $uid + * @param string $timezone + * @param string $goaway_url + * @return array + */ +function wdcal_postEditPage($uri, $uid = 0, $timezone = "", $goaway_url = "") +{ + $uid = IntVal($uid); + $localization = wdcal_local::getInstanceByUser($uid); + + $server = dav_create_server(true, true, false); + + if ($uri > 0) { + $calendar = dav_get_current_user_calendar_by_id($server, $_REQUEST["calendar"], DAV_ACL_READ); + $obj_uri = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($uri); + $obj_uri = $obj_uri["uri"]; + + $vObject = dav_get_current_user_calendarobject($server, $calendar, $obj_uri, DAV_ACL_WRITE); + $component = dav_get_eventComponent($vObject); + + if ($component == null) return array("ok" => false, "msg" => t('Could not open component for editing')); + } else { + $calendar = dav_get_current_user_calendar_by_id($server, $_REQUEST["calendar"], DAV_ACL_WRITE); + $vObject = dav_create_empty_vevent(); + $component = dav_get_eventComponent($vObject); + $obj_uri = $component->__get("UID"); + } + + $ts_start = wdcal_set_component_date($component, $localization); + wdcal_set_component_recurrence($component, $localization); + wdcal_set_component_alerts($component, $localization, icalendar_sanitize_string(dav_compat_parse_text_serverside("summary")), $ts_start); + + $component->__unset("LOCATION"); + $component->__unset("SUMMARY"); + $component->__unset("DESCRIPTION"); + $component->__unset("X-ANIMEXX-COLOR"); + $component->add("SUMMARY", icalendar_sanitize_string(dav_compat_parse_text_serverside("summary"))); + $component->add("LOCATION", icalendar_sanitize_string(dav_compat_parse_text_serverside("location"))); + $component->add("DESCRIPTION", icalendar_sanitize_string(dav_compat_parse_text_serverside("wdcal_desc"))); + if (isset($_REQUEST["color_override"])) { + $component->add("X-ANIMEXX-COLOR", $_REQUEST["color"]); + } + + $data = $vObject->serialize(); + + if ($uri == 0) { + $calendar->createFile($obj_uri . ".ics", $data); + } else { + $obj = $calendar->getChild($obj_uri); + $obj->put($data); + } + return array("ok" => false, "msg" => t("Saved")); +} + + +/** + * @return string + */ +function wdcal_getEditPage_exception_selector() +{ + header("Content-type: application/json"); + + $a = get_app(); + $localization = wdcal_local::getInstanceByUser($a->user["uid"]); + + $vObject = dav_create_empty_vevent(); + + foreach ($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') break; + } + /** @var Sabre_VObject_Component_VEvent $component */ + wdcal_set_component_date($component, $localization); + wdcal_set_component_recurrence($component, $localization); + + + $it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->__get("UID")); + $max_ts = mktime(0, 0, 0, 1, 1, CALDAV_MAX_YEAR + 1); + $last_start = 0; + + $o = "\n"; + + return $o; +} \ No newline at end of file diff --git a/dav/database-init.inc.php b/dav/database-init.inc.php index 06e3abc52..8cdd9b9b1 100644 --- a/dav/database-init.inc.php +++ b/dav/database-init.inc.php @@ -2,40 +2,78 @@ /** + * @param int $from_version + * @return array|string[] + */ +function dav_get_update_statements($from_version) +{ + $stms = array(); + + if ($from_version == 1) { + $stms[] = "ALTER TABLE `dav_calendarobjects` + ADD `calendar_id` INT NOT NULL AFTER `namespace_id` , + ADD `user_temp` INT NOT NULL AFTER `calendar_id` "; + $stms[] = "ALTER TABLE `dav_calendarobjects` + ADD `componentType` ENUM( 'VEVENT', 'VTODO' ) NOT NULL DEFAULT 'VEVENT' AFTER `lastmodified` , + ADD `firstOccurence` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `componentType` , + ADD `lastOccurence` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `firstOccurence`"; + $stms[] = "UPDATE dav_calendarobjects a JOIN dav_calendars b ON a.namespace = b.namespace AND a.namespace_id = b.namespace_id SET a.user_temp = b.uid"; + $stms[] = "DROP TABLE IF EXISTS + `dav_addressbooks_community` , + `dav_addressbooks_phone` , + `dav_cache_synchronized` , + `dav_caldav_log` , + `dav_calendars` , + `dav_cal_virtual_object_cache` , + `dav_cards` , + `dav_jqcalendar` , + `dav_locks` , + `dav_notifications` ;"; + + $stms = array_merge($stms, dav_get_create_statements(array("dav_calendarobjects"))); + + $user_ids = q("SELECT DISTINCT `uid` FROM %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX); + foreach ($user_ids as $user) $stms = array_merge($stms, wdcal_create_std_calendars_get_statements($user["uid"], false)); + + $stms[] = "UPDATE dav_calendarobjects a JOIN dav_calendars b + ON b.`namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND a.`user_temp` = b.`namespace_id` AND b.`uri` = 'private' + SET a.`calendar_id` = b.`id`"; + + $stms[] = "ALTER TABLE `dav_calendarobjects` DROP `namespace`, DROP `namespace_id`, DROP `user_temp`"; + + } + + return $stms; +} + +/** + * @param array $except * @return array */ -function dav_get_create_statements() { +function dav_get_create_statements($except = array()) +{ $arr = array(); - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` ( - `uid` int(11) NOT NULL, + if (!in_array("dav_addressbooks_community", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_community` ( + `uid` int(11) NOT NULL, `ctag` int(11) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`uid`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; +) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_phone` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + if (!in_array("dav_addressbooks_phone", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_addressbooks_phone` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL, - `principaluri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, - `displayname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `uri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, - `description` text COLLATE utf8_unicode_ci, + `principaluri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `displayname` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `uri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + `description` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, `ctag` int(11) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `principaluri` (`principaluri`,`uri`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cache_synchronized` ( - `uid` mediumint(8) unsigned NOT NULL, - `namespace` smallint(6) NOT NULL, - `namespace_id` int(11) NOT NULL, - `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`uid`,`namespace`,`namespace_id`), - KEY `namespace` (`namespace`,`namespace_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_caldav_log` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + if (!in_array("dav_caldav_log", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_caldav_log` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` mediumint(9) NOT NULL, `ip` varchar(15) NOT NULL, `user_agent` varchar(100) NOT NULL, @@ -46,124 +84,111 @@ function dav_get_create_statements() { KEY `mitglied` (`uid`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendarobjects` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `namespace` mediumint(9) NOT NULL, - `namespace_id` int(10) unsigned NOT NULL, + if (!in_array("dav_calendarobjects", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendarobjects` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `calendar_id` int(11) NOT NULL, `calendardata` text, `uri` varchar(200) NOT NULL, `lastmodified` timestamp NULL DEFAULT NULL, + `componentType` enum('VEVENT','VTODO') NOT NULL DEFAULT 'VEVENT', + `firstOccurence` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOccurence` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `etag` varchar(15) NOT NULL, `size` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `uri` (`uri`,`namespace`,`namespace_id`), - KEY `namespace` (`namespace`,`namespace_id`) + UNIQUE KEY `uri` (`uri`), + KEY `calendar_id` (`calendar_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendars` ( - `namespace` mediumint(9) NOT NULL, + if (!in_array("dav_calendars", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_calendars` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `namespace` mediumint(9) NOT NULL, `namespace_id` int(10) unsigned NOT NULL, - `uid` mediumint(8) unsigned NOT NULL, `calendarorder` int(11) NOT NULL DEFAULT '1', - `calendarcolor` varchar(20) NOT NULL DEFAULT '#5858FF', + `calendarcolor` char(6) NOT NULL DEFAULT '5858FF', `displayname` varchar(200) NOT NULL, `timezone` text NOT NULL, `description` varchar(500) NOT NULL, + `uri` varchar(50) NOT NULL DEFAULT '', + `has_vevent` tinyint(4) NOT NULL DEFAULT '1', + `has_vtodo` tinyint(4) NOT NULL DEFAULT '1', `ctag` int(10) unsigned NOT NULL, - PRIMARY KEY (`namespace`,`namespace_id`), - KEY `uid` (`uid`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; + PRIMARY KEY (`id`), + UNIQUE KEY (`namespace` , `namespace_id` , `uri`), + KEY `uri` (`uri`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_cache` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, - `uid` mediumint(8) unsigned NOT NULL, - `namespace` mediumint(9) NOT NULL, - `namespace_id` int(11) NOT NULL DEFAULT '0', + if (!in_array("dav_cal_virtual_object_cache", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_cache` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `calendar_id` int(10) unsigned NOT NULL, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `data_uri` char(80) NOT NULL, - `data_subject` varchar(1000) NOT NULL, + `data_summary` varchar(1000) NOT NULL, `data_location` varchar(1000) NOT NULL, - `data_description` text NOT NULL, `data_start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `data_end` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `data_allday` tinyint(4) NOT NULL, `data_type` varchar(20) NOT NULL, - `ical` text NOT NULL, - `ical_size` int(11) NOT NULL, - `ical_etag` varchar(15) NOT NULL, + `calendardata` text NOT NULL, + `size` int(11) NOT NULL, + `etag` varchar(15) NOT NULL, PRIMARY KEY (`id`), - KEY `ref_type` (`namespace`,`namespace_id`), - KEY `mitglied` (`uid`,`data_end`), - KEY `data_uri` (`data_uri`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; + KEY `data_uri` (`data_uri`), + KEY `ref_type` (`calendar_id`,`data_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cards` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + if (!in_array("dav_cal_virtual_object_sync", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cal_virtual_object_sync` ( + `calendar_id` int(10) unsigned NOT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`calendar_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8"; + + if (!in_array("dav_cards", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_cards` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `namespace` tinyint(3) unsigned NOT NULL, `namespace_id` int(11) unsigned NOT NULL, `contact` int(11) DEFAULT NULL, - `carddata` mediumtext COLLATE utf8_unicode_ci, - `uri` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, + `carddata` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `uri` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, `lastmodified` int(11) unsigned DEFAULT NULL, `manually_edited` tinyint(4) NOT NULL DEFAULT '0', `manually_deleted` tinyint(4) NOT NULL DEFAULT '0', - `etag` varchar(15) COLLATE utf8_unicode_ci NOT NULL, + `etag` varchar(15) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, `size` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `namespace` (`namespace`,`namespace_id`,`contact`), KEY `contact` (`contact`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; +) ENGINE=InnoDB DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_jqcalendar` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uid` int(10) unsigned NOT NULL, - `ical_uri` varchar(200) NOT NULL, - `ical_recurr_uri` varchar(100) NOT NULL, - `namespace` mediumint(9) NOT NULL, - `namespace_id` int(11) NOT NULL, - `permission_edit` tinyint(4) NOT NULL DEFAULT '1', - `Subject` varchar(1000) DEFAULT NULL, - `Location` varchar(1000) DEFAULT NULL, - `Description` text, + if (!in_array("dav_jqcalendar", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_jqcalendar` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ical_recurr_uri` varchar(100) DEFAULT NULL, + `calendar_id` int(10) unsigned NOT NULL, + `calendarobject_id` int(10) unsigned NOT NULL, + `Summary` varchar(100) NOT NULL, `StartTime` timestamp NULL DEFAULT NULL, `EndTime` timestamp NULL DEFAULT NULL, - `IsAllDayEvent` smallint(6) NOT NULL, - `Color` varchar(20) DEFAULT NULL, - `RecurringRule` varchar(500) DEFAULT NULL, + `IsEditable` tinyint(3) unsigned NOT NULL, + `IsAllDayEvent` tinyint(4) NOT NULL, + `IsRecurring` tinyint(4) NOT NULL, + `Color` char(6) DEFAULT NULL, PRIMARY KEY (`id`), - KEY `user` (`uid`,`StartTime`), - KEY `zuord_typ` (`namespace`,`namespace_id`), - KEY `ical_uri` (`ical_uri`,`ical_recurr_uri`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; + KEY `calendarByStart` (`calendar_id`,`StartTime`), + KEY `calendarobject_id` (`calendarobject_id`,`ical_recurr_uri`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8"; - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_locks` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `owner` varchar(100) DEFAULT NULL, - `timeout` int(10) unsigned DEFAULT NULL, - `created` int(11) DEFAULT NULL, - `token` varchar(100) DEFAULT NULL, - `scope` tinyint(4) DEFAULT NULL, - `depth` tinyint(4) DEFAULT NULL, - `uri` text, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; - - - $arr[] = "CREATE TABLE IF NOT EXISTS `dav_notifications` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uid` int(10) unsigned NOT NULL, - `ical_uri` varchar(200) NOT NULL, - `ical_recurr_uri` varchar(100) NOT NULL, - `namespace` mediumint(8) unsigned NOT NULL, - `namespace_id` int(10) unsigned NOT NULL, + if (!in_array("dav_notifications", $except)) $arr[] = "CREATE TABLE IF NOT EXISTS `dav_notifications` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ical_recurr_uri` varchar(100) DEFAULT NULL, + `calendar_id` int(11) NOT NULL, + `calendarobject_id` int(10) unsigned NOT NULL, + `action` enum('email','display') NOT NULL DEFAULT 'email', `alert_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `rel_type` enum('second','minute','hour','day','week','month','year') NOT NULL, - `rel_value` mediumint(9) NOT NULL, `notified` tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `notified` (`notified`,`alert_date`), - KEY `ical_uri` (`uid`,`ical_uri`,`ical_recurr_uri`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8"; + KEY `calendar_id` (`calendar_id`,`calendarobject_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8"; return $arr; } @@ -171,11 +196,13 @@ function dav_get_create_statements() { /** * @return int */ -function dav_check_tables() { - $dbv = get_config("dav", "db_version"); - if ($dbv == CALDAV_DB_VERSION) return 0; // Correct - if (is_numeric($dbv)) return 1; // Older version (update needed) - return -1; // Not installed +function dav_check_tables() +{ + $x = q("DESCRIBE %s%scalendars", CALDAV_SQL_DB, CALDAV_SQL_PREFIX); + if (!$x) return -1; + if (count($x) == 9) return 1; // Version 0.1 + if (count($x) == 12) return 0; // Correct + return -2; // Unknown version } @@ -184,7 +211,7 @@ function dav_check_tables() { */ function dav_create_tables() { - $stms = dav_get_create_statements(); + $stms = dav_get_create_statements(); $errors = array(); global $db; @@ -193,7 +220,25 @@ function dav_create_tables() if ($db->error) $errors[] = $db->error; } - if (count($errors) == 0) set_config("dav", "db_version", CALDAV_DB_VERSION); + return $errors; +} + +/** + * @return array + */ +function dav_upgrade_tables() +{ + $ver = dav_check_tables(); + if (!in_array($ver, array(1) )) return array("Unknown error"); + + $stms = dav_get_update_statements($ver); + $errors = array(); + + global $db; + foreach ($stms as $st) { + $db->q($st); + if ($db->error) $errors[] = $db->error; + } return $errors; } \ No newline at end of file diff --git a/dav/dav.php b/dav/dav.php index 1f35b06ad..05916d01f 100644 --- a/dav/dav.php +++ b/dav/dav.php @@ -2,7 +2,7 @@ /** * Name: Calendar with CalDAV Support * Description: A web-based calendar system with CalDAV-support. Also brings your Friendica-Contacts to your CardDAV-capable mobile phone. Requires PHP >= 5.3. - * Version: 0.1.1 + * Version: 0.2.0 * Author: Tobias Hößl */ diff --git a/dav/dav_caldav_backend_friendica.inc.php b/dav/dav_caldav_backend_friendica.inc.php deleted file mode 100644 index 11b27ea6f..000000000 --- a/dav/dav_caldav_backend_friendica.inc.php +++ /dev/null @@ -1,141 +0,0 @@ -user["uid"]; - $x = explode("-", $calendarId); - - $ret = array(); - $objs = FriendicaVirtualCalSourceBackend::getItemsByTime($user_id, $x[1]); - foreach ($objs as $obj) { - $ret[] = array( - "id" => IntVal($obj["data_uri"]), - "calendardata" => $obj["ical"], - "uri" => $obj["data_uri"], - "lastmodified" => $obj["date"], - "calendarid" => $calendarId, - "etag" => $obj["ical_etag"], - "size" => IntVal($obj["ical_size"]), - ); - } - - return $ret; - } - - /** - * Returns information from a single calendar object, based on it's object - * uri. - * - * The returned array must have the same keys as getCalendarObjects. The - * 'calendardata' object is required here though, while it's not required - * for getCalendarObjects. - * - * @param string $calendarId - * @param string $objectUri - * @throws Sabre_DAV_Exception_FileNotFound - * @return array - */ - function getCalendarObject($calendarId, $objectUri) - { - $a = get_app(); - $user_id = $a->user["uid"]; - $obj = FriendicaVirtualCalSourceBackend::getItemsByUri($user_id, $objectUri); - - return array( - "id" => IntVal($obj["data_uri"]), - "calendardata" => $obj["ical"], - "uri" => $obj["data_uri"], - "lastmodified" => $obj["date"], - "calendarid" => $calendarId, - "etag" => $obj["ical_etag"], - "size" => IntVal($obj["ical_size"]), - ); - } - - /** - * Creates a new calendar object. - * - * @param string $calendarId - * @param string $objectUri - * @param string $calendarData - * @throws Sabre_DAV_Exception_Forbidden - * @return null|string|void - */ - function createCalendarObject($calendarId, $objectUri, $calendarData) - { - throw new Sabre_DAV_Exception_Forbidden(); - } - - /** - * Updates an existing calendarobject, based on it's uri. - * - * @param string $calendarId - * @param string $objectUri - * @param string $calendarData - * @throws Sabre_DAV_Exception_Forbidden - * @return null|string|void - */ - function updateCalendarObject($calendarId, $objectUri, $calendarData) - { - throw new Sabre_DAV_Exception_Forbidden(); - } - - /** - * Deletes an existing calendar object. - * - * @param string $calendarId - * @param string $objectUri - * @throws Sabre_DAV_Exception_Forbidden - * @return void - */ - function deleteCalendarObject($calendarId, $objectUri) - { - throw new Sabre_DAV_Exception_Forbidden(); - } -} diff --git a/dav/dav_caldav_backend_virtual_friendica.inc.php b/dav/dav_caldav_backend_virtual_friendica.inc.php new file mode 100644 index 000000000..9178aaabb --- /dev/null +++ b/dav/dav_caldav_backend_virtual_friendica.inc.php @@ -0,0 +1,253 @@ + 0"; + break; + default: + throw new Sabre_DAV_Exception_NotFound(); + } + + $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"])); + + foreach ($r as $row) { + $uid = $calendar["uri"] . "-" . $row["id"]; + $vevent = dav_create_empty_vevent($uid); + $component = dav_get_eventComponent($vevent); + + if ($row["adjust"]) { + $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]); + $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]); + } else { + $start = $row["start"]; + $finish = $row["finish"]; + } + + $summary = ($row["summary"] != "" ? $row["summary"] : $row["desc"]); + $desc = ($row["summary"] != "" ? $row["desc"] : ""); + $component->add("SUMMARY", icalendar_sanitize_string($summary)); + $component->add("LOCATION", icalendar_sanitize_string($row["location"])); + $component->add("DESCRIPTION", icalendar_sanitize_string($desc)); + + $ts_start = wdcal_mySql2PhpTime($start); + $ts_end = wdcal_mySql2PhpTime($start); + + $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false); + $type = ($allday ? Sabre_VObject_Property_DateTime::DATE : Sabre_VObject_Property_DateTime::LOCALTZ); + + $datetime_start = new Sabre_VObject_Property_DateTime("DTSTART"); + $datetime_start->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_start)), $type); + $datetime_end = new Sabre_VObject_Property_DateTime("DTEND"); + $datetime_end->setDateTime(new DateTime(date("Y-m-d H:i:s", $ts_end)), $type); + + $component->add($datetime_start); + $component->add($datetime_end); + + $data = $vevent->serialize(); + + q("INSERT INTO %s%scal_virtual_object_cache (`calendar_id`, `data_uri`, `data_summary`, `data_location`, `data_start`, `data_end`, `data_allday`, `data_type`, + `calendardata`, `size`, `etag`) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', %d, '%s')", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendarId, dbesc($uid), dbesc($summary), dbesc($row["location"]), dbesc($row["start"]), dbesc($row["finish"]), + ($allday ? 1 : 0), dbesc(($row["type"] == "birthday" ? "birthday" : "")), dbesc($data), strlen($data), md5($data)); + + } + + } + + + /** + * @param array $row + * @param array $calendar + * @param string $base_path + * @return array + */ + private function jqcal2wdcal($row, $calendar, $base_path) + { + if ($row["adjust"]) { + $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]); + $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]); + } else { + $start = $row["start"]; + $finish = $row["finish"]; + } + + $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false); + + $summary = (($row["summary"]) ? $row["summary"] : substr(preg_replace("/\[[^\]]*\]/", "", $row["desc"]), 0, 100)); + + return array( + "jq_id" => $row["id"], + "ev_id" => $row["id"], + "summary" => escape_tags($summary), + "start" => wdcal_mySql2PhpTime($start), + "end" => wdcal_mySql2PhpTime($finish), + "is_allday" => ($allday ? 1 : 0), + "is_moredays" => (substr($start, 0, 10) != substr($finish, 0, 10)), + "is_recurring" => ($row["type"] == "birthday"), + "color" => "7878ff", + "is_editable" => 0, + "is_editable_quick" => 0, + "location" => $row["location"], + "attendees" => '', + "has_notification" => 0, + "url_detail" => $base_path . "/events/event/" . $row["id"], + "url_edit" => "", + "special_type" => ($row["type"] == "birthday" ? "birthday" : ""), + ); + } + + + /** + * @param int $calendarId + * @param string $date_from + * @param string $date_to + * @param string $base_path + * @throws Sabre_DAV_Exception_NotFound + * @return array + */ + public function listItemsByRange($calendarId, $date_from, $date_to, $base_path) + { + $calendar = Sabre_CalDAV_Backend_Common::loadCalendarById($calendarId); + + if ($calendar["namespace"] != CALDAV_NAMESPACE_PRIVATE) throw new Sabre_DAV_Exception_NotFound(); + + switch ($calendar["uri"]) { + case CALDAV_FRIENDICA_MINE: + $sql_where = " AND cid = 0"; + break; + case CALDAV_FRIENDICA_CONTACTS: + $sql_where = " AND cid > 0"; + break; + default: + throw new Sabre_DAV_Exception_NotFound(); + } + + if ($date_from != "") { + if (is_numeric($date_from)) $sql_where .= " AND `finish` >= '" . date("Y-m-d H:i:s", $date_from) . "'"; + else $sql_where .= " AND `finish` >= '" . dbesc($date_from) . "'"; + } + if ($date_to != "") { + if (is_numeric($date_to)) $sql_where .= " AND `start` <= '" . date("Y-m-d H:i:s", $date_to) . "'"; + else $sql_where .= " AND `start` <= '" . dbesc($date_to) . "'"; + } + $ret = array(); + + $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", IntVal($calendar["namespace_id"])); + + $a = get_app(); + foreach ($r as $row) { + $r = $this->jqcal2wdcal($row, $calendar, $a->get_baseurl()); + $r["calendar_id"] = $calendar["id"]; + $ret[] = $r; + } + + return $ret; + } + + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri, which the basename of the uri with which the calendar is + * accessed. + * * principaluri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * @param string $principalUri + * @return array + */ + public function getCalendarsForUser($principalUri) + { + $n = dav_compat_principal2namespace($principalUri); + if ($n["namespace"] != $this->getNamespace()) return array(); + + $cals = q("SELECT * FROM %s%scalendars WHERE `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $this->getNamespace(), IntVal($n["namespace_id"])); + $ret = array(); + foreach ($cals as $cal) { + if (!in_array($cal["uri"], $GLOBALS["CALDAV_PRIVATE_SYSTEM_CALENDARS"])) continue; + + $dat = array( + "id" => $cal["id"], + "uri" => $cal["uri"], + "principaluri" => $principalUri, + '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $cal['ctag'] ? $cal['ctag'] : '0', + '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet(array("VEVENT")), + "calendar_class" => "Sabre_CalDAV_Calendar_Virtual", + ); + foreach ($this->propertyMap as $key=> $field) $dat[$key] = $cal[$field]; + + $ret[] = $dat; + } + + return $ret; + } + + /** + * @param int $calendar_id + * @param int $calendarobject_id + * @return string + */ + function getItemDetailRedirect($calendar_id, $calendarobject_id) + { + $a = get_app(); + $item = q("SELECT `id` FROM `item` WHERE `event-id` = %d AND `uid` = %d AND deleted = 0", IntVal($calendarobject_id), $a->user["uid"]); + if (count($item) == 0) return "/events/"; + return "/display/" . $a->user["nickname"] . "/" . IntVal($item[0]["id"]); + + } +} diff --git a/dav/dav_carddav_backend_friendica_community.inc.php b/dav/dav_carddav_backend_virtual_friendica.inc.php similarity index 96% rename from dav/dav_carddav_backend_friendica_community.inc.php rename to dav/dav_carddav_backend_virtual_friendica.inc.php index cf8b2ff00..8d8a156ca 100644 --- a/dav/dav_carddav_backend_friendica_community.inc.php +++ b/dav/dav_carddav_backend_virtual_friendica.inc.php @@ -3,6 +3,22 @@ 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 */ diff --git a/dav/dav_friendica_auth.inc.php b/dav/dav_friendica_auth.inc.php index 5082a2f95..acc33fa1e 100644 --- a/dav/dav_friendica_auth.inc.php +++ b/dav/dav_friendica_auth.inc.php @@ -1,16 +1,39 @@ currentUser); } - - public function getCurrentUser() { + + /** + * @return null|string + */ + public function getCurrentUser() { return $this->currentUser; } @@ -27,6 +50,12 @@ class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBa */ public function authenticate(Sabre_DAV_Server $server, $realm) { + $a = get_app(); + if (isset($a->user["uid"])) { + $this->currentUser = strtolower($a->user["nickname"]); + return true; + } + $auth = new Sabre_HTTP_BasicAuth(); $auth->setHTTPRequest($server->httpRequest); $auth->setHTTPResponse($server->httpResponse); @@ -47,12 +76,18 @@ class Sabre_DAV_Auth_Backend_Friendica extends Sabre_DAV_Auth_Backend_AbstractBa } + /** + * @param string $username + * @param string $password + * @return bool + */ protected function validateUserPass($username, $password) { - - $user = array( - 'uri' => "/" . 'principals/users/' . strtolower($username), + $encrypted = hash('whirlpool',trim($password)); + $r = q("SELECT COUNT(*) anz FROM `user` WHERE `nickname` = '%s' AND `password` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1", + dbesc(trim($username)), + dbesc($encrypted) ); - return $user; + return ($r[0]["anz"] == 1); } } diff --git a/dav/dav_friendica_principal.inc.php b/dav/dav_friendica_principal.inc.php index eba31c288..780bcd24b 100644 --- a/dav/dav_friendica_principal.inc.php +++ b/dav/dav_friendica_principal.inc.php @@ -1,7 +1,7 @@ = '5.1' ) - // && ( 'UTC' == date_default_timezone_get())) - date_default_timezone_set( 'Europe/Stockholm' ); -/*********************************************************************************/ -/* version, do NOT remove!! */ -define( 'ICALCREATOR_VERSION', 'iCalcreator 2.12' ); -/*********************************************************************************/ -/*********************************************************************************/ -/** - * vcalendar class - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - */ -class vcalendar { - // calendar property variables - var $calscale; - var $method; - var $prodid; - var $version; - var $xprop; - // container for calendar components - var $components; - // component config variables - var $allowEmpty; - var $unique_id; - var $language; - var $directory; - var $filename; - var $url; - var $delimiter; - var $nl; - var $format; - var $dtzid; - // component internal variables - var $attributeDelimiter; - var $valueInit; - // component xCal declaration container - var $xcaldecl; -/** - * constructor for calendar object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - * @param array $config - * @return void - */ - function vcalendar ( $config = array()) { - $this->_makeVersion(); - $this->calscale = null; - $this->method = null; - $this->_makeUnique_id(); - $this->prodid = null; - $this->xprop = array(); - $this->language = null; - $this->directory = null; - $this->filename = null; - $this->url = null; - $this->dtzid = null; -/** - * language = - */ - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - $this->xcaldecl = array(); - $this->components = array(); - } -/*********************************************************************************/ -/** - * Property Name: CALSCALE - */ -/** - * creates formatted output for calendar property calscale - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @return string - */ - function createCalscale() { - if( empty( $this->calscale )) return FALSE; - switch( $this->format ) { - case 'xcal': - return $this->nl.' calscale="'.$this->calscale.'"'; - break; - default: - return 'CALSCALE:'.$this->calscale.$this->nl; - break; - } - } -/** - * set calendar property calscale - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @param string $value - * @return void - */ - function setCalscale( $value ) { - if( empty( $value )) return FALSE; - $this->calscale = $value; - } -/*********************************************************************************/ -/** - * Property Name: METHOD - */ -/** - * creates formatted output for calendar property method - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @return string - */ - function createMethod() { - if( empty( $this->method )) return FALSE; - switch( $this->format ) { - case 'xcal': - return $this->nl.' method="'.$this->method.'"'; - break; - default: - return 'METHOD:'.$this->method.$this->nl; - break; - } - } -/** - * set calendar property method - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-20-23 - * @param string $value - * @return bool - */ - function setMethod( $value ) { - if( empty( $value )) return FALSE; - $this->method = $value; - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: PRODID - * - * The identifier is RECOMMENDED to be the identical syntax to the - * [RFC 822] addr-spec. A good method to assure uniqueness is to put the - * domain name or a domain literal IP address of the host on which.. . - */ -/** - * creates formatted output for calendar property prodid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @return string - */ - function createProdid() { - if( !isset( $this->prodid )) - $this->_makeProdid(); - switch( $this->format ) { - case 'xcal': - return $this->nl.' prodid="'.$this->prodid.'"'; - break; - default: - return 'PRODID:'.$this->prodid.$this->nl; - break; - } - } -/** - * make default value for calendar prodid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.8 - 2009-12-30 - * @return void - */ - function _makeProdid() { - $this->prodid = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language ); - } -/** - * Conformance: The property MUST be specified once in an iCalendar object. - * Description: The vendor of the implementation SHOULD assure that this - * is a globally unique identifier; using some technique such as an FPI - * value, as defined in [ISO 9070]. - */ -/** - * make default unique_id for calendar prodid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 0.3.0 - 2006-08-10 - * @return void - */ - function _makeUnique_id() { - $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost'; - } -/*********************************************************************************/ -/** - * Property Name: VERSION - * - * Description: A value of "2.0" corresponds to this memo. - */ -/** - * creates formatted output for calendar property version - - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @return string - */ - function createVersion() { - if( empty( $this->version )) - $this->_makeVersion(); - switch( $this->format ) { - case 'xcal': - return $this->nl.' version="'.$this->version.'"'; - break; - default: - return 'VERSION:'.$this->version.$this->nl; - break; - } - } -/** - * set default calendar version - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 0.3.0 - 2006-08-10 - * @return void - */ - function _makeVersion() { - $this->version = '2.0'; - } -/** - * set calendar version - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @param string $value - * @return void - */ - function setVersion( $value ) { - if( empty( $value )) return FALSE; - $this->version = $value; - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: x-prop - */ -/** - * creates formatted output for calendar property x-prop, iCal format only - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-11-01 - * @return string - */ - function createXprop() { - if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE; - $output = null; - $toolbox = new calendarComponent(); - $toolbox->setConfig( $this->getConfig()); - foreach( $this->xprop as $label => $xpropPart ) { - if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) { - $output .= $toolbox->_createElement( $label ); - continue; - } - $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' )); - if( is_array( $xpropPart['value'] )) { - foreach( $xpropPart['value'] as $pix => $theXpart ) - $xpropPart['value'][$pix] = $toolbox->_strrep( $theXpart ); - $xpropPart['value'] = implode( ',', $xpropPart['value'] ); - } - else - $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] ); - $output .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] ); - if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) { - foreach( $toolbox->xcaldecl as $localxcaldecl ) - $this->xcaldecl[] = $localxcaldecl; - } - } - return $output; - } -/** - * set calendar property x-prop - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.9 - 2012-01-16 - * @param string $label - * @param string $value - * @param array $params optional - * @return bool - */ - function setXprop( $label, $value, $params=FALSE ) { - if( empty( $label )) - return FALSE; - if( 'X-' != strtoupper( substr( $label, 0, 2 ))) - return FALSE; - if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $xprop = array( 'value' => $value ); - $xprop['params'] = iCalUtilityFunctions::_setParams( $params ); - if( !is_array( $this->xprop )) $this->xprop = array(); - $this->xprop[strtoupper( $label )] = $xprop; - return TRUE; - } -/*********************************************************************************/ -/** - * delete calendar property value - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param mixed $propName, bool FALSE => X-property - * @param int $propix, optional, if specific property is wanted in case of multiply occurences - * @return bool, if successfull delete - */ - function deleteProperty( $propName=FALSE, $propix=FALSE ) { - $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP'; - if( !$propix ) - $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1; - $this->propdelix[$propName] = --$propix; - $return = FALSE; - switch( $propName ) { - case 'CALSCALE': - if( isset( $this->calscale )) { - $this->calscale = null; - $return = TRUE; - } - break; - case 'METHOD': - if( isset( $this->method )) { - $this->method = null; - $return = TRUE; - } - break; - default: - $reduced = array(); - if( $propName != 'X-PROP' ) { - if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; } - foreach( $this->xprop as $k => $a ) { - if(( $k != $propName ) && !empty( $a )) - $reduced[$k] = $a; - } - } - else { - if( count( $this->xprop ) <= $propix ) return FALSE; - $xpropno = 0; - foreach( $this->xprop as $xpropkey => $xpropvalue ) { - if( $propix != $xpropno ) - $reduced[$xpropkey] = $xpropvalue; - $xpropno++; - } - } - $this->xprop = $reduced; - if( empty( $this->xprop )) { - unset( $this->propdelix[$propName] ); - return FALSE; - } - return TRUE; - } - return $return; - } -/** - * get calendar property value/params - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-04-16 - * @param string $propName, optional - * @param int $propix, optional, if specific property is wanted in case of multiply occurences - * @param bool $inclParam=FALSE - * @return mixed - */ - function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) { - $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP'; - if( 'X-PROP' == $propName ) { - if( !$propix ) - $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1; - $this->propix[$propName] = --$propix; - } - switch( $propName ) { - case 'ATTENDEE': - case 'CATEGORIES': - case 'DTSTART': - case 'LOCATION': - case 'ORGANIZER': - case 'PRIORITY': - case 'RESOURCES': - case 'STATUS': - case 'SUMMARY': - case 'RECURRENCE-ID-UID': - case 'R-UID': - case 'UID': - $output = array(); - foreach ( $this->components as $cix => $component) { - if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) - continue; - if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { - $component->_getProperties( $propName, $output ); - continue; - } - elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) { - if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' ))) - $content = $component->getProperty( 'UID' ); - } - elseif( FALSE === ( $content = $component->getProperty( $propName ))) - continue; - if( FALSE === $content ) - continue; - elseif( is_array( $content )) { - if( isset( $content['year'] )) { - $key = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] ); - if( !isset( $output[$key] )) - $output[$key] = 1; - else - $output[$key] += 1; - } - else { - foreach( $content as $partValue => $partCount ) { - if( !isset( $output[$partValue] )) - $output[$partValue] = $partCount; - else - $output[$partValue] += $partCount; - } - } - } // end elseif( is_array( $content )) { - elseif( !isset( $output[$content] )) - $output[$content] = 1; - else - $output[$content] += 1; - } // end foreach ( $this->components as $cix => $component) - if( !empty( $output )) - ksort( $output ); - return $output; - break; - - case 'CALSCALE': - return ( !empty( $this->calscale )) ? $this->calscale : FALSE; - break; - case 'METHOD': - return ( !empty( $this->method )) ? $this->method : FALSE; - break; - case 'PRODID': - if( empty( $this->prodid )) - $this->_makeProdid(); - return $this->prodid; - break; - case 'VERSION': - return ( !empty( $this->version )) ? $this->version : FALSE; - break; - default: - if( $propName != 'X-PROP' ) { - if( !isset( $this->xprop[$propName] )) return FALSE; - return ( $inclParam ) ? array( $propName, $this->xprop[$propName] ) - : array( $propName, $this->xprop[$propName]['value'] ); - } - else { - if( empty( $this->xprop )) return FALSE; - $xpropno = 0; - foreach( $this->xprop as $xpropkey => $xpropvalue ) { - if( $propix == $xpropno ) - return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] ) - : array( $xpropkey, $this->xprop[$xpropkey]['value'] ); - else - $xpropno++; - } - unset( $this->propix[$propName] ); - return FALSE; // not found ?? - } - } - return FALSE; - } -/** - * general vcalendar property setting - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.2.13 - 2007-11-04 - * @param mixed $args variable number of function arguments, - * first argument is ALWAYS component name, - * second ALWAYS component value! - * @return bool - */ - function setProperty () { - $numargs = func_num_args(); - if( 1 > $numargs ) - return FALSE; - $arglist = func_get_args(); - $arglist[0] = strtoupper( $arglist[0] ); - switch( $arglist[0] ) { - case 'CALSCALE': - return $this->setCalscale( $arglist[1] ); - case 'METHOD': - return $this->setMethod( $arglist[1] ); - case 'VERSION': - return $this->setVersion( $arglist[1] ); - default: - if( !isset( $arglist[1] )) $arglist[1] = null; - if( !isset( $arglist[2] )) $arglist[2] = null; - return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] ); - } - return FALSE; - } -/*********************************************************************************/ -/** - * get vcalendar config values or * calendar components - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.7 - 2012-01-12 - * @param mixed $config - * @return value - */ - function getConfig( $config = FALSE ) { - if( !$config ) { - $return = array(); - $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' ); - $return['DELIMITER'] = $this->getConfig( 'DELIMITER' ); - $return['DIRECTORY'] = $this->getConfig( 'DIRECTORY' ); - $return['FILENAME'] = $this->getConfig( 'FILENAME' ); - $return['DIRFILE'] = $this->getConfig( 'DIRFILE' ); - $return['FILESIZE'] = $this->getConfig( 'FILESIZE' ); - $return['FORMAT'] = $this->getConfig( 'FORMAT' ); - if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' ))) - $return['LANGUAGE'] = $lang; - $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' ); - $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' ); - if( FALSE !== ( $url = $this->getConfig( 'URL' ))) - $return['URL'] = $url; - $return['TZID'] = $this->getConfig( 'TZID' ); - return $return; - } - switch( strtoupper( $config )) { - case 'ALLOWEMPTY': - return $this->allowEmpty; - break; - case 'COMPSINFO': - unset( $this->compix ); - $info = array(); - foreach( $this->components as $cix => $component ) { - if( empty( $component )) continue; - $info[$cix]['ordno'] = $cix + 1; - $info[$cix]['type'] = $component->objName; - $info[$cix]['uid'] = $component->getProperty( 'uid' ); - $info[$cix]['props'] = $component->getConfig( 'propinfo' ); - $info[$cix]['sub'] = $component->getConfig( 'compsinfo' ); - } - return $info; - break; - case 'DELIMITER': - return $this->delimiter; - break; - case 'DIRECTORY': - if( empty( $this->directory ) && ( '0' != $this->directory )) - $this->directory = '.'; - return $this->directory; - break; - case 'DIRFILE': - return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' ); - break; - case 'FILEINFO': - return array( $this->getConfig( 'directory' ) - , $this->getConfig( 'filename' ) - , $this->getConfig( 'filesize' )); - break; - case 'FILENAME': - if( empty( $this->filename ) && ( '0' != $this->filename )) { - if( 'xcal' == $this->format ) - $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. . - else - $this->filename = date( 'YmdHis' ).'.ics'; - } - return $this->filename; - break; - case 'FILESIZE': - $size = 0; - if( empty( $this->url )) { - $dirfile = $this->getConfig( 'dirfile' ); - if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile )))) - $size = 0; - clearstatcache(); - } - return $size; - break; - case 'FORMAT': - return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal'; - break; - case 'LANGUAGE': - /* get language for calendar component as defined in [RFC 1766] */ - return $this->language; - break; - case 'NL': - case 'NEWLINECHAR': - return $this->nl; - break; - case 'TZID': - return $this->dtzid; - break; - case 'UNIQUE_ID': - return $this->unique_id; - break; - case 'URL': - if( !empty( $this->url )) - return $this->url; - else - return FALSE; - break; - } - } -/** - * general vcalendar config setting - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.11 - 2011-01-16 - * @param mixed $config - * @param string $value - * @return void - */ - function setConfig( $config, $value = FALSE) { - if( is_array( $config )) { - $ak = array_keys( $config ); - foreach( $ak as $k ) { - if( 'DIRECTORY' == strtoupper( $k )) { - if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] )) - return FALSE; - unset( $config[$k] ); - } - elseif( 'NEWLINECHAR' == strtoupper( $k )) { - if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] )) - return FALSE; - unset( $config[$k] ); - } - } - foreach( $config as $cKey => $cValue ) { - if( FALSE === $this->setConfig( $cKey, $cValue )) - return FALSE; - } - return TRUE; - } - $res = FALSE; - switch( strtoupper( $config )) { - case 'ALLOWEMPTY': - $this->allowEmpty = $value; - $subcfg = array( 'ALLOWEMPTY' => $value ); - $res = TRUE; - break; - case 'DELIMITER': - $this->delimiter = $value; - return TRUE; - break; - case 'DIRECTORY': - $value = trim( $value ); - $del = $this->getConfig('delimiter'); - if( $del == substr( $value, ( 0 - strlen( $del )))) - $value = substr( $value, 0, ( strlen( $value ) - strlen( $del ))); - if( is_dir( $value )) { - /* local directory */ - clearstatcache(); - $this->directory = $value; - $this->url = null; - return TRUE; - } - else - return FALSE; - break; - case 'FILENAME': - $value = trim( $value ); - if( !empty( $this->url )) { - /* remote directory+file -> URL */ - $this->filename = $value; - return TRUE; - } - $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value; - if( file_exists( $dirfile )) { - /* local file exists */ - if( is_readable( $dirfile ) || is_writable( $dirfile )) { - clearstatcache(); - $this->filename = $value; - return TRUE; - } - else - return FALSE; - } - elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) { - /* read- or writable directory */ - $this->filename = $value; - return TRUE; - } - else - return FALSE; - break; - case 'FORMAT': - $value = trim( strtolower( $value )); - if( 'xcal' == $value ) { - $this->format = 'xcal'; - $this->attributeDelimiter = $this->nl; - $this->valueInit = null; - } - else { - $this->format = null; - $this->attributeDelimiter = ';'; - $this->valueInit = ':'; - } - $subcfg = array( 'FORMAT' => $value ); - $res = TRUE; - break; - case 'LANGUAGE': - // set language for calendar component as defined in [RFC 1766] - $value = trim( $value ); - $this->language = $value; - $subcfg = array( 'LANGUAGE' => $value ); - $res = TRUE; - break; - case 'NL': - case 'NEWLINECHAR': - $this->nl = $value; - if( 'xcal' == $value ) { - $this->attributeDelimiter = $this->nl; - $this->valueInit = null; - } - else { - $this->attributeDelimiter = ';'; - $this->valueInit = ':'; - } - $subcfg = array( 'NL' => $value ); - $res = TRUE; - break; - case 'TZID': - $this->dtzid = $value; - $subcfg = array( 'TZID' => $value ); - $res = TRUE; - break; - case 'UNIQUE_ID': - $value = trim( $value ); - $this->unique_id = $value; - $this->_makeProdid(); - $subcfg = array( 'UNIQUE_ID' => $value ); - $res = TRUE; - break; - case 'URL': - /* remote file - URL */ - $value = trim( $value ); - $value = str_replace( 'HTTP://', 'http://', $value ); - $value = str_replace( 'WEBCAL://', 'http://', $value ); - $value = str_replace( 'webcal://', 'http://', $value ); - $this->url = $value; - $this->directory = null; - $parts = pathinfo( $value ); - return $this->setConfig( 'filename', $parts['basename'] ); - break; - default: // any unvalid config key.. . - return TRUE; - } - if( !$res ) return FALSE; - if( isset( $subcfg ) && !empty( $this->components )) { - foreach( $subcfg as $cfgkey => $cfgvalue ) { - foreach( $this->components as $cix => $component ) { - $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE ); - if( !$res ) - break 2; - $this->components[$cix] = $component->copy(); // PHP4 compliant - } - } - } - return $res; - } -/*********************************************************************************/ -/** - * add calendar component to container - * - * alias to setComponent - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 1.x.x - 2007-04-24 - * @param object $component calendar component - * @return void - */ - function addComponent( $component ) { - $this->setComponent( $component ); - } -/** - * delete calendar component from container - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param mixed $arg1 ordno / component type / component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return void - */ - function deleteComponent( $arg1, $arg2=FALSE ) { - $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { - $argType = 'INDEX'; - $index = (int) $arg1 - 1; - } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { - $argType = strtolower( $arg1 ); - $index = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0; - } - $cix1dC = 0; - foreach ( $this->components as $cix => $component) { - if( empty( $component )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) { - unset( $this->components[$cix] ); - return TRUE; - } - elseif( $argType == $component->objName ) { - if( $index == $cix1dC ) { - unset( $this->components[$cix] ); - return TRUE; - } - $cix1dC++; - } - elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { - unset( $this->components[$cix] ); - return TRUE; - } - } - return FALSE; - } -/** - * get calendar component from container - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.1 - 2011-04-16 - * @param mixed $arg1 optional, ordno/component type/ component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return object - */ - function getComponent( $arg1=FALSE, $arg2=FALSE ) { - $index = $argType = null; - if ( !$arg1 ) { // first or next in component chain - $argType = 'INDEX'; - $index = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1; - } - elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain - $argType = 'INDEX'; - $index = (int) $arg1; - unset( $this->compix ); - } - elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) - $arg2 = implode( '-', array_keys( $arg1 )); - $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1; - $dateProps = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' ); - $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' ); - } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name - unset( $this->compix['INDEX'] ); - $argType = strtolower( $arg1 ); - if( !$arg2 ) - $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; - elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 )) - $index = (int) $arg2; - } - elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument - if( !$arg2 ) - $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1; - elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 )) - $index = (int) $arg2; - } - if( isset( $index )) - $index -= 1; - $ckeys = array_keys( $this->components ); - if( !empty( $index) && ( $index > end( $ckeys ))) - return FALSE; - $cix1gC = 0; - foreach ( $this->components as $cix => $component) { - if( empty( $component )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) - return $component->copy(); - elseif( $argType == $component->objName ) { - if( $index == $cix1gC ) - return $component->copy(); - $cix1gC++; - } - elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) - $hit = FALSE; - foreach( $arg1 as $pName => $pValue ) { - $pName = strtoupper( $pName ); - if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps )) - continue; - if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur - $propValues = array(); - $component->_getProperties( $pName, $propValues ); - $propValues = array_keys( $propValues ); - $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE; - continue; - } // end if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur - if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency - $hit = FALSE; // missing property - continue; - } - if( 'SUMMARY' == $pName ) { // exists within (any case) - $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE; - continue; - } - if( in_array( strtoupper( $pName ), $dateProps )) { - $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] ); - if( 8 < strlen( $pValue )) { - if( isset( $value['hour'] )) { - if( 'T' == substr( $pValue, 8, 1 )) - $pValue = str_replace( 'T', '', $pValue ); - $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] ); - } - else - $pValue = substr( $pValue, 0, 8 ); - } - $hit = ( $pValue == $valuedate ) ? TRUE : FALSE; - continue; - } - elseif( !is_array( $value )) - $value = array( $value ); - foreach( $value as $part ) { - $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part ); - foreach( $part as $subPart ) { - if( $pValue == $subPart ) { - $hit = TRUE; - continue 2; - } - } - } - $hit = FALSE; // no hit in property - } // end foreach( $arg1 as $pName => $pValue ) - if( $hit ) { - if( $index == $cix1gC ) - return $component->copy(); - $cix1gC++; - } - } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) - elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID - if( $index == $cix1gC ) - return $component->copy(); - $cix1gC++; - } - } // end foreach ( $this->components.. . - /* not found.. . */ - unset( $this->compix ); - return FALSE; - } -/** - * create new calendar component, already included within calendar - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.33 - 2011-01-03 - * @param string $compType component type - * @return object (reference) - */ - function & newComponent( $compType ) { - $config = $this->getConfig(); - $keys = array_keys( $this->components ); - $ix = end( $keys) + 1; - switch( strtoupper( $compType )) { - case 'EVENT': - case 'VEVENT': - $this->components[$ix] = new vevent( $config ); - break; - case 'TODO': - case 'VTODO': - $this->components[$ix] = new vtodo( $config ); - break; - case 'JOURNAL': - case 'VJOURNAL': - $this->components[$ix] = new vjournal( $config ); - break; - case 'FREEBUSY': - case 'VFREEBUSY': - $this->components[$ix] = new vfreebusy( $config ); - break; - case 'TIMEZONE': - case 'VTIMEZONE': - array_unshift( $this->components, new vtimezone( $config )); - $ix = 0; - break; - default: - return FALSE; - } - return $this->components[$ix]; - } -/** - * select components from calendar on date or selectOption basis - * - * Ensure DTSTART is set for every component. - * No date controls occurs. - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.22 - 2012-02-13 - * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions ( *[ => ] ) - * @param int $startM optional, start Month, default current Month - * @param int $startD optional, start Day, default current Day - * @param int $endY optional, end Year, default $startY - * @param int $endY optional, end Month, default $startM - * @param int $endY optional, end Day, default $startD - * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s) - * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][] - * TRUE => output : array[] (ignores split) - * @param bool $any optional, TRUE (default) - select component(-s) that occurs within period - * FALSE - only component(-s) that starts within period - * @param bool $split optional, TRUE (default) - one component copy every DAY it occurs during the - * period (implies flat=FALSE) - * FALSE - one occurance of component only in output array - * @return array or FALSE - */ - function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) { - /* check if empty calendar */ - if( 0 >= count( $this->components )) return FALSE; - if( is_array( $startY )) - return $this->selectComponents2( $startY ); - /* check default dates */ - if( !$startY ) $startY = date( 'Y' ); - if( !$startM ) $startM = date( 'm' ); - if( !$startD ) $startD = date( 'd' ); - $startDate = mktime( 0, 0, 0, $startM, $startD, $startY ); - if( !$endY ) $endY = $startY; - if( !$endM ) $endM = $startM; - if( !$endD ) $endD = $startD; - $endDate = mktime( 23, 59, 59, $endM, $endD, $endY ); -//echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."
\n"; $tcnt = 0;// test ### - /* check component types */ - $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ); - if( is_array( $cType )) { - foreach( $cType as $cix => $theType ) { - $cType[$cix] = $theType = strtolower( $theType ); - if( !in_array( $theType, $validTypes )) - $cType[$cix] = 'vevent'; - } - $cType = array_unique( $cType ); - } - elseif( !empty( $cType )) { - $cType = strtolower( $cType ); - if( !in_array( $cType, $validTypes )) - $cType = array( 'vevent' ); - else - $cType = array( $cType ); - } - else - $cType = $validTypes; - if( 0 >= count( $cType )) - $cType = $validTypes; - if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination - $split = FALSE; - if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination - $split = FALSE; - /* iterate components */ - $result = array(); - foreach ( $this->components as $cix => $component ) { - if( empty( $component )) continue; - unset( $start ); - /* deselect unvalid type components */ - if( !in_array( $component->objName, $cType )) - continue; - $start = $component->getProperty( 'dtstart' ); - /* select due when dtstart is missing */ - if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' )))) - continue; - if( empty( $start )) - continue; - $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE; - unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up - $startWdate = iCalUtilityFunctions::_date2timestamp( $start ); - $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; - /* get end date from dtend/due/duration properties */ - $end = $component->getProperty( 'dtend' ); - if( !empty( $end )) { - $dtendExist = TRUE; - $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; - } - if( empty( $end ) && ( $component->objName == 'vtodo' )) { - $end = $component->getProperty( 'due' ); - if( !empty( $end )) { - $dueExist = TRUE; - $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; - } - } - if( !empty( $end ) && !isset( $end['hour'] )) { - /* a DTEND without time part regards an event that ends the day before, - for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */ - $endAllDayEvent = TRUE; - $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] ); - $end['year'] = date( 'Y', $endWdate ); - $end['month'] = date( 'm', $endWdate ); - $end['day'] = date( 'd', $endWdate ); - $end['hour'] = 23; - $end['min'] = $end['sec'] = 59; - } - if( empty( $end )) { - $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format - if( !empty( $end )) - $durationExist = TRUE; - $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; -// if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."
\n"; // test ### - } - if( empty( $end )) { // assume one day duration if missing end date - $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 ); - } -// if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."
\n"; // test ### - $endWdate = iCalUtilityFunctions::_date2timestamp( $end ); - if( $endWdate < $startWdate ) { // MUST be after start date!! - $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 ); - $endWdate = iCalUtilityFunctions::_date2timestamp( $end ); - } - $rdurWsecs = $endWdate - $startWdate; // compute event (component) duration in seconds - /* make a list of optional exclude dates for component occurence from exrule and exdate */ - $exdatelist = array(); - $workstart = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6); - $workend = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6); - while( FALSE !== ( $exrule = $component->getProperty( 'exrule' ))) // check exrule - iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend ); - while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) { // check exdate - foreach( $exdate as $theExdate ) { - $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate ); - $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!! - if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate )) - $exdatelist[$exWdate] = TRUE; - } // end - foreach( $exdate as $theExdate ) - } // end - check exdate - $compUID = $component->getProperty( 'UID' ); - /* check recurrence-id (with sequence), remove hit with reccurr-id date */ - if(( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) && - ( FALSE !== ( $sequence = $component->getProperty( 'sequence' ))) ) { - $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid ); - $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!! - $endD = $recurrid + $rdurWsecs; - do { - if( date( 'Ymd', $startWdate ) != date( 'Ymd', $recurrid )) - $exdatelist[$recurrid] = TRUE; // exclude all other days than startdate - $wd = getdate( $recurrid ); - if( isset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] )) - unset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ); // remove from output, dtstart etc added below - if( $split && ( $recurrid <= $endD )) - $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ) + 1, date( 'Y', $recurrid )); // step one day - else - break; - } while( TRUE ); - } // end recurrence-id test - /* select only components with.. . */ - if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period - ( $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) { // occurs within the period - /* add the selected component (WITHIN valid dates) to output array */ - if( $flat ) { // any=true/false, ignores split - if( !$recurrid ) - $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id) - } - elseif( $split ) { // split the original component - if( $endWdate > $endDate ) - $endWdate = $endDate; // use period end date - $rstart = $startWdate; - if( $rstart < $startDate ) - $rstart = $startDate; // use period start date - $startYMD = date( 'Ymd', $rstart ); - $endYMD = date( 'Ymd', $endWdate ); - $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate - $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist - $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day - continue; - } - if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart - $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ))); - else - $datestring = date( $startDateFormat, $rstart ); - if( isset( $start['tz'] )) - $datestring .= ' '.$start['tz']; -// echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
";$component->setProperty( 'X-CNT', $tcnt ); // test ### - $component->setProperty( 'X-CURRENT-DTSTART', $datestring ); - if( $dtendExist || $dueExist || $durationExist ) { - if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day - $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); - else - $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - if( $endAllDayEvent && $dtendExist ) - $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day - $datestring = date( $endDateFormat, $tend ); - if( isset( $end['tz'] )) - $datestring .= ' '.$end['tz']; - $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; - $component->setProperty( $propName, $datestring ); - } // end if( $dtendExist || $dueExist || $durationExist ) - $wd = getdate( $rstart ); - $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output - $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day - } // end while( $rstart <= $endWdate ) - } // end if( $split ) - else use component date - elseif( $recurrid && !$flat && !$any && !$split ) - $continue = TRUE; - else { // !$flat && !$split, i.e. no flat array and DTSTART within period - $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!! - if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist - $wd = getdate( $startWdate ); - $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output - } - } - } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) - - /* if 'any' components, check components with reccurrence rules, removing all excluding dates */ - if( TRUE === $any ) { - /* make a list of optional repeating dates for component occurence, rrule, rdate */ - $recurlist = array(); - while( FALSE !== ( $rrule = $component->getProperty( 'rrule' ))) // check rrule - iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend ); - foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp - $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds - while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) { // check rdate - foreach( $rdate as $theRdate ) { - if( is_array( $theRdate ) && ( 2 == count( $theRdate )) && // all days within PERIOD - array_key_exists( '0', $theRdate ) && array_key_exists( '1', $theRdate )) { - $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] ); - if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate )) - continue; - if( isset( $theRdate[1]['year'] )) // date-date period - $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] ); - else { // date-duration period - $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] ); - $rend = iCalUtilityFunctions::_date2timestamp( $rend ); - } - while( $rstart < $rend ) { - $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds - $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day - } - } // PERIOD end - else { // single date - $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate ); - if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate )) - $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds - } - } - } // end - check rdate - if( 0 < count( $recurlist )) { - ksort( $recurlist ); - $xRecurrence = 1; - $component2 = $component->copy(); - $compUID = $component2->getProperty( 'UID' ); - foreach( $recurlist as $recurkey => $durvalue ) { -// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."
\n"; // test ###; - if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period - continue; - $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!! - if( isset( $exdatelist[$checkDate] )) // check excluded dates - continue; - if( $startWdate >= $recurkey ) // exclude component start date - continue; - $rstart = $recurkey; - $rend = $recurkey + $durvalue; - /* add repeating components within valid dates to output array, only start date set */ - if( $flat ) { - if( !isset( $result[$compUID] )) // only one comp - $result[$compUID] = $component2->copy(); // copy to output - } - /* add repeating components within valid dates to output array, one each day */ - elseif( $split ) { - if( $rend > $endDate ) - $rend = $endDate; - $startYMD = date( 'Ymd', $rstart ); - $endYMD = date( 'Ymd', $rend ); -// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."
\n"; // test ###; - while( $rstart <= $rend ) { // iterate.. . - $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - if( isset( $exdatelist[$checkDate] )) // exclude any recurrence START date, found in exdatelist - break; -// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."
"; // test ###; - if( $rstart >= $startDate ) { // date after dtstart - if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart - $datestring = date( $startDateFormat, $checkDate ); - else - $datestring = date( $startDateFormat, $rstart ); - if( isset( $start['tz'] )) - $datestring .= ' '.$start['tz']; -//echo "X-CURRENT-DTSTART 1 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
";$component2->setProperty( 'X-CNT', $tcnt ); // test ### - $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); - if( $dtendExist || $dueExist || $durationExist ) { - if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day - $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); - else - $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - if( $endAllDayEvent && $dtendExist ) - $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day - $datestring = date( $endDateFormat, $tend ); - if( isset( $end['tz'] )) - $datestring .= ' '.$end['tz']; - $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; - $component2->setProperty( $propName, $datestring ); - } // end if( $dtendExist || $dueExist || $durationExist ) - $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); - $wd = getdate( $rstart ); - $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output - } // end if( $checkDate > $startYMD ) { // date after dtstart - $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day - } // end while( $rstart <= $rend ) - $xRecurrence += 1; - } // end elseif( $split ) - elseif( $rstart >= $startDate ) { // date within period //* flat=FALSE && split=FALSE => one comp every recur startdate *// - $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! - if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist - $xRecurrence += 1; - $datestring = date( $startDateFormat, $rstart ); - if( isset( $start['tz'] )) - $datestring .= ' '.$start['tz']; -//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
";$component2->setProperty( 'X-CNT', $tcnt ); // test ### - $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); - if( $dtendExist || $dueExist || $durationExist ) { - $tend = $rstart + $rdurWsecs; - if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate )) - $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend )); - else - $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!! - if( $endAllDayEvent && $dtendExist ) - $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day - $datestring = date( $endDateFormat, $tend ); - if( isset( $end['tz'] )) - $datestring .= ' '.$end['tz']; - $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; - $component2->setProperty( $propName, $datestring ); - } // end if( $dtendExist || $dueExist || $durationExist ) - $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); - $wd = getdate( $rstart ); - $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output - } // end if( !isset( $exdatelist[$checkDate] )) - } // end elseif( $rstart >= $startDate ) - } // end foreach( $recurlist as $recurkey => $durvalue ) - } // end if( 0 < count( $recurlist )) - /* deselect components with startdate/enddate not within period */ - if(( $endWdate < $startDate ) || ( $startWdate > $endDate )) - continue; - } // end if( TRUE === $any ) - } // end foreach ( $this->components as $cix => $component ) - if( 0 >= count( $result )) return FALSE; - elseif( !$flat ) { - foreach( $result as $y => $yeararr ) { - foreach( $yeararr as $m => $montharr ) { - foreach( $montharr as $d => $dayarr ) { - if( empty( $result[$y][$m][$d] )) - unset( $result[$y][$m][$d] ); - else - $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. . - } - if( empty( $result[$y][$m] )) - unset( $result[$y][$m] ); - else - ksort( $result[$y][$m] ); - } - if( empty( $result[$y] )) - unset( $result[$y] ); - else - ksort( $result[$y] ); - } - if( empty( $result )) - unset( $result ); - else - ksort( $result ); - } // end elseif( !$flat ) - if( 0 >= count( $result )) - return FALSE; - return $result; - } -/** - * select components from calendar on based on Categories, Location, Resources and/or Summary - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-05-03 - * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName) - * @return array - */ - function selectComponents2( $selectOptions ) { - $output = array(); - $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' ); - foreach( $this->components as $cix => $component3 ) { - if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) - continue; - $uid = $component3->getProperty( 'UID' ); - foreach( $selectOptions as $propName => $pvalue ) { - $propName = strtoupper( $propName ); - if( !in_array( $propName, $allowedProperties )) - continue; - if( !is_array( $pvalue )) - $pvalue = array( $pvalue ); - if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) { - $output[] = $component3->copy(); - continue; - } - elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { - $propValues = array(); - $component3->_getProperties( $propName, $propValues ); - $propValues = array_keys( $propValues ); - foreach( $pvalue as $theValue ) { - if( in_array( $theValue, $propValues ) && !isset( $output[$uid] )) { - $output[$uid] = $component3->copy(); - break; - } - } - continue; - } // end elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) - elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence - continue; - if( is_array( $d )) { - foreach( $d as $part ) { - if( in_array( $part, $pvalue ) && !isset( $output[$uid] )) - $output[$uid] = $component3->copy(); - } - } - elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) { - foreach( $pvalue as $pval ) { - if( FALSE !== stripos( $d, $pval )) { - $output[$uid] = $component3->copy(); - break; - } - } - } - elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] )) - $output[$uid] = $component3->copy(); - } // end foreach( $selectOptions as $propName => $pvalue ) { - } // end foreach( $this->components as $cix => $component3 ) { - if( !empty( $output )) { - ksort( $output ); - $output = array_values( $output ); - } - return $output; - } -/** - * add calendar component to container - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param object $component calendar component - * @param mixed $arg1 optional, ordno/component type/ component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return void - */ - function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) { - $component->setConfig( $this->getConfig(), FALSE, TRUE ); - if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) { - /* make sure dtstamp and uid is set */ - $dummy1 = $component->getProperty( 'dtstamp' ); - $dummy2 = $component->getProperty( 'uid' ); - } - if( !$arg1 ) { // plain insert, last in chain - $this->components[] = $component->copy(); - return TRUE; - } - $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { // index insert/replace - $argType = 'INDEX'; - $index = (int) $arg1 - 1; - } - elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) { - $argType = strtolower( $arg1 ); - $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0; - } - // else if arg1 is set, arg1 must be an UID - $cix1sC = 0; - foreach ( $this->components as $cix => $component2) { - if( empty( $component2 )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace - $this->components[$cix] = $component->copy(); - return TRUE; - } - elseif( $argType == $component2->objName ) { // component Type index insert/replace - if( $index == $cix1sC ) { - $this->components[$cix] = $component->copy(); - return TRUE; - } - $cix1sC++; - } - elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace - $this->components[$cix] = $component->copy(); - return TRUE; - } - } - /* arg1=index and not found.. . insert at index .. .*/ - if( 'INDEX' == $argType ) { - $this->components[$index] = $component->copy(); - ksort( $this->components, SORT_NUMERIC ); - } - else /* not found.. . insert last in chain anyway .. .*/ - $this->components[] = $component->copy(); - return TRUE; - } -/** - * sort iCal compoments - * - * ascending sort on properties (if exist) x-current-dtstart, dtstart, - * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid - * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.4 - 2011-06-02 - * @param string $sortArg, optional - * @return void - * - */ - function sort( $sortArg=FALSE ) { - if( is_array( $this->components )) { - if( $sortArg ) { - $sortArg = strtoupper( $sortArg ); - if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' ))) - $sortArg = FALSE; - } - /* set sort parameters for each component */ - foreach( $this->components as $cix => & $c ) { - $c->srtk = array( '0', '0', '0', '0' ); - if( 'vtimezone' == $c->objName ) { - if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' ))) - $c->srtk[0] = 0; - continue; - } - elseif( $sortArg ) { - if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES' == $sortArg )) { - $propValues = array(); - $c->_getProperties( $sortArg, $propValues ); - $c->srtk[0] = reset( array_keys( $propValues )); - } - elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) - $c->srtk[0] = $d; - continue; - } - if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) { - $c->srtk[0] = iCalUtilityFunctions::_date_time_string( $d[1] ); - unset( $c->srtk[0]['unparsedtext'] ); - } - elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' ))) - $c->srtk[1] = 0; // sortkey 0 : dtstart - if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) { - $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] ); // sortkey 1 : dtend/due(/dtstart+duration) - unset( $c->srtk[1]['unparsedtext'] ); - } - elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) { - if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) { - $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] ); - unset( $c->srtk[1]['unparsedtext'] ); - } - elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' ))) - if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE ))) - $c->srtk[1] = 0; - } - if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' ))) // sortkey 2 : created/dtstamp - if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' ))) - $c->srtk[2] = 0; - if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' ))) // sortkey 3 : uid - $c->srtk[3] = 0; - } // end foreach( $this->components as & $c - /* sort */ - usort( $this->components, array( $this, '_cmpfcn' )); - } - } - function _cmpfcn( $a, $b ) { - if( empty( $a )) return -1; - if( empty( $b )) return 1; - if( 'vtimezone' == $a->objName ) { - if( 'vtimezone' != $b->objName ) return -1; - elseif( $a->srtk[0] <= $b->srtk[0] ) return -1; - else return 1; - } - elseif( 'vtimezone' == $b->objName ) return 1; - $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' ); - for( $k = 0; $k < 4 ; $k++ ) { - if( empty( $a->srtk[$k] )) return -1; - elseif( empty( $b->srtk[$k] )) return 1; - if( is_array( $a->srtk[$k] )) { - if( is_array( $b->srtk[$k] )) { - foreach( $sortkeys as $key ) { - if ( empty( $a->srtk[$k][$key] )) return -1; - elseif( empty( $b->srtk[$k][$key] )) return 1; - if ( $a->srtk[$k][$key] == $b->srtk[$k][$key]) - continue; - if (( (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] )) - return -1; - elseif(( (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] )) - return 1; - } - } - else return -1; - } - elseif( is_array( $b->srtk[$k] )) return 1; - elseif( $a->srtk[$k] < $b->srtk[$k] ) return -1; - elseif( $a->srtk[$k] > $b->srtk[$k] ) return 1; - } - return 0; - } -/** - * parse iCal text/file into vcalendar, components, properties and parameters - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.10 - 2012-01-31 - * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings - * @return bool FALSE if error occurs during parsing - * - */ - function parse( $unparsedtext=FALSE ) { - $nl = $this->getConfig( 'nl' ); - if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) { - /* directory+filename is set previously via setConfig directory+filename or url */ - if( FALSE === ( $filename = $this->getConfig( 'url' ))) - $filename = $this->getConfig( 'dirfile' ); - /* READ FILE */ - if( FALSE === ( $rows = file_get_contents( $filename ))) - return FALSE; /* err 1 */ - } - elseif( is_array( $unparsedtext )) - $rows = implode( '\n'.$nl, $unparsedtext ); - else - $rows = & $unparsedtext; - /* identify BEGIN:VCALENDAR, MUST be first row */ - if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 ))) - return FALSE; /* err 8 */ - /* fix line folding */ - $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings - $EOLmark = FALSE; - foreach( $eolchars as $eolchar ) { - if( !$EOLmark && ( FALSE !== strpos( $rows, $eolchar ))) { - $rows = str_replace( $eolchar." ", '', $rows ); - $rows = str_replace( $eolchar."\t", '', $rows ); - if( $eolchar != $nl ) - $rows = str_replace( $eolchar, $nl, $rows ); - $EOLmark = TRUE; - } - } - $rows = explode( $nl, $rows ); - /* skip trailing empty lines */ - $lix = count( $rows ) - 1; - while( empty( $rows[$lix] ) && ( 0 < $lix )) - $lix -= 1; - /* identify ending END:VCALENDAR row, MUST be last row */ - if( 'END:VCALENDAR' != strtoupper( substr( $rows[$lix], 0, 13 ))) - return FALSE; /* err 9 */ - if( 3 > count( $rows )) - return FALSE; /* err 10 */ - $comp = & $this; - $calsync = 0; - /* identify components and update unparsed data within component */ - $config = $this->getConfig(); - foreach( $rows as $line ) { - if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) { - $calsync++; - continue; - } - elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) { - $calsync--; - break; - } - elseif( 1 != $calsync ) - return FALSE; /* err 20 */ - elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) { - $this->components[] = $comp->copy(); - continue; - } - if( 'BEGIN:VEVENT' == strtoupper( substr( $line, 0, 12 ))) - $comp = new vevent( $config ); - elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) - $comp = new vfreebusy( $config ); - elseif( 'BEGIN:VJOURNAL' == strtoupper( substr( $line, 0, 14 ))) - $comp = new vjournal( $config ); - elseif( 'BEGIN:VTODO' == strtoupper( substr( $line, 0, 11 ))) - $comp = new vtodo( $config ); - elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) - $comp = new vtimezone( $config ); - else { /* update component with unparsed data */ - $comp->unparsed[] = $line; - } - } // end foreach( $rows as $line ) - unset( $config ); - /* parse data for calendar (this) object */ - if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) { - /* concatenate property values spread over several lines */ - $lastix = -1; - $propnames = array( 'calscale','method','prodid','version','x-' ); - $proprows = array(); - foreach( $this->unparsed as $line ) { - $newProp = FALSE; - foreach ( $propnames as $propname ) { - if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) { - $newProp = TRUE; - break; - } - } - if( $newProp ) { - $newProp = FALSE; - $lastix++; - $proprows[$lastix] = $line; - } - else - $proprows[$lastix] .= '!"#¤%&/()=?'.$line; - } - $paramMStz = array( 'utc-', 'utc+', 'gmt-', 'gmt+' ); - $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ); - $paramProto4 = array( 'crid:', 'news:', 'pres:' ); - foreach( $proprows as $line ) { - $line = str_replace( '!"#¤%&/()=? ', '', $line ); - $line = str_replace( '!"#¤%&/()=?', '', $line ); - if( '\n' == substr( $line, -2 )) - $line = substr( $line, 0, strlen( $line ) - 2 ); - /* get property name */ - $cix = $propname = null; - for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) { - if( in_array( $line[$cix], array( ':', ';' ))) - break; - else - $propname .= $line[$cix]; - } - /* ignore version/prodid properties */ - if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' ))) - continue; - $line = substr( $line, $cix); - /* separate attributes from value */ - $attr = array(); - $attrix = -1; - $strlen = strlen( $line ); - $WithinQuotes = FALSE; - for( $cix=0; $cix < $strlen; $cix++ ) { - if( ( ':' == $line[$cix] ) && - ( substr( $line,$cix, 3 ) != '://' ) && - ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz )) && - ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) && - ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) && - ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' ) && - !$WithinQuotes ) { - $attrEnd = TRUE; - if(( $cix < ( $strlen - 4 )) && - ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr?? - for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) { - if( '://' == substr( $line, $c2ix - 2, 3 )) { - $attrEnd = FALSE; - break; // an URI with a portnr!! - } - } - } - if( $attrEnd) { - $line = substr( $line, ( $cix + 1 )); - break; - } - } - if( '"' == $line[$cix] ) - $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE; - if( ';' == $line[$cix] ) - $attr[++$attrix] = null; - else - $attr[$attrix] .= $line[$cix]; - } - /* make attributes in array format */ - $propattr = array(); - foreach( $attr as $attribute ) { - $attrsplit = explode( '=', $attribute, 2 ); - if( 1 < count( $attrsplit )) - $propattr[$attrsplit[0]] = $attrsplit[1]; - else - $propattr[] = $attribute; - } - /* update Property */ - if( FALSE !== strpos( $line, ',' )) { - $llen = strlen( $line ); - $content = array( 0 => '' ); - $cix = 0; - for( $lix = 0; $lix < $llen; $lix++ ) { - if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) { - $cix++; - $content[$cix] = ''; - } - else - $content[$cix] .= $line[$lix]; - } - if( 1 < count( $content )) { - foreach( $content as $cix => $contentPart ) - $content[$cix] = calendarComponent::_strunrep( $contentPart ); - $this->setProperty( $propname, $content, $propattr ); - continue; - } - else - $line = reset( $content ); - $line = calendarComponent::_strunrep( $line ); - } - $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr ); - } // end - foreach( $this->unparsed.. . - } // end - if( is_array( $this->unparsed.. . - unset( $unparsedtext, $rows, $this->unparsed, $proprows ); - /* parse Components */ - if( is_array( $this->components ) && ( 0 < count( $this->components ))) { - $ckeys = array_keys( $this->components ); - foreach( $ckeys as $ckey ) { - if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) { - $this->components[$ckey]->parse(); - } - } - } - else - return FALSE; /* err 91 or something.. . */ - return TRUE; - } -/*********************************************************************************/ -/** - * creates formatted output for calendar object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @return string - */ - function createCalendar() { - $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = ''; - switch( $this->format ) { - case 'xcal': - $calendarInit = ''.$this->nl. - 'nl. - '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"'; - $calendarStart = '>'.$this->nl.'nl; - break; - } - $calendarStart .= $this->createVersion(); - $calendarStart .= $this->createProdid(); - $calendarStart .= $this->createCalscale(); - $calendarStart .= $this->createMethod(); - if( 'xcal' == $this->format ) - $calendarStart .= '>'.$this->nl; - $calendar .= $this->createXprop(); - - foreach( $this->components as $component ) { - if( empty( $component )) continue; - $component->setConfig( $this->getConfig(), FALSE, TRUE ); - $calendar .= $component->createComponent( $this->xcaldecl ); - } - if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only - $calendarInit .= ' ['; - $old_xcaldecl = array(); - foreach( $this->xcaldecl as $declix => $declPart ) { - if(( 0 < count( $old_xcaldecl)) && - isset( $declPart['uri'] ) && isset( $declPart['external'] ) && - isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) && - ( in_array( $declPart['uri'], $old_xcaldecl['uri'] )) && - ( in_array( $declPart['external'], $old_xcaldecl['external'] ))) - continue; // no duplicate uri and ext. references - if(( 0 < count( $old_xcaldecl)) && - !isset( $declPart['uri'] ) && !isset( $declPart['uri'] ) && - isset( $declPart['ref'] ) && isset( $old_xcaldecl['ref'] ) && - ( in_array( $declPart['ref'], $old_xcaldecl['ref'] ))) - continue; // no duplicate element declarations - $calendarxCaldecl .= $this->nl.' $declValue ) { - switch( $declKey ) { // index - case 'xmldecl': // no 1 - $calendarxCaldecl .= $declValue.' '; - break; - case 'uri': // no 2 - $calendarxCaldecl .= $declValue.' '; - $old_xcaldecl['uri'][] = $declValue; - break; - case 'ref': // no 3 - $calendarxCaldecl .= $declValue.' '; - $old_xcaldecl['ref'][] = $declValue; - break; - case 'external': // no 4 - $calendarxCaldecl .= '"'.$declValue.'" '; - $old_xcaldecl['external'][] = $declValue; - break; - case 'type': // no 5 - $calendarxCaldecl .= $declValue.' '; - break; - case 'type2': // no 6 - $calendarxCaldecl .= $declValue; - break; - } - } - $calendarxCaldecl .= '>'; - } - $calendarxCaldecl .= $this->nl.']'; - } - switch( $this->format ) { - case 'xcal': - $calendar .= ''.$this->nl; - break; - default: - $calendar .= 'END:VCALENDAR'.$this->nl; - break; - } - return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar; - } -/** - * a HTTP redirect header is sent with created, updated and/or parsed calendar - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.24 - 2011-12-23 - * @param bool $utf8Encode - * @param bool $gzip - * @return redirect - */ - function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) { - $filename = $this->getConfig( 'filename' ); - $output = $this->createCalendar(); - if( $utf8Encode ) - $output = utf8_encode( $output ); - if( $gzip ) { - $output = gzencode( $output, 9 ); - header( 'Content-Encoding: gzip' ); - header( 'Vary: *' ); - header( 'Content-Length: '.strlen( $output )); - } - if( 'xcal' == $this->format ) - header( 'Content-Type: application/calendar+xml; charset=utf-8' ); - else - header( 'Content-Type: text/calendar; charset=utf-8' ); - header( 'Content-Disposition: attachment; filename="'.$filename.'"' ); - header( 'Cache-Control: max-age=10' ); - die( $output ); - } -/** - * save content in a file - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.2.12 - 2007-12-30 - * @param string $directory optional - * @param string $filename optional - * @param string $delimiter optional - * @return bool - */ - function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) { - if( $directory ) - $this->setConfig( 'directory', $directory ); - if( $filename ) - $this->setConfig( 'filename', $filename ); - if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR )) - $this->setConfig( 'delimiter', $delimiter ); - if( FALSE === ( $dirfile = $this->getConfig( 'url' ))) - $dirfile = $this->getConfig( 'dirfile' ); - $iCalFile = @fopen( $dirfile, 'w' ); - if( $iCalFile ) { - if( FALSE === fwrite( $iCalFile, $this->createCalendar() )) - return FALSE; - fclose( $iCalFile ); - return TRUE; - } - else - return FALSE; - } -/** - * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent - * else FALSE is returned - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.2.12 - 2007-10-28 - * @param string $directory optional alt. int timeout - * @param string $filename optional - * @param string $delimiter optional - * @param int timeout optional, default 3600 sec - * @return redirect/FALSE - */ - function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) { - if ( $directory && ctype_digit( (string) $directory ) && !$filename ) { - $timeout = (int) $directory; - $directory = FALSE; - } - if( $directory ) - $this->setConfig( 'directory', $directory ); - if( $filename ) - $this->setConfig( 'filename', $filename ); - if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR )) - $this->setConfig( 'delimiter', $delimiter ); - $filesize = $this->getConfig( 'filesize' ); - if( 0 >= $filesize ) - return FALSE; - $dirfile = $this->getConfig( 'dirfile' ); - if( time() - filemtime( $dirfile ) < $timeout) { - clearstatcache(); - $dirfile = $this->getConfig( 'dirfile' ); - $filename = $this->getConfig( 'filename' ); -// if( headers_sent( $filename, $linenum )) -// die( "Headers already sent in $filename on line $linenum\n" ); - if( 'xcal' == $this->format ) - header( 'Content-Type: application/calendar+xml; charset=utf-8' ); - else - header( 'Content-Type: text/calendar; charset=utf-8' ); - header( 'Content-Length: '.$filesize ); - header( 'Content-Disposition: attachment; filename="'.$filename.'"' ); - header( 'Cache-Control: max-age=10' ); - $fp = @fopen( $dirfile, 'r' ); - if( $fp ) { - fpassthru( $fp ); - fclose( $fp ); - } - die(); - } - else - return FALSE; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * abstract class for calendar components - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - */ -class calendarComponent { - // component property variables - var $uid; - var $dtstamp; - - // component config variables - var $allowEmpty; - var $language; - var $nl; - var $unique_id; - var $format; - var $objName; // created automatically at instance creation - var $dtzid; // default (local) timezone - // component internal variables - var $componentStart1; - var $componentStart2; - var $componentEnd1; - var $componentEnd2; - var $elementStart1; - var $elementStart2; - var $elementEnd1; - var $elementEnd2; - var $intAttrDelimiter; - var $attributeDelimiter; - var $valueInit; - // component xCal declaration container - var $xcaldecl; -/** - * constructor for calendar component object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-17 - */ - function calendarComponent() { - $this->objName = ( isset( $this->timezonetype )) ? - strtolower( $this->timezonetype ) : get_class ( $this ); - $this->uid = array(); - $this->dtstamp = array(); - - $this->language = null; - $this->nl = null; - $this->unique_id = null; - $this->format = null; - $this->dtzid = null; - $this->allowEmpty = TRUE; - $this->xcaldecl = array(); - - $this->_createFormat(); - $this->_makeDtstamp(); - } -/*********************************************************************************/ -/** - * Property Name: ACTION - */ -/** - * creates formatted output for calendar component property action - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createAction() { - if( empty( $this->action )) return FALSE; - if( empty( $this->action['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE; - $attributes = $this->_createParams( $this->action['params'] ); - return $this->_createElement( 'ACTION', $attributes, $this->action['value'] ); - } -/** - * set calendar component property action - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE" - * @param mixed $params - * @return bool - */ - function setAction( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: ATTACH - */ -/** - * creates formatted output for calendar component property attach - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.16 - 2012-02-04 - * @return string - */ - function createAttach() { - if( empty( $this->attach )) return FALSE; - $output = null; - foreach( $this->attach as $attachPart ) { - if( !empty( $attachPart['value'] )) { - $attributes = $this->_createParams( $attachPart['params'] ); - if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) { - $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes ); - $str = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value']; - $output = substr( $str, 0, 75 ).$this->nl; - $str = substr( $str, 75 ); - $output .= ' '.chunk_split( $str, 74, $this->nl.' ' ); - if( ' ' == substr( $output, -1 )) - $output = rtrim( $output ); - if( $this->nl != substr( $output, ( 0 - strlen( $this->nl )))) - $output .= $this->nl; - return $output; - } - $output .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] ); - } - elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' ); - } - return $output; - } -/** - * set calendar component property attach - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-06 - * @param string $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setAttach( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: ATTENDEE - */ -/** - * creates formatted output for calendar component property attendee - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.12 - 2012-01-31 - * @return string - */ - function createAttendee() { - if( empty( $this->attendee )) return FALSE; - $output = null; - foreach( $this->attendee as $attendeePart ) { // start foreach 1 - if( empty( $attendeePart['value'] )) { - if( $this->getConfig( 'allowEmpty' )) - $output .= $this->_createElement( 'ATTENDEE' ); - continue; - } - $attendee1 = $attendee2 = null; - foreach( $attendeePart as $paramlabel => $paramvalue ) { // start foreach 2 - if( 'value' == $paramlabel ) - $attendee2 .= $paramvalue; - elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif - $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' ); - foreach( $paramvalue as $pKey => $pValue ) { // fix (opt) quotes - if( is_array( $pValue ) || in_array( $pKey, $mParams )) - continue; - if(( FALSE !== strpos( $pValue, ':' )) || - ( FALSE !== strpos( $pValue, ';' )) || - ( FALSE !== strpos( $pValue, ',' ))) - $paramvalue[$pKey] = '"'.$pValue.'"'; - } - // set attenddee parameters in rfc2445 order - if( isset( $paramvalue['CUTYPE'] )) - $attendee1 .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE']; - if( isset( $paramvalue['MEMBER'] )) { - $attendee1 .= $this->intAttrDelimiter.'MEMBER='; - foreach( $paramvalue['MEMBER'] as $cix => $opv ) - $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; - } - if( isset( $paramvalue['ROLE'] )) - $attendee1 .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE']; - if( isset( $paramvalue['PARTSTAT'] )) - $attendee1 .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT']; - if( isset( $paramvalue['RSVP'] )) - $attendee1 .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP']; - if( isset( $paramvalue['DELEGATED-TO'] )) { - $attendee1 .= $this->intAttrDelimiter.'DELEGATED-TO='; - foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv ) - $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; - } - if( isset( $paramvalue['DELEGATED-FROM'] )) { - $attendee1 .= $this->intAttrDelimiter.'DELEGATED-FROM='; - foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv ) - $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ; - } - if( isset( $paramvalue['SENT-BY'] )) - $attendee1 .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY']; - if( isset( $paramvalue['CN'] )) - $attendee1 .= $this->intAttrDelimiter.'CN='.$paramvalue['CN']; - if( isset( $paramvalue['DIR'] )) { - $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : ''; - $attendee1 .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim; - } - if( isset( $paramvalue['LANGUAGE'] )) - $attendee1 .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE']; - $xparams = array(); - foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3 - if( ctype_digit( (string) $optparamlabel )) { - $xparams[] = $optparamvalue; - continue; - } - if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' ))) - $xparams[$optparamlabel] = $optparamvalue; - } // end foreach 3 - ksort( $xparams, SORT_STRING ); - foreach( $xparams as $paramKey => $paramValue ) { - if( ctype_digit( (string) $paramKey )) - $attendee1 .= $this->intAttrDelimiter.$paramValue; - else - $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue"; - } // end foreach 3 - } // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) - } // end foreach 2 - $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 ); - } // end foreach 1 - return $output; - } -/** - * set calendar component property attach - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.17 - 2012-02-03 - * @param string $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setAttendee( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero:// may exist.. . also in params - if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) - $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos ); - elseif( !empty( $value )) - $value = 'MAILTO:'.$value; - $params2 = array(); - if( is_array($params )) { - $optarrays = array(); - foreach( $params as $optparamlabel => $optparamvalue ) { - $optparamlabel = strtoupper( $optparamlabel ); - switch( $optparamlabel ) { - case 'MEMBER': - case 'DELEGATED-TO': - case 'DELEGATED-FROM': - if( !is_array( $optparamvalue )) - $optparamvalue = array( $optparamvalue ); - foreach( $optparamvalue as $part ) { - $part = trim( $part ); - if(( '"' == substr( $part, 0, 1 )) && - ( '"' == substr( $part, -1 ))) - $part = substr( $part, 1, ( strlen( $part ) - 2 )); - if( 'mailto:' != strtolower( substr( $part, 0, 7 ))) - $part = "MAILTO:$part"; - else - $part = 'MAILTO:'.substr( $part, 7 ); - $optarrays[$optparamlabel][] = $part; - } - break; - default: - if(( '"' == substr( $optparamvalue, 0, 1 )) && - ( '"' == substr( $optparamvalue, -1 ))) - $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 )); - if( 'SENT-BY' == $optparamlabel ) { - if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 ))) - $optparamvalue = "MAILTO:$optparamvalue"; - else - $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 ); - } - $params2[$optparamlabel] = $optparamvalue; - break; - } // end switch( $optparamlabel.. . - } // end foreach( $optparam.. . - foreach( $optarrays as $optparamlabel => $optparams ) - $params2[$optparamlabel] = $optparams; - } - // remove defaults - iCalUtilityFunctions::_existRem( $params2, 'CUTYPE', 'INDIVIDUAL' ); - iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' ); - iCalUtilityFunctions::_existRem( $params2, 'ROLE', 'REQ-PARTICIPANT' ); - iCalUtilityFunctions::_existRem( $params2, 'RSVP', 'FALSE' ); - // check language setting - if( isset( $params2['CN' ] )) { - $lang = $this->getConfig( 'language' ); - if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang )) - $params2['LANGUAGE' ] = $lang; - } - iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: CATEGORIES - */ -/** - * creates formatted output for calendar component property categories - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createCategories() { - if( empty( $this->categories )) return FALSE; - $output = null; - foreach( $this->categories as $category ) { - if( empty( $category['value'] )) { - if ( $this->getConfig( 'allowEmpty' )) - $output .= $this->_createElement( 'CATEGORIES' ); - continue; - } - $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' )); - if( is_array( $category['value'] )) { - foreach( $category['value'] as $cix => $categoryPart ) - $category['value'][$cix] = $this->_strrep( $categoryPart ); - $content = implode( ',', $category['value'] ); - } - else - $content = $this->_strrep( $category['value'] ); - $output .= $this->_createElement( 'CATEGORIES', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property categories - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-06 - * @param mixed $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setCategories( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: CLASS - */ -/** - * creates formatted output for calendar component property class - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 0.9.7 - 2006-11-20 - * @return string - */ - function createClass() { - if( empty( $this->class )) return FALSE; - if( empty( $this->class['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE; - $attributes = $this->_createParams( $this->class['params'] ); - return $this->_createElement( 'CLASS', $attributes, $this->class['value'] ); - } -/** - * set calendar component property class - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name - * @param array $params optional - * @return bool - */ - function setClass( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: COMMENT - */ -/** - * creates formatted output for calendar component property comment - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createComment() { - if( empty( $this->comment )) return FALSE; - $output = null; - foreach( $this->comment as $commentPart ) { - if( empty( $commentPart['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' ); - continue; - } - $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' )); - $content = $this->_strrep( $commentPart['value'] ); - $output .= $this->_createElement( 'COMMENT', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property comment - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-06 - * @param string $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setComment( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: COMPLETED - */ -/** - * creates formatted output for calendar component property completed - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createCompleted( ) { - if( empty( $this->completed )) return FALSE; - if( !isset( $this->completed['value']['year'] ) && - !isset( $this->completed['value']['month'] ) && - !isset( $this->completed['value']['day'] ) && - !isset( $this->completed['value']['hour'] ) && - !isset( $this->completed['value']['min'] ) && - !isset( $this->completed['value']['sec'] )) - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'COMPLETED' ); - else return FALSE; - $formatted = iCalUtilityFunctions::_format_date_time( $this->completed['value'], 7 ); - $attributes = $this->_createParams( $this->completed['params'] ); - return $this->_createElement( 'COMPLETED', $attributes, $formatted ); - } -/** - * set calendar component property completed - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return bool - */ - function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - if( empty( $year )) { - if( $this->getConfig( 'allowEmpty' )) { - $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } - else - return FALSE; - } - $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: CONTACT - */ -/** - * creates formatted output for calendar component property contact - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @return string - */ - function createContact() { - if( empty( $this->contact )) return FALSE; - $output = null; - foreach( $this->contact as $contact ) { - if( !empty( $contact['value'] )) { - $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' )); - $content = $this->_strrep( $contact['value'] ); - $output .= $this->_createElement( 'CONTACT', $attributes, $content ); - } - elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' ); - } - return $output; - } -/** - * set calendar component property contact - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param string $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setContact( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: CREATED - */ -/** - * creates formatted output for calendar component property created - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createCreated() { - if( empty( $this->created )) return FALSE; - $formatted = iCalUtilityFunctions::_format_date_time( $this->created['value'], 7 ); - $attributes = $this->_createParams( $this->created['params'] ); - return $this->_createElement( 'CREATED', $attributes, $formatted ); - } -/** - * set calendar component property created - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @param mixed $year optional - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param mixed $params optional - * @return bool - */ - function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - if( !isset( $year )) { - $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' ))); - } - $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DESCRIPTION - */ -/** - * creates formatted output for calendar component property description - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createDescription() { - if( empty( $this->description )) return FALSE; - $output = null; - foreach( $this->description as $description ) { - if( !empty( $description['value'] )) { - $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' )); - $content = $this->_strrep( $description['value'] ); - $output .= $this->_createElement( 'DESCRIPTION', $attributes, $content ); - } - elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' ); - } - return $output; - } -/** - * set calendar component property description - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.24 - 2010-11-06 - * @param string $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setDescription( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; } - if( 'vjournal' != $this->objName ) - $index = 1; - iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DTEND - */ -/** - * creates formatted output for calendar component property dtend - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - * @return string - */ - function createDtend() { - if( empty( $this->dtend )) return FALSE; - if( !isset( $this->dtend['value']['year'] ) && - !isset( $this->dtend['value']['month'] ) && - !isset( $this->dtend['value']['day'] ) && - !isset( $this->dtend['value']['hour'] ) && - !isset( $this->dtend['value']['min'] ) && - !isset( $this->dtend['value']['sec'] )) - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'DTEND' ); - else return FALSE; - $formatted = iCalUtilityFunctions::_format_date_time( $this->dtend['value'] ); - if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && - ( !isset( $this->dtend['params']['VALUE'] ) || ( $this->dtend['params']['VALUE'] != 'DATE' )) && - !isset( $this->dtend['params']['TZID'] )) - $this->dtend['params']['TZID'] = $tzid; - $attributes = $this->_createParams( $this->dtend['params'] ); - return $this->_createElement( 'DTEND', $attributes, $formatted ); - } -/** - * set calendar component property dtend - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param string $tz optional - * @param array $params optional - * @return bool - */ - function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { - if( empty( $year )) { - if( $this->getConfig( 'allowEmpty' )) { - $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } - else - return FALSE; - } - $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DTSTAMP - */ -/** - * creates formatted output for calendar component property dtstamp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.4 - 2008-03-07 - * @return string - */ - function createDtstamp() { - if( !isset( $this->dtstamp['value']['year'] ) && - !isset( $this->dtstamp['value']['month'] ) && - !isset( $this->dtstamp['value']['day'] ) && - !isset( $this->dtstamp['value']['hour'] ) && - !isset( $this->dtstamp['value']['min'] ) && - !isset( $this->dtstamp['value']['sec'] )) - $this->_makeDtstamp(); - $formatted = iCalUtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 ); - $attributes = $this->_createParams( $this->dtstamp['params'] ); - return $this->_createElement( 'DTSTAMP', $attributes, $formatted ); - } -/** - * computes datestamp for calendar component object instance dtstamp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.9 - 2011-08-10 - * @return void - */ - function _makeDtstamp() { - $d = mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')); - $this->dtstamp['value'] = array( 'year' => date( 'Y', $d ) - , 'month' => date( 'm', $d ) - , 'day' => date( 'd', $d ) - , 'hour' => date( 'H', $d ) - , 'min' => date( 'i', $d ) - , 'sec' => date( 's', $d )); - $this->dtstamp['params'] = null; - } -/** - * set calendar component property dtstamp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return TRUE - */ - function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - if( empty( $year )) - $this->_makeDtstamp(); - else - $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DTSTART - */ -/** - * creates formatted output for calendar component property dtstart - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-15 - * @return string - */ - function createDtstart() { - if( empty( $this->dtstart )) return FALSE; - if( !isset( $this->dtstart['value']['year'] ) && - !isset( $this->dtstart['value']['month'] ) && - !isset( $this->dtstart['value']['day'] ) && - !isset( $this->dtstart['value']['hour'] ) && - !isset( $this->dtstart['value']['min'] ) && - !isset( $this->dtstart['value']['sec'] )) { - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'DTSTART' ); - else return FALSE; - } - if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) - unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] ); - elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && - ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' )) && - !isset( $this->dtstart['params']['TZID'] )) - $this->dtstart['params']['TZID'] = $tzid; - $formatted = iCalUtilityFunctions::_format_date_time( $this->dtstart['value'] ); - $attributes = $this->_createParams( $this->dtstart['params'] ); - return $this->_createElement( 'DTSTART', $attributes, $formatted ); - } -/** - * set calendar component property dtstart - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.22 - 2010-09-22 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param string $tz optional - * @param array $params optional - * @return bool - */ - function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { - if( empty( $year )) { - if( $this->getConfig( 'allowEmpty' )) { - $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } - else - return FALSE; - } - $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DUE - */ -/** - * creates formatted output for calendar component property due - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createDue() { - if( empty( $this->due )) return FALSE; - if( !isset( $this->due['value']['year'] ) && - !isset( $this->due['value']['month'] ) && - !isset( $this->due['value']['day'] ) && - !isset( $this->due['value']['hour'] ) && - !isset( $this->due['value']['min'] ) && - !isset( $this->due['value']['sec'] )) { - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'DUE' ); - else - return FALSE; - } - $formatted = iCalUtilityFunctions::_format_date_time( $this->due['value'] ); - if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && - ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' )) && - !isset( $this->due['params']['TZID'] )) - $this->due['params']['TZID'] = $tzid; - $attributes = $this->_createParams( $this->due['params'] ); - return $this->_createElement( 'DUE', $attributes, $formatted ); - } -/** - * set calendar component property due - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return bool - */ - function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { - if( empty( $year )) { - if( $this->getConfig( 'allowEmpty' )) { - $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } - else - return FALSE; - } - $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: DURATION - */ -/** - * creates formatted output for calendar component property duration - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createDuration() { - if( empty( $this->duration )) return FALSE; - if( !isset( $this->duration['value']['week'] ) && - !isset( $this->duration['value']['day'] ) && - !isset( $this->duration['value']['hour'] ) && - !isset( $this->duration['value']['min'] ) && - !isset( $this->duration['value']['sec'] )) - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'DURATION', array(), null ); - else return FALSE; - $attributes = $this->_createParams( $this->duration['params'] ); - return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_format_duration( $this->duration['value'] )); - } -/** - * set calendar component property duration - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param mixed $week - * @param mixed $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return bool - */ - function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE; - if( is_array( $week ) && ( 1 <= count( $week ))) - $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); - elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) { - $week = trim( $week ); - if( in_array( substr( $week, 0, 1 ), array( '+', '-' ))) - $week = substr( $week, 1 ); - $this->duration = array( 'value' => iCalUtilityFunctions::_duration_string( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); - } - elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec )) - return FALSE; - else - $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: EXDATE - */ -/** - * creates formatted output for calendar component property exdate - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createExdate() { - if( empty( $this->exdate )) return FALSE; - $output = null; - foreach( $this->exdate as $ex => $theExdate ) { - if( empty( $theExdate['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'EXDATE' ); - continue; - } - $content = $attributes = null; - foreach( $theExdate['value'] as $eix => $exdatePart ) { - $parno = count( $exdatePart ); - $formatted = iCalUtilityFunctions::_format_date_time( $exdatePart, $parno ); - if( isset( $theExdate['params']['TZID'] )) - $formatted = str_replace( 'Z', '', $formatted); - if( 0 < $eix ) { - if( isset( $theExdate['value'][0]['tz'] )) { - if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) || - ( 'Z' == $theExdate['value'][0]['tz'] )) { - if( 'Z' != substr( $formatted, -1 )) - $formatted .= 'Z'; - } - else - $formatted = str_replace( 'Z', '', $formatted ); - } - else - $formatted = str_replace( 'Z', '', $formatted ); - } - $content .= ( 0 < $eix ) ? ','.$formatted : $formatted; - } - $attributes .= $this->_createParams( $theExdate['params'] ); - $output .= $this->_createElement( 'EXDATE', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property exdate - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-01-19 - * @param array exdates - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setExdate( $exdates, $params=FALSE, $index=FALSE ) { - if( empty( $exdates )) { - if( $this->getConfig( 'allowEmpty' )) { - iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index ); - return TRUE; - } - else - return FALSE; - } - $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); - $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE; - /* ev. check 1:st date and save ev. timezone **/ - iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] ); - iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter - foreach( $exdates as $eix => $theExdate ) { - iCalUtilityFunctions::_strDate2arr( $theExdate ); - if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) - $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno ); - elseif( is_array( $theExdate )) - $exdatea = iCalUtilityFunctions::_date_time_array( $theExdate, $parno ); - elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18 - $exdatea = iCalUtilityFunctions::_date_time_string( $theExdate, $parno ); - unset( $exdatea['unparsedtext'] ); - } - if( 3 == $parno ) - unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] ); - elseif( isset( $exdatea['tz'] )) - $exdatea['tz'] = (string) $exdatea['tz']; - if( isset( $input['params']['TZID'] ) || - ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) || - ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) || - ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) - unset( $exdatea['tz'] ); - if( $toZ ) // time zone Z - $exdatea['tz'] = 'Z'; - $input['value'][] = $exdatea; - } - if( 0 >= count( $input['value'] )) - return FALSE; - if( 3 == $parno ) { - $input['params']['VALUE'] = 'DATE'; - unset( $input['params']['TZID'] ); - } - if( $toZ ) // time zone Z - unset( $input['params']['TZID'] ); - iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: EXRULE - */ -/** - * creates formatted output for calendar component property exrule - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createExrule() { - if( empty( $this->exrule )) return FALSE; - return $this->_format_recur( 'EXRULE', $this->exrule ); - } -/** - * set calendar component property exdate - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param array $exruleset - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setExrule( $exruleset, $params=FALSE, $index=FALSE ) { - if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: FREEBUSY - */ -/** - * creates formatted output for calendar component property freebusy - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.1.23 - 2012-02-16 - * @return string - */ - function createFreebusy() { - if( empty( $this->freebusy )) return FALSE; - $output = null; - foreach( $this->freebusy as $freebusyPart ) { - if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' ); - continue; - } - $attributes = $content = null; - if( isset( $freebusyPart['value']['fbtype'] )) { - $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype']; - unset( $freebusyPart['value']['fbtype'] ); - $freebusyPart['value'] = array_values( $freebusyPart['value'] ); - } - else - $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY'; - $attributes .= $this->_createParams( $freebusyPart['params'] ); - $fno = 1; - $cnt = count( $freebusyPart['value']); - foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) { - $formatted = iCalUtilityFunctions::_format_date_time( $freebusyPeriod[0] ); - $content .= $formatted; - $content .= '/'; - $cnt2 = count( $freebusyPeriod[1]); - if( array_key_exists( 'year', $freebusyPeriod[1] )) // date-time - $cnt2 = 7; - elseif( array_key_exists( 'week', $freebusyPeriod[1] )) // duration - $cnt2 = 5; - if(( 7 == $cnt2 ) && // period= -> date-time - isset( $freebusyPeriod[1]['year'] ) && - isset( $freebusyPeriod[1]['month'] ) && - isset( $freebusyPeriod[1]['day'] )) { - $content .= iCalUtilityFunctions::_format_date_time( $freebusyPeriod[1] ); - } - else { // period= -> dur-time - $content .= iCalUtilityFunctions::_format_duration( $freebusyPeriod[1] ); - } - if( $fno < $cnt ) - $content .= ','; - $fno++; - } - $output .= $this->_createElement( 'FREEBUSY', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property freebusy - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.30 - 2012-01-16 - * @param string $fbType - * @param array $fbValues - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) { - if( empty( $fbValues )) { - if( $this->getConfig( 'allowEmpty' )) { - iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index ); - return TRUE; - } - else - return FALSE; - } - $fbType = strtoupper( $fbType ); - if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) && - ( 'X-' != substr( $fbType, 0, 2 ))) - $fbType = 'BUSY'; - $input = array( 'fbtype' => $fbType ); - foreach( $fbValues as $fbPeriod ) { // periods => period - if( empty( $fbPeriod )) - continue; - $freebusyPeriod = array(); - foreach( $fbPeriod as $fbMember ) { // pairs => singlepart - $freebusyPairMember = array(); - if( is_array( $fbMember )) { - if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value - $freebusyPairMember = iCalUtilityFunctions::_date_time_array( $fbMember, 7 ); - $freebusyPairMember['tz'] = 'Z'; - } - elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value - $freebusyPairMember = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 ); - $freebusyPairMember['tz'] = 'Z'; - } - else { // array format duration - $freebusyPairMember = iCalUtilityFunctions::_duration_array( $fbMember ); - } - } - elseif(( 3 <= strlen( trim( $fbMember ))) && // string format duration - ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) { - if( 'P' != $fbMember{0} ) - $fbmember = substr( $fbMember, 1 ); - $freebusyPairMember = iCalUtilityFunctions::_duration_string( $fbMember ); - } - elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18 - $freebusyPairMember = iCalUtilityFunctions::_date_time_string( $fbMember, 7 ); - unset( $freebusyPairMember['unparsedtext'] ); - $freebusyPairMember['tz'] = 'Z'; - } - $freebusyPeriod[] = $freebusyPairMember; - } - $input[] = $freebusyPeriod; - } - iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: GEO - */ -/** - * creates formatted output for calendar component property geo - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createGeo() { - if( empty( $this->geo )) return FALSE; - if( empty( $this->geo['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE; - $attributes = $this->_createParams( $this->geo['params'] ); - $content = null; - $content .= number_format( (float) $this->geo['value']['latitude'], 6, '.', ''); - $content .= ';'; - $content .= number_format( (float) $this->geo['value']['longitude'], 6, '.', ''); - return $this->_createElement( 'GEO', $attributes, $content ); - } -/** - * set calendar component property geo - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param float $latitude - * @param float $longitude - * @param array $params optional - * @return bool - */ - function setGeo( $latitude, $longitude, $params=FALSE ) { - if( !empty( $latitude ) && !empty( $longitude )) { - if( !is_array( $this->geo )) $this->geo = array(); - $this->geo['value']['latitude'] = $latitude; - $this->geo['value']['longitude'] = $longitude; - $this->geo['params'] = iCalUtilityFunctions::_setParams( $params ); - } - elseif( $this->getConfig( 'allowEmpty' )) - $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) ); - else - return FALSE; - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: LAST-MODIFIED - */ -/** - * creates formatted output for calendar component property last-modified - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createLastModified() { - if( empty( $this->lastmodified )) return FALSE; - $attributes = $this->_createParams( $this->lastmodified['params'] ); - $formatted = iCalUtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 ); - return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted ); - } -/** - * set calendar component property completed - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @param mixed $year optional - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return boll - */ - function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - if( empty( $year )) - $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' ))); - $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: LOCATION - */ -/** - * creates formatted output for calendar component property location - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @return string - */ - function createLocation() { - if( empty( $this->location )) return FALSE; - if( empty( $this->location['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE; - $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' )); - $content = $this->_strrep( $this->location['value'] ); - return $this->_createElement( 'LOCATION', $attributes, $content ); - } -/** - * set calendar component property location - ' - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param array params optional - * @return bool - */ - function setLocation( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: ORGANIZER - */ -/** - * creates formatted output for calendar component property organizer - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.33 - 2010-12-17 - * @return string - */ - function createOrganizer() { - if( empty( $this->organizer )) return FALSE; - if( empty( $this->organizer['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE; - $attributes = $this->_createParams( $this->organizer['params'] - , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' )); - return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] ); - } -/** - * set calendar component property organizer - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.27 - 2010-11-29 - * @param string $value - * @param array params optional - * @return bool - */ - function setOrganizer( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) - $value = 'MAILTO:'.$value; - else - $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos ); - $value = str_replace( 'mailto:', 'MAILTO:', $value ); - $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - if( isset( $this->organizer['params']['SENT-BY'] )){ - if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 ))) - $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY']; - else - $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 ); - } - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: PERCENT-COMPLETE - */ -/** - * creates formatted output for calendar component property percent-complete - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @return string - */ - function createPercentComplete() { - if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE; - if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] ))) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE; - $attributes = $this->_createParams( $this->percentcomplete['params'] ); - return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] ); - } -/** - * set calendar component property percent-complete - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @param int $value - * @param array $params optional - * @return bool - */ - function setPercentComplete( $value, $params=FALSE ) { - if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: PRIORITY - */ -/** - * creates formatted output for calendar component property priority - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @return string - */ - function createPriority() { - if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE; - if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] ))) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE; - $attributes = $this->_createParams( $this->priority['params'] ); - return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] ); - } -/** - * set calendar component property priority - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @param int $value - * @param array $params optional - * @return bool - */ - function setPriority( $value, $params=FALSE ) { - if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: RDATE - */ -/** - * creates formatted output for calendar component property rdate - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-10-26 - * @return string - */ - function createRdate() { - if( empty( $this->rdate )) return FALSE; - $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE; - $output = null; - if( $utctime ) - unset( $this->rdate['params']['TZID'] ); - foreach( $this->rdate as $theRdate ) { - if( empty( $theRdate['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' ); - continue; - } - if( $utctime ) - unset( $theRdate['params']['TZID'] ); - $attributes = $this->_createParams( $theRdate['params'] ); - $cnt = count( $theRdate['value'] ); - $content = null; - $rno = 1; - foreach( $theRdate['value'] as $rpix => $rdatePart ) { - $contentPart = null; - if( is_array( $rdatePart ) && - isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD - if( $utctime ) - unset( $rdatePart[0]['tz'] ); - $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1 - if( $utctime || !empty( $theRdate['params']['TZID'] )) - $formatted = str_replace( 'Z', '', $formatted); - if( 0 < $rpix ) { - if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { - if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; - } - else - $formatted = str_replace( 'Z', '', $formatted ); - } - $contentPart .= $formatted; - $contentPart .= '/'; - $cnt2 = count( $rdatePart[1]); - if( array_key_exists( 'year', $rdatePart[1] )) { - if( array_key_exists( 'hour', $rdatePart[1] )) - $cnt2 = 7; // date-time - else - $cnt2 = 3; // date - } - elseif( array_key_exists( 'week', $rdatePart[1] )) // duration - $cnt2 = 5; - if(( 7 == $cnt2 ) && // period= -> date-time - isset( $rdatePart[1]['year'] ) && - isset( $rdatePart[1]['month'] ) && - isset( $rdatePart[1]['day'] )) { - if( $utctime ) - unset( $rdatePart[1]['tz'] ); - $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2 - if( $utctime || !empty( $theRdate['params']['TZID'] )) - $formatted = str_replace( 'Z', '', $formatted); - if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { - if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; - } - else - $formatted = str_replace( 'Z', '', $formatted ); - $contentPart .= $formatted; - } - else { // period= -> dur-time - $contentPart .= iCalUtilityFunctions::_format_duration( $rdatePart[1] ); - } - } // PERIOD end - else { // SINGLE date start - if( $utctime ) - unset( $rdatePart['tz'] ); - $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart); - if( $utctime || !empty( $theRdate['params']['TZID'] )) - $formatted = str_replace( 'Z', '', $formatted); - if( !$utctime && ( 0 < $rpix )) { - if( !empty( $theRdate['value'][0]['tz'] ) && iCalUtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) { - if( 'Z' != substr( $formatted, -1 )) - $formatted .= 'Z'; - } - else - $formatted = str_replace( 'Z', '', $formatted ); - } - $contentPart .= $formatted; - } - $content .= $contentPart; - if( $rno < $cnt ) - $content .= ','; - $rno++; - } - $output .= $this->_createElement( 'RDATE', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property rdate - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-01-31 - * @param array $rdates - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setRdate( $rdates, $params=FALSE, $index=FALSE ) { - if( empty( $rdates )) { - if( $this->getConfig( 'allowEmpty' )) { - iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index ); - return TRUE; - } - else - return FALSE; - } - $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); - if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) { - unset( $input['params']['TZID'] ); - $input['params']['VALUE'] = 'DATE-TIME'; - } - $zArr = array( 'GMT', 'UTC', 'Z' ); - $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE; - /* check if PERIOD, if not set */ - if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) && - isset( $rdates[0] ) && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) && - isset( $rdates[0][0] ) && isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) && - (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) || - iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) || - ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] ))))) && - ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] )))))) - $input['params']['VALUE'] = 'PERIOD'; - /* check 1:st date, upd. $parno (opt) and save ev. timezone **/ - $date = reset( $rdates ); - if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD - $date = reset( $date ); - iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] ); - iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default - foreach( $rdates as $rpix => $theRdate ) { - $inputa = null; - iCalUtilityFunctions::_strDate2arr( $theRdate ); - if( is_array( $theRdate )) { - if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD - foreach( $theRdate as $rix => $rPeriod ) { - iCalUtilityFunctions::_strDate2arr( $theRdate ); - if( is_array( $rPeriod )) { - if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) // timestamp - $inputab = ( isset( $rPeriod['tz'] )) ? iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCalUtilityFunctions::_timestamp2date( $rPeriod, 6 ); - elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) - $inputab = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCalUtilityFunctions::_date_time_array( $rPeriod, 6 ); - elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date - $inputab = iCalUtilityFunctions::_date_time_string( reset( $rPeriod ), $parno ); - unset( $inputab['unparsedtext'] ); - } - else // array format duration - $inputab = iCalUtilityFunctions::_duration_array( $rPeriod ); - } - elseif(( 3 <= strlen( trim( $rPeriod ))) && // string format duration - ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) { - if( 'P' != $rPeriod[0] ) - $rPeriod = substr( $rPeriod, 1 ); - $inputab = iCalUtilityFunctions::_duration_string( $rPeriod ); - } - elseif( 8 <= strlen( trim( $rPeriod ))) { // text date ex. 2006-08-03 10:12:18 - $inputab = iCalUtilityFunctions::_date_time_string( $rPeriod, $parno ); - unset( $inputab['unparsedtext'] ); - } - if( isset( $input['params']['TZID'] ) || - ( isset( $inputab['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputab['tz'] )) || - ( isset( $inputa[0] ) && ( !isset( $inputa[0]['tz'] ))) || - ( isset( $inputa[0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa[0]['tz'] ))) - unset( $inputab['tz'] ); - if( $toZ ) - $inputab['tz'] = 'Z'; - $inputa[] = $inputab; - } - } // PERIOD end - elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) { // timestamp - $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno ); - if( $toZ ) - $inputa['tz'] = 'Z'; - } - else { // date[-time] - $inputa = iCalUtilityFunctions::_date_time_array( $theRdate, $parno ); - $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE; - if( $toZ ) - $inputa['tz'] = 'Z'; - } - } - elseif( 8 <= strlen( trim( $theRdate ))) { // text date ex. 2006-08-03 10:12:18 - $inputa = iCalUtilityFunctions::_date_time_string( $theRdate, $parno ); - unset( $inputa['unparsedtext'] ); - if( $toZ ) - $inputa['tz'] = 'Z'; - } - if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD - if( 3 == $parno ) - unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] ); - elseif( isset( $inputa['tz'] )) - $inputa['tz'] = (string) $inputa['tz']; - if( isset( $input['params']['TZID'] ) || - ( isset( $inputa['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa['tz'] )) || - ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) || - ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) - if( !$toZ ) - unset( $inputa['tz'] ); - } - $input['value'][] = $inputa; - } - if( 3 == $parno ) { - $input['params']['VALUE'] = 'DATE'; - unset( $input['params']['TZID'] ); - } - if( $toZ ) - unset( $input['params']['TZID'] ); - iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: RECURRENCE-ID - */ -/** - * creates formatted output for calendar component property recurrence-id - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-15 - * @return string - */ - function createRecurrenceid() { - if( empty( $this->recurrenceid )) return FALSE; - if( empty( $this->recurrenceid['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE; - $formatted = iCalUtilityFunctions::_format_date_time( $this->recurrenceid['value'] ); - if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && - ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' )) && - !isset( $this->recurrenceid['params']['TZID'] )) - $this->recurrenceid['params']['TZID'] = $tzid; - $attributes = $this->_createParams( $this->recurrenceid['params'] ); - return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted ); - } -/** - * set calendar component property recurrence-id - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-15 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return bool - */ - function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { - if( empty( $year )) { - if( $this->getConfig( 'allowEmpty' )) { - $this->recurrenceid = array( 'value' => null, 'params' => null ); - return TRUE; - } - else - return FALSE; - } - $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: RELATED-TO - */ -/** - * creates formatted output for calendar component property related-to - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.24 - 2012-02-23 - * @return string - */ - function createRelatedTo() { - if( empty( $this->relatedto )) return FALSE; - $output = null; - foreach( $this->relatedto as $relation ) { - if( !empty( $relation['value'] )) - $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), $this->_strrep( $relation['value'] ) ); - elseif( $this->getConfig( 'allowEmpty' )) - $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] )); - } - return $output; - } -/** - * set calendar component property related-to - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.24 - 2012-02-23 - * @param float $relid - * @param array $params, optional - * @param index $index, optional - * @return bool - */ - function setRelatedTo( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default - iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: REPEAT - */ -/** - * creates formatted output for calendar component property repeat - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @return string - */ - function createRepeat() { - if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE; - if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] ))) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE; - $attributes = $this->_createParams( $this->repeat['params'] ); - return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] ); - } -/** - * set calendar component property repeat - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @param string $value - * @param array $params optional - * @return void - */ - function setRepeat( $value, $params=FALSE ) { - if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: REQUEST-STATUS - */ -/** - * creates formatted output for calendar component property request-status - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @return string - */ - function createRequestStatus() { - if( empty( $this->requeststatus )) return FALSE; - $output = null; - foreach( $this->requeststatus as $rstat ) { - if( empty( $rstat['value']['statcode'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' ); - continue; - } - $attributes = $this->_createParams( $rstat['params'], array( 'LANGUAGE' )); - $content = number_format( (float) $rstat['value']['statcode'], 2, '.', ''); - $content .= ';'.$this->_strrep( $rstat['value']['text'] ); - if( isset( $rstat['value']['extdata'] )) - $content .= ';'.$this->_strrep( $rstat['value']['extdata'] ); - $output .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property request-status - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param float $statcode - * @param string $text - * @param string $extdata, optional - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) { - if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE; - $input = array( 'statcode' => $statcode, 'text' => $text ); - if( $extdata ) - $input['extdata'] = $extdata; - iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: RESOURCES - */ -/** - * creates formatted output for calendar component property resources - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-23 - * @return string - */ - function createResources() { - if( empty( $this->resources )) return FALSE; - $output = null; - foreach( $this->resources as $resource ) { - if( empty( $resource['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' ); - continue; - } - $attributes = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' )); - if( is_array( $resource['value'] )) { - foreach( $resource['value'] as $rix => $resourcePart ) - $resource['value'][$rix] = $this->_strrep( $resourcePart ); - $content = implode( ',', $resource['value'] ); - } - else - $content = $this->_strrep( $resource['value'] ); - $output .= $this->_createElement( 'RESOURCES', $attributes, $content ); - } - return $output; - } -/** - * set calendar component property recources - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param mixed $value - * @param array $params, optional - * @param integer $index, optional - * @return bool - */ - function setResources( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: RRULE - */ -/** - * creates formatted output for calendar component property rrule - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createRrule() { - if( empty( $this->rrule )) return FALSE; - return $this->_format_recur( 'RRULE', $this->rrule ); - } -/** - * set calendar component property rrule - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param array $rruleset - * @param array $params, optional - * @param integer $index, optional - * @return void - */ - function setRrule( $rruleset, $params=FALSE, $index=FALSE ) { - if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: SEQUENCE - */ -/** - * creates formatted output for calendar component property sequence - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @return string - */ - function createSequence() { - if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE; - if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) && - ( '0' != $this->sequence['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE; - $attributes = $this->_createParams( $this->sequence['params'] ); - return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] ); - } -/** - * set calendar component property sequence - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.8 - 2011-09-19 - * @param int $value optional - * @param array $params optional - * @return bool - */ - function setSequence( $value=FALSE, $params=FALSE ) { - if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value )) - $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0'; - $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: STATUS - */ -/** - * creates formatted output for calendar component property status - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createStatus() { - if( empty( $this->status )) return FALSE; - if( empty( $this->status['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE; - $attributes = $this->_createParams( $this->status['params'] ); - return $this->_createElement( 'STATUS', $attributes, $this->status['value'] ); - } -/** - * set calendar component property status - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param array $params optional - * @return bool - */ - function setStatus( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: SUMMARY - */ -/** - * creates formatted output for calendar component property summary - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createSummary() { - if( empty( $this->summary )) return FALSE; - if( empty( $this->summary['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE; - $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' )); - $content = $this->_strrep( $this->summary['value'] ); - return $this->_createElement( 'SUMMARY', $attributes, $content ); - } -/** - * set calendar component property summary - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setSummary( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: TRANSP - */ -/** - * creates formatted output for calendar component property transp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTransp() { - if( empty( $this->transp )) return FALSE; - if( empty( $this->transp['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE; - $attributes = $this->_createParams( $this->transp['params'] ); - return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] ); - } -/** - * set calendar component property transp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setTransp( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: TRIGGER - */ -/** - * creates formatted output for calendar component property trigger - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-10-21 - * @return string - */ - function createTrigger() { - if( empty( $this->trigger )) return FALSE; - if( empty( $this->trigger['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE; - $content = $attributes = null; - if( isset( $this->trigger['value']['year'] ) && - isset( $this->trigger['value']['month'] ) && - isset( $this->trigger['value']['day'] )) - $content .= iCalUtilityFunctions::_format_date_time( $this->trigger['value'] ); - else { - if( TRUE !== $this->trigger['value']['relatedStart'] ) - $attributes .= $this->intAttrDelimiter.'RELATED=END'; - if( $this->trigger['value']['before'] ) - $content .= '-'; - $content .= iCalUtilityFunctions::_format_duration( $this->trigger['value'] ); - } - $attributes .= $this->_createParams( $this->trigger['params'] ); - return $this->_createElement( 'TRIGGER', $attributes, $content ); - } -/** - * set calendar component property trigger - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.30 - 2012-01-16 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $week optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param bool $relatedStart optional - * @param bool $before optional - * @param array $params optional - * @return bool - */ - function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) { - if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec )) - if( $this->getConfig( 'allowEmpty' )) { - $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) ); - return TRUE; - } - else - return FALSE; - if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp - $params = iCalUtilityFunctions::_setParams( $month ); - $date = iCalUtilityFunctions::_timestamp2date( $year, 7 ); - foreach( $date as $k => $v ) - $$k = $v; - } - elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) { - $params = iCalUtilityFunctions::_setParams( $month ); - if(!(array_key_exists( 'year', $year ) && // exclude date-time - array_key_exists( 'month', $year ) && - array_key_exists( 'day', $year ))) { // when this must be a duration - if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) - $relatedStart = FALSE; - else - $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE; - $before = ( array_key_exists( 'before', $year ) && ( TRUE !== $year['before'] )) ? FALSE : TRUE; - } - $SSYY = ( array_key_exists( 'year', $year )) ? $year['year'] : null; - $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null; - $day = ( array_key_exists( 'day', $year )) ? $year['day'] : null; - $week = ( array_key_exists( 'week', $year )) ? $year['week'] : null; - $hour = ( array_key_exists( 'hour', $year )) ? $year['hour'] : 0; //null; - $min = ( array_key_exists( 'min', $year )) ? $year['min'] : 0; //null; - $sec = ( array_key_exists( 'sec', $year )) ? $year['sec'] : 0; //null; - $year = $SSYY; - } - elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) { // duration or date in a string - $params = iCalUtilityFunctions::_setParams( $month ); - if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration - $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE; - $before = ( '-' == $year[0] ) ? TRUE : FALSE; - if( 'P' != $year[0] ) - $year = substr( $year, 1 ); - $date = iCalUtilityFunctions::_duration_string( $year); - } - else // date - $date = iCalUtilityFunctions::_date_time_string( $year, 7 ); - unset( $year, $month, $day, $date['unparsedtext'] ); - if( empty( $date )) - $sec = 0; - else - foreach( $date as $k => $v ) - $$k = $v; - } - else // single values in function input parameters - $params = iCalUtilityFunctions::_setParams( $params ); - if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date - $params['VALUE'] = 'DATE-TIME'; - $hour = ( $hour ) ? $hour : 0; - $min = ( $min ) ? $min : 0; - $sec = ( $sec ) ? $sec : 0; - $this->trigger = array( 'params' => $params ); - $this->trigger['value'] = array( 'year' => $year - , 'month' => $month - , 'day' => $day - , 'hour' => $hour - , 'min' => $min - , 'sec' => $sec - , 'tz' => 'Z' ); - return TRUE; - } - elseif(( empty( $year ) && empty( $month )) && // duration - (( !empty( $week ) || ( 0 == $week )) || - ( !empty( $day ) || ( 0 == $day )) || - ( !empty( $hour ) || ( 0 == $hour )) || - ( !empty( $min ) || ( 0 == $min )) || - ( !empty( $sec ) || ( 0 == $sec )))) { - unset( $params['RELATED'] ); // set at output creation (END only) - unset( $params['VALUE'] ); // 'DURATION' default - $this->trigger = array( 'params' => $params ); - $this->trigger['value'] = array(); - if( !empty( $week )) $this->trigger['value']['week'] = $week; - if( !empty( $day )) $this->trigger['value']['day'] = $day; - if( !empty( $hour )) $this->trigger['value']['hour'] = $hour; - if( !empty( $min )) $this->trigger['value']['min'] = $min; - if( !empty( $sec )) $this->trigger['value']['sec'] = $sec; - if( empty( $this->trigger['value'] )) { - $this->trigger['value']['sec'] = 0; - $before = FALSE; - } - $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE; - $before = ( FALSE !== $before ) ? TRUE : FALSE; - $this->trigger['value']['relatedStart'] = $relatedStart; - $this->trigger['value']['before'] = $before; - return TRUE; - } - return FALSE; - } -/*********************************************************************************/ -/** - * Property Name: TZID - */ -/** - * creates formatted output for calendar component property tzid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTzid() { - if( empty( $this->tzid )) return FALSE; - if( empty( $this->tzid['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE; - $attributes = $this->_createParams( $this->tzid['params'] ); - return $this->_createElement( 'TZID', $attributes, $this->_strrep( $this->tzid['value'] )); - } -/** - * set calendar component property tzid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param array $params optional - * @return bool - */ - function setTzid( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * .. . - * Property Name: TZNAME - */ -/** - * creates formatted output for calendar component property tzname - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTzname() { - if( empty( $this->tzname )) return FALSE; - $output = null; - foreach( $this->tzname as $theName ) { - if( !empty( $theName['value'] )) { - $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' )); - $output .= $this->_createElement( 'TZNAME', $attributes, $this->_strrep( $theName['value'] )); - } - elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' ); - } - return $output; - } -/** - * set calendar component property tzname - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param string $value - * @param string $params, optional - * @param integer $index, optional - * @return bool - */ - function setTzname( $value, $params=FALSE, $index=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index ); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: TZOFFSETFROM - */ -/** - * creates formatted output for calendar component property tzoffsetfrom - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTzoffsetfrom() { - if( empty( $this->tzoffsetfrom )) return FALSE; - if( empty( $this->tzoffsetfrom['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE; - $attributes = $this->_createParams( $this->tzoffsetfrom['params'] ); - return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] ); - } -/** - * set calendar component property tzoffsetfrom - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setTzoffsetfrom( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: TZOFFSETTO - */ -/** - * creates formatted output for calendar component property tzoffsetto - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTzoffsetto() { - if( empty( $this->tzoffsetto )) return FALSE; - if( empty( $this->tzoffsetto['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE; - $attributes = $this->_createParams( $this->tzoffsetto['params'] ); - return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] ); - } -/** - * set calendar component property tzoffsetto - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setTzoffsetto( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: TZURL - */ -/** - * creates formatted output for calendar component property tzurl - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createTzurl() { - if( empty( $this->tzurl )) return FALSE; - if( empty( $this->tzurl['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE; - $attributes = $this->_createParams( $this->tzurl['params'] ); - return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] ); - } -/** - * set calendar component property tzurl - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return boll - */ - function setTzurl( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: UID - */ -/** - * creates formatted output for calendar component property uid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 0.9.7 - 2006-11-20 - * @return string - */ - function createUid() { - if( 0 >= count( $this->uid )) - $this->_makeuid(); - $attributes = $this->_createParams( $this->uid['params'] ); - return $this->_createElement( 'UID', $attributes, $this->uid['value'] ); - } -/** - * create an unique id for this calendar component object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.2.7 - 2007-09-04 - * @return void - */ - function _makeUid() { - $date = date('Ymd\THisT'); - $unique = substr(microtime(), 2, 4); - $base = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890'; - $start = 0; - $end = strlen( $base ) - 1; - $length = 6; - $str = null; - for( $p = 0; $p < $length; $p++ ) - $unique .= $base{mt_rand( $start, $end )}; - $this->uid = array( 'params' => null ); - $this->uid['value'] = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' ); - } -/** - * set calendar component property uid - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setUid( $value, $params=FALSE ) { - if( empty( $value )) return FALSE; // no allowEmpty check here !!!! - $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: URL - */ -/** - * creates formatted output for calendar component property url - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-21 - * @return string - */ - function createUrl() { - if( empty( $this->url )) return FALSE; - if( empty( $this->url['value'] )) - return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE; - $attributes = $this->_createParams( $this->url['params'] ); - return $this->_createElement( 'URL', $attributes, $this->url['value'] ); - } -/** - * set calendar component property url - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-11-04 - * @param string $value - * @param string $params optional - * @return bool - */ - function setUrl( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); - return TRUE; - } -/*********************************************************************************/ -/** - * Property Name: x-prop - */ -/** - * creates formatted output for calendar component property x-prop - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.3 - 2011-05-14 - * @return string - */ - function createXprop() { - if( empty( $this->xprop )) return FALSE; - $output = null; - foreach( $this->xprop as $label => $xpropPart ) { - if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label ); - continue; - } - $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' )); - if( is_array( $xpropPart['value'] )) { - foreach( $xpropPart['value'] as $pix => $theXpart ) - $xpropPart['value'][$pix] = $this->_strrep( $theXpart ); - $xpropPart['value'] = implode( ',', $xpropPart['value'] ); - } - else - $xpropPart['value'] = $this->_strrep( $xpropPart['value'] ); - $output .= $this->_createElement( $label, $attributes, $xpropPart['value'] ); - } - return $output; - } -/** - * set calendar component property x-prop - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.9 - 2012-01-16 - * @param string $label - * @param mixed $value - * @param array $params optional - * @return bool - */ - function setXprop( $label, $value, $params=FALSE ) { - if( empty( $label )) - return FALSE; - if( 'X-' != strtoupper( substr( $label, 0, 2 ))) - return FALSE; - if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $xprop = array( 'value' => $value ); - $xprop['params'] = iCalUtilityFunctions::_setParams( $params ); - if( !is_array( $this->xprop )) $this->xprop = array(); - $this->xprop[strtoupper( $label )] = $xprop; - return TRUE; - } -/*********************************************************************************/ -/*********************************************************************************/ -/** - * create element format parts - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.0.6 - 2006-06-20 - * @return string - */ - function _createFormat() { - $objectname = null; - switch( $this->format ) { - case 'xcal': - $objectname = ( isset( $this->timezonetype )) ? - strtolower( $this->timezonetype ) : strtolower( $this->objName ); - $this->componentStart1 = $this->elementStart1 = '<'; - $this->componentStart2 = $this->elementStart2 = '>'; - $this->componentEnd1 = $this->elementEnd1 = 'componentEnd2 = $this->elementEnd2 = '>'.$this->nl; - $this->intAttrDelimiter = ''; - $this->attributeDelimiter = $this->nl; - $this->valueInit = null; - break; - default: - $objectname = ( isset( $this->timezonetype )) ? - strtoupper( $this->timezonetype ) : strtoupper( $this->objName ); - $this->componentStart1 = 'BEGIN:'; - $this->componentStart2 = null; - $this->componentEnd1 = 'END:'; - $this->componentEnd2 = $this->nl; - $this->elementStart1 = null; - $this->elementStart2 = null; - $this->elementEnd1 = null; - $this->elementEnd2 = $this->nl; - $this->intAttrDelimiter = ''; - $this->attributeDelimiter = ';'; - $this->valueInit = ':'; - break; - } - return $objectname; - } -/** - * creates formatted output for calendar component property - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @param string $label property name - * @param string $attributes property attributes - * @param string $content property content (optional) - * @return string - */ - function _createElement( $label, $attributes=null, $content=FALSE ) { - switch( $this->format ) { - case 'xcal': - $label = strtolower( $label ); - break; - default: - $label = strtoupper( $label ); - break; - } - $output = $this->elementStart1.$label; - $categoriesAttrLang = null; - $attachInlineBinary = FALSE; - $attachfmttype = null; - if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) { - $this->xcaldecl[] = array( 'xmldecl' => 'ELEMENT' - , 'ref' => $label - , 'type2' => '(#PCDATA)' ); - } - if( !empty( $attributes )) { - $attributes = trim( $attributes ); - if ( 'xcal' == $this->format ) { - $attributes2 = explode( $this->intAttrDelimiter, $attributes ); - $attributes = null; - foreach( $attributes2 as $aix => $attribute ) { - $attrKVarr = explode( '=', $attribute ); - if( empty( $attrKVarr[0] )) - continue; - if( !isset( $attrKVarr[1] )) { - $attrValue = $attrKVarr[0]; - $attrKey = $aix; - } - elseif( 2 == count( $attrKVarr)) { - $attrKey = strtolower( $attrKVarr[0] ); - $attrValue = $attrKVarr[1]; - } - else { - $attrKey = strtolower( $attrKVarr[0] ); - unset( $attrKVarr[0] ); - $attrValue = implode( '=', $attrKVarr ); - } - if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) { - $attachInlineBinary = TRUE; - if( 'fmttype' == $attrKey ) - $attachfmttype = $attrKey.'='.$attrValue; - continue; - } - elseif(( 'categories' == $label ) && ( 'language' == $attrKey )) - $categoriesAttrLang = $attrKey.'='.$attrValue; - else { - $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' '; - $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null; - if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) { - $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 )); - $attrValue = str_replace( '"', '', $attrValue ); - } - $attributes .= '"'.htmlspecialchars( $attrValue ).'"'; - } - } - } - else { - $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes ); - } - } - if(( 'xcal' == $this->format) && - ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) { - $pos = strrpos($content, "/"); - $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content; - $this->xcaldecl[] = array( 'xmldecl' => 'ENTITY' - , 'uri' => $docname - , 'ref' => 'SYSTEM' - , 'external' => $content - , 'type' => 'NDATA' - , 'type2' => 'BINERY' ); - $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' '; - $attributes .= 'uri="'.$docname.'"'; - $content = null; - if( 'attach' == $label ) { - $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes ); - $content = $this->nl.$this->_createElement( 'extref', $attributes, null ); - $attributes = null; - } - } - elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) { - $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute - } - $output .= $attributes; - if( !$content && ( '0' != $content )) { - switch( $this->format ) { - case 'xcal': - $output .= ' /'; - $output .= $this->elementStart2.$this->nl; - return $output; - break; - default: - $output .= $this->elementStart2.$this->valueInit; - return $this->_size75( $output ); - break; - } - } - $output .= $this->elementStart2; - $output .= $this->valueInit.$content; - switch( $this->format ) { - case 'xcal': - return $output.$this->elementEnd1.$label.$this->elementEnd2; - break; - default: - return $this->_size75( $output ); - break; - } - } -/** - * creates formatted output for calendar component property parameters - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.27 - 2012-01-16 - * @param array $params optional - * @param array $ctrKeys optional - * @return string - */ - function _createParams( $params=array(), $ctrKeys=array() ) { - if( !is_array( $params ) || empty( $params )) - $params = array(); - $attrLANG = $attr1 = $attr2 = $lang = null; - $CNattrKey = ( in_array( 'CN', $ctrKeys )) ? TRUE : FALSE ; - $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ; - $CNattrExist = $LANGattrExist = FALSE; - $xparams = array(); - foreach( $params as $paramKey => $paramValue ) { - if(( FALSE !== strpos( $paramValue, ':' )) || - ( FALSE !== strpos( $paramValue, ';' )) || - ( FALSE !== strpos( $paramValue, ',' ))) - $paramValue = '"'.$paramValue.'"'; - if( ctype_digit( (string) $paramKey )) { - $xparams[] = $paramValue; - continue; - } - $paramKey = strtoupper( $paramKey ); - if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' ))) - $xparams[$paramKey] = $paramValue; - else - $params[$paramKey] = $paramValue; - } - ksort( $xparams, SORT_STRING ); - foreach( $xparams as $paramKey => $paramValue ) { - if( ctype_digit( (string) $paramKey )) - $attr2 .= $this->intAttrDelimiter.$paramValue; - else - $attr2 .= $this->intAttrDelimiter."$paramKey=$paramValue"; - } - if( isset( $params['FMTTYPE'] ) && !in_array( 'FMTTYPE', $ctrKeys )) { - $attr1 .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2; - $attr2 = null; - } - if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING', $ctrKeys )) { - if( !empty( $attr2 )) { - $attr1 .= $attr2; - $attr2 = null; - } - $attr1 .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING']; - } - if( isset( $params['VALUE'] ) && !in_array( 'VALUE', $ctrKeys )) - $attr1 .= $this->intAttrDelimiter.'VALUE='.$params['VALUE']; - if( isset( $params['TZID'] ) && !in_array( 'TZID', $ctrKeys )) { - $attr1 .= $this->intAttrDelimiter.'TZID='.$params['TZID']; - } - if( isset( $params['RANGE'] ) && !in_array( 'RANGE', $ctrKeys )) - $attr1 .= $this->intAttrDelimiter.'RANGE='.$params['RANGE']; - if( isset( $params['RELTYPE'] ) && !in_array( 'RELTYPE', $ctrKeys )) - $attr1 .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE']; - if( isset( $params['CN'] ) && $CNattrKey ) { - $attr1 = $this->intAttrDelimiter.'CN='.$params['CN']; - $CNattrExist = TRUE; - } - if( isset( $params['DIR'] ) && in_array( 'DIR', $ctrKeys )) { - $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"'; - $attr1 .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim; - } - if( isset( $params['SENT-BY'] ) && in_array( 'SENT-BY', $ctrKeys )) - $attr1 .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY']; - if( isset( $params['ALTREP'] ) && in_array( 'ALTREP', $ctrKeys )) { - $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"'; - $attr1 .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim; - } - if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) { - $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE']; - $LANGattrExist = TRUE; - } - if( !$LANGattrExist ) { - $lang = $this->getConfig( 'language' ); - if(( $CNattrExist || $LANGattrKey ) && $lang ) - $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang; - } - return $attr1.$attrLANG.$attr2; - } -/** - * creates formatted output for calendar component property data value type recur - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-22 - * @param array $recurlabel - * @param array $recurdata - * @return string - */ - function _format_recur( $recurlabel, $recurdata ) { - $output = null; - foreach( $recurdata as $therule ) { - if( empty( $therule['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel ); - continue; - } - $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null; - $content1 = $content2 = null; - foreach( $therule['value'] as $rulelabel => $rulevalue ) { - switch( $rulelabel ) { - case 'FREQ': { - $content1 .= "FREQ=$rulevalue"; - break; - } - case 'UNTIL': { - $content2 .= ";UNTIL="; - $content2 .= iCalUtilityFunctions::_format_date_time( $rulevalue ); - break; - } - case 'COUNT': - case 'INTERVAL': - case 'WKST': { - $content2 .= ";$rulelabel=$rulevalue"; - break; - } - case 'BYSECOND': - case 'BYMINUTE': - case 'BYHOUR': - case 'BYMONTHDAY': - case 'BYYEARDAY': - case 'BYWEEKNO': - case 'BYMONTH': - case 'BYSETPOS': { - $content2 .= ";$rulelabel="; - if( is_array( $rulevalue )) { - foreach( $rulevalue as $vix => $valuePart ) { - $content2 .= ( $vix ) ? ',' : null; - $content2 .= $valuePart; - } - } - else - $content2 .= $rulevalue; - break; - } - case 'BYDAY': { - $content2 .= ";$rulelabel="; - $bydaycnt = 0; - foreach( $rulevalue as $vix => $valuePart ) { - $content21 = $content22 = null; - if( is_array( $valuePart )) { - $content2 .= ( $bydaycnt ) ? ',' : null; - foreach( $valuePart as $vix2 => $valuePart2 ) { - if( 'DAY' != strtoupper( $vix2 )) - $content21 .= $valuePart2; - else - $content22 .= $valuePart2; - } - $content2 .= $content21.$content22; - $bydaycnt++; - } - else { - $content2 .= ( $bydaycnt ) ? ',' : null; - if( 'DAY' != strtoupper( $vix )) - $content21 .= $valuePart; - else { - $content22 .= $valuePart; - $bydaycnt++; - } - $content2 .= $content21.$content22; - } - } - break; - } - default: { - $content2 .= ";$rulelabel=$rulevalue"; - break; - } - } - } - $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 ); - } - return $output; - } -/** - * check if property not exists within component - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-15 - * @param string $propName - * @return bool - */ - function _notExistProp( $propName ) { - if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed - $propName = strtolower( $propName ); - if( 'last-modified' == $propName ) { if( !isset( $this->lastmodified )) return TRUE; } - elseif( 'percent-complete' == $propName ) { if( !isset( $this->percentcomplete )) return TRUE; } - elseif( 'recurrence-id' == $propName ) { if( !isset( $this->recurrenceid )) return TRUE; } - elseif( 'related-to' == $propName ) { if( !isset( $this->relatedto )) return TRUE; } - elseif( 'request-status' == $propName ) { if( !isset( $this->requeststatus )) return TRUE; } - elseif(( 'x-' != substr($propName,0,2)) && !isset( $this->$propName )) return TRUE; - return FALSE; - } -/*********************************************************************************/ -/*********************************************************************************/ -/** - * get general component config variables or info about subcomponents - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.6 - 2011-05-14 - * @param mixed $config - * @return value - */ - function getConfig( $config = FALSE) { - if( !$config ) { - $return = array(); - $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' ); - $return['FORMAT'] = $this->getConfig( 'FORMAT' ); - if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' ))) - $return['LANGUAGE'] = $lang; - $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' ); - $return['TZTD'] = $this->getConfig( 'TZID' ); - $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' ); - return $return; - } - switch( strtoupper( $config )) { - case 'ALLOWEMPTY': - return $this->allowEmpty; - break; - case 'COMPSINFO': - unset( $this->compix ); - $info = array(); - if( isset( $this->components )) { - foreach( $this->components as $cix => $component ) { - if( empty( $component )) continue; - $info[$cix]['ordno'] = $cix + 1; - $info[$cix]['type'] = $component->objName; - $info[$cix]['uid'] = $component->getProperty( 'uid' ); - $info[$cix]['props'] = $component->getConfig( 'propinfo' ); - $info[$cix]['sub'] = $component->getConfig( 'compsinfo' ); - } - } - return $info; - break; - case 'FORMAT': - return $this->format; - break; - case 'LANGUAGE': - // get language for calendar component as defined in [RFC 1766] - return $this->language; - break; - case 'NL': - case 'NEWLINECHAR': - return $this->nl; - break; - case 'PROPINFO': - $output = array(); - if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) { - if( empty( $this->uid['value'] )) $this->_makeuid(); - $output['UID'] = 1; - } - if( !empty( $this->dtstamp )) $output['DTSTAMP'] = 1; - if( !empty( $this->summary )) $output['SUMMARY'] = 1; - if( !empty( $this->description )) $output['DESCRIPTION'] = count( $this->description ); - if( !empty( $this->dtstart )) $output['DTSTART'] = 1; - if( !empty( $this->dtend )) $output['DTEND'] = 1; - if( !empty( $this->due )) $output['DUE'] = 1; - if( !empty( $this->duration )) $output['DURATION'] = 1; - if( !empty( $this->rrule )) $output['RRULE'] = count( $this->rrule ); - if( !empty( $this->rdate )) $output['RDATE'] = count( $this->rdate ); - if( !empty( $this->exdate )) $output['EXDATE'] = count( $this->exdate ); - if( !empty( $this->exrule )) $output['EXRULE'] = count( $this->exrule ); - if( !empty( $this->action )) $output['ACTION'] = 1; - if( !empty( $this->attach )) $output['ATTACH'] = count( $this->attach ); - if( !empty( $this->attendee )) $output['ATTENDEE'] = count( $this->attendee ); - if( !empty( $this->categories )) $output['CATEGORIES'] = count( $this->categories ); - if( !empty( $this->class )) $output['CLASS'] = 1; - if( !empty( $this->comment )) $output['COMMENT'] = count( $this->comment ); - if( !empty( $this->completed )) $output['COMPLETED'] = 1; - if( !empty( $this->contact )) $output['CONTACT'] = count( $this->contact ); - if( !empty( $this->created )) $output['CREATED'] = 1; - if( !empty( $this->freebusy )) $output['FREEBUSY'] = count( $this->freebusy ); - if( !empty( $this->geo )) $output['GEO'] = 1; - if( !empty( $this->lastmodified )) $output['LAST-MODIFIED'] = 1; - if( !empty( $this->location )) $output['LOCATION'] = 1; - if( !empty( $this->organizer )) $output['ORGANIZER'] = 1; - if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1; - if( !empty( $this->priority )) $output['PRIORITY'] = 1; - if( !empty( $this->recurrenceid )) $output['RECURRENCE-ID'] = 1; - if( !empty( $this->relatedto )) $output['RELATED-TO'] = count( $this->relatedto ); - if( !empty( $this->repeat )) $output['REPEAT'] = 1; - if( !empty( $this->requeststatus )) $output['REQUEST-STATUS'] = count( $this->requeststatus ); - if( !empty( $this->resources )) $output['RESOURCES'] = count( $this->resources ); - if( !empty( $this->sequence )) $output['SEQUENCE'] = 1; - if( !empty( $this->sequence )) $output['SEQUENCE'] = 1; - if( !empty( $this->status )) $output['STATUS'] = 1; - if( !empty( $this->transp )) $output['TRANSP'] = 1; - if( !empty( $this->trigger )) $output['TRIGGER'] = 1; - if( !empty( $this->tzid )) $output['TZID'] = 1; - if( !empty( $this->tzname )) $output['TZNAME'] = count( $this->tzname ); - if( !empty( $this->tzoffsetfrom )) $output['TZOFFSETFROM'] = 1; - if( !empty( $this->tzoffsetto )) $output['TZOFFSETTO'] = 1; - if( !empty( $this->tzurl )) $output['TZURL'] = 1; - if( !empty( $this->url )) $output['URL'] = 1; - if( !empty( $this->xprop )) $output['X-PROP'] = count( $this->xprop ); - return $output; - break; - case 'TZID': - return $this->dtzid; - break; - case 'UNIQUE_ID': - if( empty( $this->unique_id )) - $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost'; - return $this->unique_id; - break; - } - } -/** - * general component config setting - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.18 - 2011-10-28 - * @param mixed $config - * @param string $value - * @param bool $softUpdate - * @return void - */ - function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) { - if( is_array( $config )) { - $ak = array_keys( $config ); - foreach( $ak as $k ) { - if( 'NEWLINECHAR' == strtoupper( $k )) { - if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] )) - return FALSE; - unset( $config[$k] ); - break; - } - } - foreach( $config as $cKey => $cValue ) { - if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate )) - return FALSE; - } - return TRUE; - } - $res = FALSE; - switch( strtoupper( $config )) { - case 'ALLOWEMPTY': - $this->allowEmpty = $value; - $subcfg = array( 'ALLOWEMPTY' => $value ); - $res = TRUE; - break; - case 'FORMAT': - $value = trim( strtolower( $value )); - $this->format = $value; - $this->_createFormat(); - $subcfg = array( 'FORMAT' => $value ); - $res = TRUE; - break; - case 'LANGUAGE': - // set language for calendar component as defined in [RFC 1766] - $value = trim( $value ); - if( empty( $this->language ) || !$softUpdate ) - $this->language = $value; - $subcfg = array( 'LANGUAGE' => $value ); - $res = TRUE; - break; - case 'NL': - case 'NEWLINECHAR': - $this->nl = $value; - $this->_createFormat(); - $subcfg = array( 'NL' => $value ); - $res = TRUE; - break; - case 'TZID': - $this->dtzid = $value; - $subcfg = array( 'TZID' => $value ); - $res = TRUE; - break; - case 'UNIQUE_ID': - $value = trim( $value ); - $this->unique_id = $value; - $subcfg = array( 'UNIQUE_ID' => $value ); - $res = TRUE; - break; - default: // any unvalid config key.. . - return TRUE; - } - if( !$res ) return FALSE; - if( isset( $subcfg ) && !empty( $this->components )) { - foreach( $subcfg as $cfgkey => $cfgvalue ) { - foreach( $this->components as $cix => $component ) { - $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate ); - if( !$res ) - break 2; - $this->components[$cix] = $component->copy(); // PHP4 compliant - } - } - } - return $res; - } -/*********************************************************************************/ -/** - * delete component property value - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param mixed $propName, bool FALSE => X-property - * @param int $propix, optional, if specific property is wanted in case of multiply occurences - * @return bool, if successfull delete TRUE - */ - function deleteProperty( $propName=FALSE, $propix=FALSE ) { - if( $this->_notExistProp( $propName )) return FALSE; - $propName = strtoupper( $propName ); - if( in_array( $propName, array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'EXDATE', 'EXRULE', - 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP' ))) { - if( !$propix ) - $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1; - $this->propdelix[$propName] = --$propix; - } - $return = FALSE; - switch( $propName ) { - case 'ACTION': - if( !empty( $this->action )) { - $this->action = ''; - $return = TRUE; - } - break; - case 'ATTACH': - return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] ); - break; - case 'ATTENDEE': - return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] ); - break; - case 'CATEGORIES': - return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] ); - break; - case 'CLASS': - if( !empty( $this->class )) { - $this->class = ''; - $return = TRUE; - } - break; - case 'COMMENT': - return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] ); - break; - case 'COMPLETED': - if( !empty( $this->completed )) { - $this->completed = ''; - $return = TRUE; - } - break; - case 'CONTACT': - return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] ); - break; - case 'CREATED': - if( !empty( $this->created )) { - $this->created = ''; - $return = TRUE; - } - break; - case 'DESCRIPTION': - return $this->deletePropertyM( $this->description, $this->propdelix[$propName] ); - break; - case 'DTEND': - if( !empty( $this->dtend )) { - $this->dtend = ''; - $return = TRUE; - } - break; - case 'DTSTAMP': - if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) - return FALSE; - if( !empty( $this->dtstamp )) { - $this->dtstamp = ''; - $return = TRUE; - } - break; - case 'DTSTART': - if( !empty( $this->dtstart )) { - $this->dtstart = ''; - $return = TRUE; - } - break; - case 'DUE': - if( !empty( $this->due )) { - $this->due = ''; - $return = TRUE; - } - break; - case 'DURATION': - if( !empty( $this->duration )) { - $this->duration = ''; - $return = TRUE; - } - break; - case 'EXDATE': - return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] ); - break; - case 'EXRULE': - return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] ); - break; - case 'FREEBUSY': - return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] ); - break; - case 'GEO': - if( !empty( $this->geo )) { - $this->geo = ''; - $return = TRUE; - } - break; - case 'LAST-MODIFIED': - if( !empty( $this->lastmodified )) { - $this->lastmodified = ''; - $return = TRUE; - } - break; - case 'LOCATION': - if( !empty( $this->location )) { - $this->location = ''; - $return = TRUE; - } - break; - case 'ORGANIZER': - if( !empty( $this->organizer )) { - $this->organizer = ''; - $return = TRUE; - } - break; - case 'PERCENT-COMPLETE': - if( !empty( $this->percentcomplete )) { - $this->percentcomplete = ''; - $return = TRUE; - } - break; - case 'PRIORITY': - if( !empty( $this->priority )) { - $this->priority = ''; - $return = TRUE; - } - break; - case 'RDATE': - return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] ); - break; - case 'RECURRENCE-ID': - if( !empty( $this->recurrenceid )) { - $this->recurrenceid = ''; - $return = TRUE; - } - break; - case 'RELATED-TO': - return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] ); - break; - case 'REPEAT': - if( !empty( $this->repeat )) { - $this->repeat = ''; - $return = TRUE; - } - break; - case 'REQUEST-STATUS': - return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] ); - break; - case 'RESOURCES': - return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] ); - break; - case 'RRULE': - return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] ); - break; - case 'SEQUENCE': - if( !empty( $this->sequence )) { - $this->sequence = ''; - $return = TRUE; - } - break; - case 'STATUS': - if( !empty( $this->status )) { - $this->status = ''; - $return = TRUE; - } - break; - case 'SUMMARY': - if( !empty( $this->summary )) { - $this->summary = ''; - $return = TRUE; - } - break; - case 'TRANSP': - if( !empty( $this->transp )) { - $this->transp = ''; - $return = TRUE; - } - break; - case 'TRIGGER': - if( !empty( $this->trigger )) { - $this->trigger = ''; - $return = TRUE; - } - break; - case 'TZID': - if( !empty( $this->tzid )) { - $this->tzid = ''; - $return = TRUE; - } - break; - case 'TZNAME': - return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] ); - break; - case 'TZOFFSETFROM': - if( !empty( $this->tzoffsetfrom )) { - $this->tzoffsetfrom = ''; - $return = TRUE; - } - break; - case 'TZOFFSETTO': - if( !empty( $this->tzoffsetto )) { - $this->tzoffsetto = ''; - $return = TRUE; - } - break; - case 'TZURL': - if( !empty( $this->tzurl )) { - $this->tzurl = ''; - $return = TRUE; - } - break; - case 'UID': - if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) - return FALSE; - if( !empty( $this->uid )) { - $this->uid = ''; - $return = TRUE; - } - break; - case 'URL': - if( !empty( $this->url )) { - $this->url = ''; - $return = TRUE; - } - break; - default: - $reduced = ''; - if( $propName != 'X-PROP' ) { - if( !isset( $this->xprop[$propName] )) return FALSE; - foreach( $this->xprop as $k => $a ) { - if(( $k != $propName ) && !empty( $a )) - $reduced[$k] = $a; - } - } - else { - if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; } - $xpropno = 0; - foreach( $this->xprop as $xpropkey => $xpropvalue ) { - if( $propix != $xpropno ) - $reduced[$xpropkey] = $xpropvalue; - $xpropno++; - } - } - $this->xprop = $reduced; - if( empty( $this->xprop )) { - unset( $this->propdelix[$propName] ); - return FALSE; - } - return TRUE; - } - return $return; - } -/*********************************************************************************/ -/** - * delete component property value, fixing components with multiple occurencies - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param array $multiprop, reference to a component property - * @param int $propix, reference to removal counter - * @return bool TRUE - */ - function deletePropertyM( & $multiprop, & $propix ) { - if( isset( $multiprop[$propix] )) - unset( $multiprop[$propix] ); - if( empty( $multiprop )) { - $multiprop = ''; - unset( $propix ); - return FALSE; - } - else - return TRUE; - } -/** - * get component property value/params - * - * if property has multiply values, consequtive function calls are needed - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.3 - 2012-01-10 - * @param string $propName, optional - * @param int @propix, optional, if specific property is wanted in case of multiply occurences - * @param bool $inclParam=FALSE - * @param bool $specform=FALSE - * @return mixed - */ - function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) { - if( $this->_notExistProp( $propName )) return FALSE; - $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP'; - if( in_array( $propName, array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'EXDATE', 'EXRULE', - 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP' ))) { - if( !$propix ) - $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1; - $this->propix[$propName] = --$propix; - } - switch( $propName ) { - case 'ACTION': - if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value']; - break; - case 'ATTACH': - $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array(); - while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value']; - break; - case 'ATTENDEE': - $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array(); - while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value']; - break; - case 'CATEGORIES': - $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array(); - while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value']; - break; - case 'CLASS': - if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value']; - break; - case 'COMMENT': - $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array(); - while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value']; - break; - case 'COMPLETED': - if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value']; - break; - case 'CONTACT': - $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array(); - while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value']; - break; - case 'CREATED': - if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value']; - break; - case 'DESCRIPTION': - $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array(); - while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value']; - break; - case 'DTEND': - if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value']; - break; - case 'DTSTAMP': - if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) - return; - if( !isset( $this->dtstamp['value'] )) - $this->_makeDtstamp(); - return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value']; - break; - case 'DTSTART': - if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value']; - break; - case 'DUE': - if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value']; - break; - case 'DURATION': - if( !isset( $this->duration['value'] )) return FALSE; - $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value']; - return ( $inclParam ) ? array( 'value' => $value, 'params' => $this->duration['params'] ) : $value; - break; - case 'EXDATE': - $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array(); - while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value']; - break; - case 'EXRULE': - $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array(); - while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value']; - break; - case 'FREEBUSY': - $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array(); - while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value']; - break; - case 'GEO': - if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value']; - break; - case 'LAST-MODIFIED': - if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value']; - break; - case 'LOCATION': - if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value']; - break; - case 'ORGANIZER': - if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value']; - break; - case 'PERCENT-COMPLETE': - if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value']; - break; - case 'PRIORITY': - if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value']; - break; - case 'RDATE': - $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array(); - while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value']; - break; - case 'RECURRENCE-ID': - if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value']; - break; - case 'RELATED-TO': - $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array(); - while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value']; - break; - case 'REPEAT': - if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value']; - break; - case 'REQUEST-STATUS': - $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array(); - while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value']; - break; - case 'RESOURCES': - $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array(); - while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value']; - break; - case 'RRULE': - $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array(); - while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value']; - break; - case 'SEQUENCE': - if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value']; - break; - case 'STATUS': - if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value']; - break; - case 'SUMMARY': - if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value']; - break; - case 'TRANSP': - if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value']; - break; - case 'TRIGGER': - if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value']; - break; - case 'TZID': - if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value']; - break; - case 'TZNAME': - $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array(); - while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak ))) - $propix++; - $this->propix[$propName] = $propix; - if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } - return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value']; - break; - case 'TZOFFSETFROM': - if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value']; - break; - case 'TZOFFSETTO': - if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value']; - break; - case 'TZURL': - if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value']; - break; - case 'UID': - if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) - return FALSE; - if( empty( $this->uid['value'] )) - $this->_makeuid(); - return ( $inclParam ) ? $this->uid : $this->uid['value']; - break; - case 'URL': - if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value']; - break; - default: - if( $propName != 'X-PROP' ) { - if( !isset( $this->xprop[$propName] )) return FALSE; - return ( $inclParam ) ? array( $propName, $this->xprop[$propName] ) - : array( $propName, $this->xprop[$propName]['value'] ); - } - else { - if( empty( $this->xprop )) return FALSE; - $xpropno = 0; - foreach( $this->xprop as $xpropkey => $xpropvalue ) { - if( $propix == $xpropno ) - return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] ) - : array( $xpropkey, $this->xprop[$xpropkey]['value'] ); - else - $xpropno++; - } - return FALSE; // not found ?? - } - } - return FALSE; - } -/** - * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-04-13 - * @param string $propName - * @param array $output, incremented result array - */ - function _getProperties( $propName, & $output ) { - if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' ))) - return output; - while( FALSE !== ( $content = $this->getProperty( $propName ))) { - if( is_array( $content )) { - foreach( $content as $part ) { - if( FALSE !== strpos( $part, ',' )) { - $part = explode( ',', $part ); - foreach( $part as $thePart ) { - $thePart = trim( $thePart ); - if( !empty( $thePart )) { - if( !isset( $output[$thePart] )) - $output[$thePart] = 1; - else - $output[$thePart] += 1; - } - } - } - else { - $part = trim( $part ); - if( !isset( $output[$part] )) - $output[$part] = 1; - else - $output[$part] += 1; - } - } - } - elseif( FALSE !== strpos( $content, ',' )) { - $content = explode( ',', $content ); - foreach( $content as $thePart ) { - $thePart = trim( $thePart ); - if( !empty( $thePart )) { - if( !isset( $output[$thePart] )) - $output[$thePart] = 1; - else - $output[$thePart] += 1; - } - } - } - else { - $content = trim( $content ); - if( !empty( $content )) { - if( !isset( $output[$content] )) - $output[$content] = 1; - else - $output[$content] += 1; - } - } - } - ksort( $output ); - return $output; - } -/** - * general component property setting - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-05 - * @param mixed $args variable number of function arguments, - * first argument is ALWAYS component name, - * second ALWAYS component value! - * @return void - */ - function setProperty() { - $numargs = func_num_args(); - if( 1 > $numargs ) return FALSE; - $arglist = func_get_args(); - if( $this->_notExistProp( $arglist[0] )) return FALSE; - if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] ))) - return FALSE; - $arglist[0] = strtoupper( $arglist[0] ); - for( $argix=$numargs; $argix < 12; $argix++ ) { - if( !isset( $arglist[$argix] )) - $arglist[$argix] = null; - } - switch( $arglist[0] ) { - case 'ACTION': - return $this->setAction( $arglist[1], $arglist[2] ); - case 'ATTACH': - return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] ); - case 'ATTENDEE': - return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] ); - case 'CATEGORIES': - return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] ); - case 'CLASS': - return $this->setClass( $arglist[1], $arglist[2] ); - case 'COMMENT': - return $this->setComment( $arglist[1], $arglist[2], $arglist[3] ); - case 'COMPLETED': - return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] ); - case 'CONTACT': - return $this->setContact( $arglist[1], $arglist[2], $arglist[3] ); - case 'CREATED': - return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] ); - case 'DESCRIPTION': - return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] ); - case 'DTEND': - return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] ); - case 'DTSTAMP': - return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] ); - case 'DTSTART': - return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] ); - case 'DUE': - return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] ); - case 'DURATION': - return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] ); - case 'EXDATE': - return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] ); - case 'EXRULE': - return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] ); - case 'FREEBUSY': - return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] ); - case 'GEO': - return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] ); - case 'LAST-MODIFIED': - return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] ); - case 'LOCATION': - return $this->setLocation( $arglist[1], $arglist[2] ); - case 'ORGANIZER': - return $this->setOrganizer( $arglist[1], $arglist[2] ); - case 'PERCENT-COMPLETE': - return $this->setPercentComplete( $arglist[1], $arglist[2] ); - case 'PRIORITY': - return $this->setPriority( $arglist[1], $arglist[2] ); - case 'RDATE': - return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] ); - case 'RECURRENCE-ID': - return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] ); - case 'RELATED-TO': - return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] ); - case 'REPEAT': - return $this->setRepeat( $arglist[1], $arglist[2] ); - case 'REQUEST-STATUS': - return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] ); - case 'RESOURCES': - return $this->setResources( $arglist[1], $arglist[2], $arglist[3] ); - case 'RRULE': - return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] ); - case 'SEQUENCE': - return $this->setSequence( $arglist[1], $arglist[2] ); - case 'STATUS': - return $this->setStatus( $arglist[1], $arglist[2] ); - case 'SUMMARY': - return $this->setSummary( $arglist[1], $arglist[2] ); - case 'TRANSP': - return $this->setTransp( $arglist[1], $arglist[2] ); - case 'TRIGGER': - return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] ); - case 'TZID': - return $this->setTzid( $arglist[1], $arglist[2] ); - case 'TZNAME': - return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] ); - case 'TZOFFSETFROM': - return $this->setTzoffsetfrom( $arglist[1], $arglist[2] ); - case 'TZOFFSETTO': - return $this->setTzoffsetto( $arglist[1], $arglist[2] ); - case 'TZURL': - return $this->setTzurl( $arglist[1], $arglist[2] ); - case 'UID': - return $this->setUid( $arglist[1], $arglist[2] ); - case 'URL': - return $this->setUrl( $arglist[1], $arglist[2] ); - default: - return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] ); - } - return FALSE; - } -/*********************************************************************************/ -/** - * parse component unparsed data into properties - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.17 - 2012-02-03 - * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings - * @return bool FALSE if error occurs during parsing - * - */ - function parse( $unparsedtext=null ) { - if( !empty( $unparsedtext )) { - $nl = $this->getConfig( 'nl' ); - if( is_array( $unparsedtext )) - $unparsedtext = implode( '\n'.$nl, $unparsedtext ); - /* fix line folding */ - $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings - $EOLmark = FALSE; - foreach( $eolchars as $eolchar ) { - if( !$EOLmark && ( FALSE !== strpos( $unparsedtext, $eolchar ))) { - $unparsedtext = str_replace( $eolchar." ", '', $unparsedtext ); - $unparsedtext = str_replace( $eolchar."\t", '', $unparsedtext ); - if( $eolchar != $nl ) - $unparsedtext = str_replace( $eolchar, $nl, $unparsedtext ); - $EOLmark = TRUE; - } - } - $tmp = explode( $nl, $unparsedtext ); - $unparsedtext = array(); - foreach( $tmp as $tmpr ) - if( !empty( $tmpr )) - $unparsedtext[] = $tmpr; - } - elseif( !isset( $this->unparsed )) - $unparsedtext = array(); - else - $unparsedtext = $this->unparsed; - $this->unparsed = array(); - $comp = & $this; - $config = $this->getConfig(); - foreach ( $unparsedtext as $line ) { - if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' ))) - $this->components[] = $comp->copy(); - elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 ))) - array_unshift( $this->components, $comp->copy()); - elseif( 'END:' == strtoupper( substr( $line, 0, 4 ))) - break; - elseif( 'BEGIN:VALARM' == strtoupper( substr( $line, 0, 12 ))) - $comp = new valarm( $config); - elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) - $comp = new vtimezone( 'standard', $config ); - elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) - $comp = new vtimezone( 'daylight', $config ); - elseif( 'BEGIN:' == strtoupper( substr( $line, 0, 6 ))) - continue; - else - $comp->unparsed[] = $line; - } - unset( $config ); - /* concatenate property values spread over several lines */ - $lastix = -1; - $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed' - , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart' - , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo' - , 'last-modified', 'location', 'organizer', 'percent-complete' - , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat' - , 'request-status', 'resources', 'rrule', 'sequence', 'status' - , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom' - , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' ); - $proprows = array(); - foreach( $this->unparsed as $line ) { - $newProp = FALSE; - foreach ( $propnames as $propname ) { - if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) { - $newProp = TRUE; - break; - } - } - if( $newProp ) { - $newProp = FALSE; - $lastix++; - $proprows[$lastix] = $line; - } - else - $proprows[$lastix] .= '!"#¤%&/()=?'.$line; - } - /* parse each property 'line' */ - $paramMStz = array( 'utc-', 'utc+', 'gmt-', 'gmt+' ); - $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ); - $paramProto4 = array( 'crid:', 'news:', 'pres:' ); - foreach( $proprows as $line ) { - $line = str_replace( '!"#¤%&/()=? ', '', $line ); - $line = str_replace( '!"#¤%&/()=?', '', $line ); - if( '\n' == substr( $line, -2 )) - $line = substr( $line, 0, strlen( $line ) - 2 ); - /* get propname, (problem with x-properties, otherwise in previous loop) */ - $cix = $propname = null; - for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) { - if( in_array( $line[$cix], array( ':', ';' ))) - break; - else - $propname .= $line[$cix]; - } - if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) { - $propname2 = $propname; - $propname = 'X-'; - } - /* rest of the line is opt.params and value */ - $line = substr( $line, $cix ); - /* separate attributes from value */ - $attr = array(); - $attrix = -1; - $clen = strlen( $line ); - $WithinQuotes = FALSE; - for( $cix=0; $cix < $clen; $cix++ ) { - if( ( ':' == $line[$cix] ) && - ( substr( $line,$cix, 3 ) != '://' ) && - ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz )) && - ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) && - ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) && - ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' ) && - !$WithinQuotes ) { - $attrEnd = TRUE; - if(( $cix < ( $clen - 4 )) && - ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr?? - for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) { - if( '://' == substr( $line, $c2ix - 2, 3 )) { - $attrEnd = FALSE; - break; // an URI with a portnr!! - } - } - } - if( $attrEnd) { - $line = substr( $line, ( $cix + 1 )); - break; - } - } - if( '"' == $line[$cix] ) - $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE; - if( ';' == $line[$cix] ) - $attr[++$attrix] = null; - else - $attr[$attrix] .= $line[$cix]; - } - /* make attributes in array format */ - $propattr = array(); - foreach( $attr as $attribute ) { - $attrsplit = explode( '=', $attribute, 2 ); - if( 1 < count( $attrsplit )) - $propattr[$attrsplit[0]] = $attrsplit[1]; - else - $propattr[] = $attribute; - } - /* call setProperty( $propname.. . */ - switch( strtoupper( $propname )) { - case 'ATTENDEE': - foreach( $propattr as $pix => $attr ) { - if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' ))) - continue; - $attr2 = explode( ',', $attr ); - if( 1 < count( $attr2 )) - $propattr[$pix] = $attr2; - } - $this->setProperty( $propname, $line, $propattr ); - break; - case 'X-': - $propname = ( isset( $propname2 )) ? $propname2 : $propname; - unset( $propname2 ); - case 'CATEGORIES': - case 'RESOURCES': - if( FALSE !== strpos( $line, ',' )) { - $llen = strlen( $line ); - $content = array( 0 => '' ); - $cix = 0; - for( $lix = 0; $lix < $llen; $lix++ ) { - if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) { - $cix++; - $content[$cix] = ''; - } - else - $content[$cix] .= $line[$lix]; - } - if( 1 < count( $content )) { - $content = array_values( $content ); - foreach( $content as $cix => $contentPart ) - $content[$cix] = calendarComponent::_strunrep( $contentPart ); - $this->setProperty( $propname, $content, $propattr ); - break; - } - else - $line = reset( $content ); - } - case 'COMMENT': - case 'CONTACT': - case 'DESCRIPTION': - case 'LOCATION': - case 'SUMMARY': - if( empty( $line )) - $propattr = null; - $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr ); - break; - case 'REQUEST-STATUS': - $values = explode( ';', $line, 3 ); - $values[1] = ( !isset( $values[1] )) ? null : calendarComponent::_strunrep( $values[1] ); - $values[2] = ( !isset( $values[2] )) ? null : calendarComponent::_strunrep( $values[2] ); - $this->setProperty( $propname - , $values[0] // statcode - , $values[1] // statdesc - , $values[2] // extdata - , $propattr ); - break; - case 'FREEBUSY': - $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing - unset( $propattr['FBTYPE'] ); - $values = explode( ',', $line ); - foreach( $values as $vix => $value ) { - $value2 = explode( '/', $value ); - if( 1 < count( $value2 )) - $values[$vix] = $value2; - } - $this->setProperty( $propname, $fbtype, $values, $propattr ); - break; - case 'GEO': - $value = explode( ';', $line, 2 ); - if( 2 > count( $value )) - $value[1] = null; - $this->setProperty( $propname, $value[0], $value[1], $propattr ); - break; - case 'EXDATE': - $values = ( !empty( $line )) ? explode( ',', $line ) : null; - $this->setProperty( $propname, $values, $propattr ); - break; - case 'RDATE': - if( empty( $line )) { - $this->setProperty( $propname, $line, $propattr ); - break; - } - $values = explode( ',', $line ); - foreach( $values as $vix => $value ) { - $value2 = explode( '/', $value ); - if( 1 < count( $value2 )) - $values[$vix] = $value2; - } - $this->setProperty( $propname, $values, $propattr ); - break; - case 'EXRULE': - case 'RRULE': - $values = explode( ';', $line ); - $recur = array(); - foreach( $values as $value2 ) { - if( empty( $value2 )) - continue; // ;-char in ending position ??? - $value3 = explode( '=', $value2, 2 ); - $rulelabel = strtoupper( $value3[0] ); - switch( $rulelabel ) { - case 'BYDAY': { - $value4 = explode( ',', $value3[1] ); - if( 1 < count( $value4 )) { - foreach( $value4 as $v5ix => $value5 ) { - $value6 = array(); - $dayno = $dayname = null; - $value5 = trim( (string) $value5 ); - if(( ctype_alpha( substr( $value5, -1 ))) && - ( ctype_alpha( substr( $value5, -2, 1 )))) { - $dayname = substr( $value5, -2, 2 ); - if( 2 < strlen( $value5 )) - $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 )); - } - if( $dayno ) - $value6[] = $dayno; - if( $dayname ) - $value6['DAY'] = $dayname; - $value4[$v5ix] = $value6; - } - } - else { - $value4 = array(); - $dayno = $dayname = null; - $value5 = trim( (string) $value3[1] ); - if(( ctype_alpha( substr( $value5, -1 ))) && - ( ctype_alpha( substr( $value5, -2, 1 )))) { - $dayname = substr( $value5, -2, 2 ); - if( 2 < strlen( $value5 )) - $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 )); - } - if( $dayno ) - $value4[] = $dayno; - if( $dayname ) - $value4['DAY'] = $dayname; - } - $recur[$rulelabel] = $value4; - break; - } - default: { - $value4 = explode( ',', $value3[1] ); - if( 1 < count( $value4 )) - $value3[1] = $value4; - $recur[$rulelabel] = $value3[1]; - break; - } - } // end - switch $rulelabel - } // end - foreach( $values.. . - $this->setProperty( $propname, $recur, $propattr ); - break; - case 'ACTION': - case 'CLASSIFICATION': - case 'STATUS': - case 'TRANSP': - case 'UID': - case 'TZID': - case 'RELATED-TO': - case 'TZNAME': - $line = calendarComponent::_strunrep( $line ); - default: - $this->setProperty( $propname, $line, $propattr ); - break; - } // end switch( $propname.. . - } // end - foreach( $proprows.. . - unset( $unparsedtext, $this->unparsed, $proprows ); - if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) { - $ckeys = array_keys( $this->components ); - foreach( $ckeys as $ckey ) { - if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) { - $this->components[$ckey]->parse(); - } - } - } - return TRUE; - } -/*********************************************************************************/ -/*********************************************************************************/ -/** - * return a copy of this component - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @return object - */ - function copy() { - $serialized_contents = serialize( $this ); - $copy = unserialize( $serialized_contents ); - return $copy; - } -/*********************************************************************************/ -/*********************************************************************************/ -/** - * delete calendar subcomponent from component container - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param mixed $arg1 ordno / component type / component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return void - */ - function deleteComponent( $arg1, $arg2=FALSE ) { - if( !isset( $this->components )) return FALSE; - $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { - $argType = 'INDEX'; - $index = (int) $arg1 - 1; - } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { - $argType = strtolower( $arg1 ); - $index = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0; - } - $cix2dC = 0; - foreach ( $this->components as $cix => $component) { - if( empty( $component )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) { - unset( $this->components[$cix] ); - return TRUE; - } - elseif( $argType == $component->objName ) { - if( $index == $cix2dC ) { - unset( $this->components[$cix] ); - return TRUE; - } - $cix2dC++; - } - elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { - unset( $this->components[$cix] ); - return TRUE; - } - } - return FALSE; - } -/** - * get calendar component subcomponent from component container - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param mixed $arg1 optional, ordno/component type/ component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return object - */ - function getComponent ( $arg1=FALSE, $arg2=FALSE ) { - if( !isset( $this->components )) return FALSE; - $index = $argType = null; - if ( !$arg1 ) { - $argType = 'INDEX'; - $index = $this->compix['INDEX'] = - ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1; - } - elseif ( ctype_digit( (string) $arg1 )) { - $argType = 'INDEX'; - $index = (int) $arg1; - unset( $this->compix ); - } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { - unset( $this->compix['INDEX'] ); - $argType = strtolower( $arg1 ); - if( !$arg2 ) - $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; - else - $index = (int) $arg2; - } - $index -= 1; - $ckeys = array_keys( $this->components ); - if( !empty( $index) && ( $index > end( $ckeys ))) - return FALSE; - $cix2gC = 0; - foreach( $this->components as $cix => $component ) { - if( empty( $component )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) - return $component->copy(); - elseif( $argType == $component->objName ) { - if( $index == $cix2gC ) - return $component->copy(); - $cix2gC++; - } - elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' ))) - return $component->copy(); - } - /* not found.. . */ - unset( $this->compix ); - return false; - } -/** - * add calendar component as subcomponent to container for subcomponents - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 1.x.x - 2007-04-24 - * @param object $component calendar component - * @return void - */ - function addSubComponent ( $component ) { - $this->setComponent( $component ); - } -/** - * create new calendar component subcomponent, already included within component - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.33 - 2011-01-03 - * @param string $compType subcomponent type - * @return object (reference) - */ - function & newComponent( $compType ) { - $config = $this->getConfig(); - $keys = array_keys( $this->components ); - $ix = end( $keys) + 1; - switch( strtoupper( $compType )) { - case 'ALARM': - case 'VALARM': - $this->components[$ix] = new valarm( $config ); - break; - case 'STANDARD': - array_unshift( $this->components, new vtimezone( 'STANDARD', $config )); - $ix = 0; - break; - case 'DAYLIGHT': - $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config ); - break; - default: - return FALSE; - } - return $this->components[$ix]; - } -/** - * add calendar component as subcomponent to container for subcomponents - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.8 - 2011-03-15 - * @param object $component calendar component - * @param mixed $arg1 optional, ordno/component type/ component uid - * @param mixed $arg2 optional, ordno if arg1 = component type - * @return bool - */ - function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) { - if( !isset( $this->components )) return FALSE; - $component->setConfig( $this->getConfig(), FALSE, TRUE ); - if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) { - /* make sure dtstamp and uid is set */ - $dummy = $component->getProperty( 'dtstamp' ); - $dummy = $component->getProperty( 'uid' ); - } - if( !$arg1 ) { // plain insert, last in chain - $this->components[] = $component->copy(); - return TRUE; - } - $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { // index insert/replace - $argType = 'INDEX'; - $index = (int) $arg1 - 1; - } - elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) { - $argType = strtolower( $arg1 ); - $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0; - } - // else if arg1 is set, arg1 must be an UID - $cix2sC = 0; - foreach ( $this->components as $cix => $component2 ) { - if( empty( $component2 )) continue; - if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace - $this->components[$cix] = $component->copy(); - return TRUE; - } - elseif( $argType == $component2->objName ) { // component Type index insert/replace - if( $index == $cix2sC ) { - $this->components[$cix] = $component->copy(); - return TRUE; - } - $cix2sC++; - } - elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace - $this->components[$cix] = $component->copy(); - return TRUE; - } - } - /* arg1=index and not found.. . insert at index .. .*/ - if( 'INDEX' == $argType ) { - $this->components[$index] = $component->copy(); - ksort( $this->components, SORT_NUMERIC ); - } - else /* not found.. . insert last in chain anyway .. .*/ - $this->components[] = $component->copy(); - return TRUE; - } -/** - * creates formatted output for subcomponents - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.20 - 2012-02-06 - * @param array $xcaldecl - * @return string - */ - function createSubComponent() { - $output = null; - if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order - $stdarr = $dlarr = array(); - foreach( $this->components as $component ) { - if( empty( $component )) - continue; - $dt = $component->getProperty( 'dtstart' ); - $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] ); - if( 'standard' == $component->objName ) { - while( isset( $stdarr[$key] )) - $key += 1; - $stdarr[$key] = $component->copy(); - } - elseif( 'daylight' == $component->objName ) { - while( isset( $dlarr[$key] )) - $key += 1; - $dlarr[$key] = $component->copy(); - } - } // end foreach( $this->components as $component ) - $this->components = array(); - ksort( $stdarr, SORT_NUMERIC ); - foreach( $stdarr as $std ) - $this->components[] = $std->copy(); - unset( $stdarr ); - ksort( $dlarr, SORT_NUMERIC ); - foreach( $dlarr as $dl ) - $this->components[] = $dl->copy(); - unset( $dlarr ); - } // end if( 'vtimezone' == $this->objName ) - foreach( $this->components as $component ) { - $component->setConfig( $this->getConfig(), FALSE, TRUE ); - $output .= $component->createComponent( $this->xcaldecl ); - } - return $output; - } -/********************************************************************************/ -/** - * break lines at pos 75 - * - * Lines of text SHOULD NOT be longer than 75 octets, excluding the line - * break. Long content lines SHOULD be split into a multiple line - * representations using a line "folding" technique. That is, a long - * line can be split between any two characters by inserting a CRLF - * immediately followed by a single linear white space character (i.e., - * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence - * of CRLF followed immediately by a single linear white space character - * is ignored (i.e., removed) when processing the content type. - * - * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where - * the reserved expression "\n" in the arg $string could be broken up by the - * folding of lines, causing ambiguity in the return string. - * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be. - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.13 - 2012-02-14 - * @param string $value - * @return string - */ - function _size75( $string ) { - $tmp = $string; - $string = ''; - $eolcharlen = strlen( '\n' ); - /* if PHP is config with mb_string and conf overload.. . */ - if( defined( 'MB_OVERLOAD_STRING' ) && ( 1 < ini_get( 'mbstring.func_overload' ))) { - $strlen = mb_strlen( $tmp ); - while( $strlen > 75 ) { - if( '\n' == mb_substr( $tmp, 75, $eolcharlen )) - $breakAtChar = 74; - else - $breakAtChar = 75; - $string .= mb_substr( $tmp, 0, $breakAtChar ); - if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl )))) - $string .= $this->nl; - $tmp = mb_substr( $tmp, $breakAtChar ); - if( !empty( $tmp )) - $tmp = ' '.$tmp; - $strlen = mb_strlen( $tmp ); - } // end while - if( 0 < $strlen ) { - $string .= $tmp; // the rest - if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl )))) - $string .= $this->nl; - } - return $string; - } - /* if PHP is not config with mb_string.. . */ - while( TRUE ) { - $bytecnt = strlen( $tmp ); - $charCnt = $ix = 0; - for( $ix = 0; $ix < $bytecnt; $ix++ ) { - if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen ))) - break; // break before '\n' - elseif( 74 < $charCnt ) { - if( '\n' == substr( $tmp, $ix, $eolcharlen )) - $ix -= 1; // don't break inside '\n' - break; // always break while-loop here - } - else { - $byte = ord( $tmp[$ix] ); - if ($byte <= 127) { // add a one byte character - $string .= substr( $tmp, $ix, 1 ); - $charCnt += 1; - } - else if ($byte >= 194 && $byte <= 223) { // start byte in two byte character - $string .= substr( $tmp, $ix, 2 ); // add a two bytes character - $charCnt += 1; - } - else if ($byte >= 224 && $byte <= 239) { // start byte in three bytes character - $string .= substr( $tmp, $ix, 3 ); // add a three bytes character - $charCnt += 1; - } - else if ($byte >= 240 && $byte <= 244) { // start byte in four bytes character - $string .= substr( $tmp, $ix, 4 ); // add a four bytes character - $charCnt += 1; - } - } - } // end for - if( $this->nl != substr( $string, ( 0 - strlen( $this->nl )))) - $string .= $this->nl; - if( FALSE === ( $tmp = substr( $tmp, $ix ))) - break; // while-loop breakes here - else - $tmp = ' '.$tmp; - } // end while - if( '\n'.$this->nl == substr( $string, ( 0 - strlen( '\n'.$this->nl )))) - $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n'.$this->nl ))).$this->nl; - return $string; - } -/** - * special characters management output - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.15 - 2010-09-24 - * @param string $string - * @return string - */ - function _strrep( $string ) { - switch( $this->format ) { - case 'xcal': - $string = str_replace( '\n', $this->nl, $string); - $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string )))); - break; - default: - $pos = 0; - $specChars = array( 'n', 'N', 'r', ',', ';' ); - while( $pos <= strlen( $string )) { - $pos = strpos( $string, "\\", $pos ); - if( FALSE === $pos ) - break; - if( !in_array( substr( $string, $pos, 1 ), $specChars )) { - $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 )); - $pos += 1; - } - $pos += 1; - } - if( FALSE !== strpos( $string, '"' )) - $string = str_replace('"', "'", $string); - if( FALSE !== strpos( $string, ',' )) - $string = str_replace(',', '\,', $string); - if( FALSE !== strpos( $string, ';' )) - $string = str_replace(';', '\;', $string); - - if( FALSE !== strpos( $string, "\r\n" )) - $string = str_replace( "\r\n", '\n', $string); - elseif( FALSE !== strpos( $string, "\r" )) - $string = str_replace( "\r", '\n', $string); - - elseif( FALSE !== strpos( $string, "\n" )) - $string = str_replace( "\n", '\n', $string); - - if( FALSE !== strpos( $string, '\N' )) - $string = str_replace( '\N', '\n', $string); -// if( FALSE !== strpos( $string, $this->nl )) - $string = str_replace( $this->nl, '\n', $string); - break; - } - return $string; - } -/** - * special characters management input (from iCal file) - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.22 - 2010-10-17 - * @param string $string - * @return string - */ - static function _strunrep( $string ) { - $string = str_replace( '\\\\', '\\', $string); - $string = str_replace( '\,', ',', $string); - $string = str_replace( '\;', ';', $string); -// $string = str_replace( '\n', $this->nl, $string); // ?? - return $string; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * class for calendar component VEVENT - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class vevent extends calendarComponent { - var $attach; - var $attendee; - var $categories; - var $comment; - var $contact; - var $class; - var $created; - var $description; - var $dtend; - var $dtstart; - var $duration; - var $exdate; - var $exrule; - var $geo; - var $lastmodified; - var $location; - var $organizer; - var $priority; - var $rdate; - var $recurrenceid; - var $relatedto; - var $requeststatus; - var $resources; - var $rrule; - var $sequence; - var $status; - var $summary; - var $transp; - var $url; - var $xprop; - // component subcomponents container - var $components; -/** - * constructor for calendar component VEVENT object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param array $config - * @return void - */ - function vevent( $config = array()) { - $this->calendarComponent(); - - $this->attach = ''; - $this->attendee = ''; - $this->categories = ''; - $this->class = ''; - $this->comment = ''; - $this->contact = ''; - $this->created = ''; - $this->description = ''; - $this->dtstart = ''; - $this->dtend = ''; - $this->duration = ''; - $this->exdate = ''; - $this->exrule = ''; - $this->geo = ''; - $this->lastmodified = ''; - $this->location = ''; - $this->organizer = ''; - $this->priority = ''; - $this->rdate = ''; - $this->recurrenceid = ''; - $this->relatedto = ''; - $this->requeststatus = ''; - $this->resources = ''; - $this->rrule = ''; - $this->sequence = ''; - $this->status = ''; - $this->summary = ''; - $this->transp = ''; - $this->url = ''; - $this->xprop = ''; - - $this->components = array(); - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VEVENT object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.16 - 2011-10-28 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createUid(); - $component .= $this->createDtstamp(); - $component .= $this->createAttach(); - $component .= $this->createAttendee(); - $component .= $this->createCategories(); - $component .= $this->createComment(); - $component .= $this->createContact(); - $component .= $this->createClass(); - $component .= $this->createCreated(); - $component .= $this->createDescription(); - $component .= $this->createDtstart(); - $component .= $this->createDtend(); - $component .= $this->createDuration(); - $component .= $this->createExdate(); - $component .= $this->createExrule(); - $component .= $this->createGeo(); - $component .= $this->createLastModified(); - $component .= $this->createLocation(); - $component .= $this->createOrganizer(); - $component .= $this->createPriority(); - $component .= $this->createRdate(); - $component .= $this->createRrule(); - $component .= $this->createRelatedTo(); - $component .= $this->createRequestStatus(); - $component .= $this->createRecurrenceid(); - $component .= $this->createResources(); - $component .= $this->createSequence(); - $component .= $this->createStatus(); - $component .= $this->createSummary(); - $component .= $this->createTransp(); - $component .= $this->createUrl(); - $component .= $this->createXprop(); - $component .= $this->createSubComponent(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * class for calendar component VTODO - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class vtodo extends calendarComponent { - var $attach; - var $attendee; - var $categories; - var $comment; - var $completed; - var $contact; - var $class; - var $created; - var $description; - var $dtstart; - var $due; - var $duration; - var $exdate; - var $exrule; - var $geo; - var $lastmodified; - var $location; - var $organizer; - var $percentcomplete; - var $priority; - var $rdate; - var $recurrenceid; - var $relatedto; - var $requeststatus; - var $resources; - var $rrule; - var $sequence; - var $status; - var $summary; - var $url; - var $xprop; - // component subcomponents container - var $components; -/** - * constructor for calendar component VTODO object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param array $config - * @return void - */ - function vtodo( $config = array()) { - $this->calendarComponent(); - - $this->attach = ''; - $this->attendee = ''; - $this->categories = ''; - $this->class = ''; - $this->comment = ''; - $this->completed = ''; - $this->contact = ''; - $this->created = ''; - $this->description = ''; - $this->dtstart = ''; - $this->due = ''; - $this->duration = ''; - $this->exdate = ''; - $this->exrule = ''; - $this->geo = ''; - $this->lastmodified = ''; - $this->location = ''; - $this->organizer = ''; - $this->percentcomplete = ''; - $this->priority = ''; - $this->rdate = ''; - $this->recurrenceid = ''; - $this->relatedto = ''; - $this->requeststatus = ''; - $this->resources = ''; - $this->rrule = ''; - $this->sequence = ''; - $this->status = ''; - $this->summary = ''; - $this->url = ''; - $this->xprop = ''; - - $this->components = array(); - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VTODO object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-11-07 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createUid(); - $component .= $this->createDtstamp(); - $component .= $this->createAttach(); - $component .= $this->createAttendee(); - $component .= $this->createCategories(); - $component .= $this->createClass(); - $component .= $this->createComment(); - $component .= $this->createCompleted(); - $component .= $this->createContact(); - $component .= $this->createCreated(); - $component .= $this->createDescription(); - $component .= $this->createDtstart(); - $component .= $this->createDue(); - $component .= $this->createDuration(); - $component .= $this->createExdate(); - $component .= $this->createExrule(); - $component .= $this->createGeo(); - $component .= $this->createLastModified(); - $component .= $this->createLocation(); - $component .= $this->createOrganizer(); - $component .= $this->createPercentComplete(); - $component .= $this->createPriority(); - $component .= $this->createRdate(); - $component .= $this->createRelatedTo(); - $component .= $this->createRequestStatus(); - $component .= $this->createRecurrenceid(); - $component .= $this->createResources(); - $component .= $this->createRrule(); - $component .= $this->createSequence(); - $component .= $this->createStatus(); - $component .= $this->createSummary(); - $component .= $this->createUrl(); - $component .= $this->createXprop(); - $component .= $this->createSubComponent(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * class for calendar component VJOURNAL - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class vjournal extends calendarComponent { - var $attach; - var $attendee; - var $categories; - var $comment; - var $contact; - var $class; - var $created; - var $description; - var $dtstart; - var $exdate; - var $exrule; - var $lastmodified; - var $organizer; - var $rdate; - var $recurrenceid; - var $relatedto; - var $requeststatus; - var $rrule; - var $sequence; - var $status; - var $summary; - var $url; - var $xprop; -/** - * constructor for calendar component VJOURNAL object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param array $config - * @return void - */ - function vjournal( $config = array()) { - $this->calendarComponent(); - - $this->attach = ''; - $this->attendee = ''; - $this->categories = ''; - $this->class = ''; - $this->comment = ''; - $this->contact = ''; - $this->created = ''; - $this->description = ''; - $this->dtstart = ''; - $this->exdate = ''; - $this->exrule = ''; - $this->lastmodified = ''; - $this->organizer = ''; - $this->rdate = ''; - $this->recurrenceid = ''; - $this->relatedto = ''; - $this->requeststatus = ''; - $this->rrule = ''; - $this->sequence = ''; - $this->status = ''; - $this->summary = ''; - $this->url = ''; - $this->xprop = ''; - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VJOURNAL object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createUid(); - $component .= $this->createDtstamp(); - $component .= $this->createAttach(); - $component .= $this->createAttendee(); - $component .= $this->createCategories(); - $component .= $this->createClass(); - $component .= $this->createComment(); - $component .= $this->createContact(); - $component .= $this->createCreated(); - $component .= $this->createDescription(); - $component .= $this->createDtstart(); - $component .= $this->createExdate(); - $component .= $this->createExrule(); - $component .= $this->createLastModified(); - $component .= $this->createOrganizer(); - $component .= $this->createRdate(); - $component .= $this->createRequestStatus(); - $component .= $this->createRecurrenceid(); - $component .= $this->createRelatedTo(); - $component .= $this->createRrule(); - $component .= $this->createSequence(); - $component .= $this->createStatus(); - $component .= $this->createSummary(); - $component .= $this->createUrl(); - $component .= $this->createXprop(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * class for calendar component VFREEBUSY - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class vfreebusy extends calendarComponent { - var $attendee; - var $comment; - var $contact; - var $dtend; - var $dtstart; - var $duration; - var $freebusy; - var $organizer; - var $requeststatus; - var $url; - var $xprop; - // component subcomponents container - var $components; -/** - * constructor for calendar component VFREEBUSY object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param array $config - * @return void - */ - function vfreebusy( $config = array()) { - $this->calendarComponent(); - - $this->attendee = ''; - $this->comment = ''; - $this->contact = ''; - $this->dtend = ''; - $this->dtstart = ''; - $this->duration = ''; - $this->freebusy = ''; - $this->organizer = ''; - $this->requeststatus = ''; - $this->url = ''; - $this->xprop = ''; - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VFREEBUSY object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.3.1 - 2007-11-19 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createUid(); - $component .= $this->createDtstamp(); - $component .= $this->createAttendee(); - $component .= $this->createComment(); - $component .= $this->createContact(); - $component .= $this->createDtstart(); - $component .= $this->createDtend(); - $component .= $this->createDuration(); - $component .= $this->createFreebusy(); - $component .= $this->createOrganizer(); - $component .= $this->createRequestStatus(); - $component .= $this->createUrl(); - $component .= $this->createXprop(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * class for calendar component VALARM - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class valarm extends calendarComponent { - var $action; - var $attach; - var $attendee; - var $description; - var $duration; - var $repeat; - var $summary; - var $trigger; - var $xprop; -/** - * constructor for calendar component VALARM object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param array $config - * @return void - */ - function valarm( $config = array()) { - $this->calendarComponent(); - - $this->action = ''; - $this->attach = ''; - $this->attendee = ''; - $this->description = ''; - $this->duration = ''; - $this->repeat = ''; - $this->summary = ''; - $this->trigger = ''; - $this->xprop = ''; - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VALARM object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-22 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createAction(); - $component .= $this->createAttach(); - $component .= $this->createAttendee(); - $component .= $this->createDescription(); - $component .= $this->createDuration(); - $component .= $this->createRepeat(); - $component .= $this->createSummary(); - $component .= $this->createTrigger(); - $component .= $this->createXprop(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/********************************************************************************** -/*********************************************************************************/ -/** - * class for calendar component VTIMEZONE - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-12 - */ -class vtimezone extends calendarComponent { - var $timezonetype; - - var $comment; - var $dtstart; - var $lastmodified; - var $rdate; - var $rrule; - var $tzid; - var $tzname; - var $tzoffsetfrom; - var $tzoffsetto; - var $tzurl; - var $xprop; - // component subcomponents container - var $components; -/** - * constructor for calendar component VTIMEZONE object - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.2 - 2011-05-01 - * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT ) - * @param array $config - * @return void - */ - function vtimezone( $timezonetype=FALSE, $config = array()) { - if( is_array( $timezonetype )) { - $config = $timezonetype; - $timezonetype = FALSE; - } - if( !$timezonetype ) - $this->timezonetype = 'VTIMEZONE'; - else - $this->timezonetype = strtoupper( $timezonetype ); - $this->calendarComponent(); - - $this->comment = ''; - $this->dtstart = ''; - $this->lastmodified = ''; - $this->rdate = ''; - $this->rrule = ''; - $this->tzid = ''; - $this->tzname = ''; - $this->tzoffsetfrom = ''; - $this->tzoffsetto = ''; - $this->tzurl = ''; - $this->xprop = ''; - - $this->components = array(); - - if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) - $config['language'] = ICAL_LANG; - if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; - if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; - if( !isset( $config['format'] )) $config['format'] = 'iCal'; - if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; - $this->setConfig( $config ); - - } -/** - * create formatted output for calendar component VTIMEZONE object instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.5.1 - 2008-10-25 - * @param array $xcaldecl - * @return string - */ - function createComponent( &$xcaldecl ) { - $objectname = $this->_createFormat(); - $component = $this->componentStart1.$objectname.$this->componentStart2.$this->nl; - $component .= $this->createTzid(); - $component .= $this->createLastModified(); - $component .= $this->createTzurl(); - $component .= $this->createDtstart(); - $component .= $this->createTzoffsetfrom(); - $component .= $this->createTzoffsetto(); - $component .= $this->createComment(); - $component .= $this->createRdate(); - $component .= $this->createRrule(); - $component .= $this->createTzname(); - $component .= $this->createXprop(); - $component .= $this->createSubComponent(); - $component .= $this->componentEnd1.$objectname.$this->componentEnd2; - if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) { - foreach( $this->xcaldecl as $localxcaldecl ) - $xcaldecl[] = $localxcaldecl; - } - return $component; - } -} -/*********************************************************************************/ -/*********************************************************************************/ -/** - * moving all utility (static) functions to a utility class - * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.1 - 2011-07-16 - * - */ -class iCalUtilityFunctions { - // Store the single instance of iCalUtilityFunctions - private static $m_pInstance; - - // Private constructor to limit object instantiation to within the class - private function __construct() { - $m_pInstance = FALSE; - } - - // Getter method for creating/returning the single instance of this class - public static function getInstance() { - if (!self::$m_pInstance) - self::$m_pInstance = new iCalUtilityFunctions(); - - return self::$m_pInstance; - } -/** - * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.30 - 2012-01-16 - * @param array $date, date to check - * @param int $parno, no of date parts (i.e. year, month.. .) - * @return array $params, property parameters - */ - public static function _chkdatecfg( $theDate, & $parno, & $params ) { - if( isset( $params['TZID'] )) - $parno = 6; - elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )) - $parno = 3; - else { - if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] )) - $parno = 7; - if( is_array( $theDate )) { - if( isset( $theDate['timestamp'] )) - $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null; - else - $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null; - if( !empty( $tzid )) { - $parno = 7; - if( !iCalUtilityFunctions::_isOffset( $tzid )) - $params['TZID'] = $tzid; // save only timezone - } - elseif( !$parno && ( 3 == count( $theDate )) && - ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))) - $parno = 3; - else - $parno = 6; - } - else { // string - $date = trim( $theDate ); - if( 'Z' == substr( $date, -1 )) - $parno = 7; // UTC DATE-TIME - elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) && - ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' )))) - $parno = 3; // DATE - $date = iCalUtilityFunctions::_date_time_string( $date, $parno ); - unset( $date['unparsedtext'] ); - if( !empty( $date['tz'] )) { - $parno = 7; - if( !iCalUtilityFunctions::_isOffset( $date['tz'] )) - $params['TZID'] = $date['tz']; // save only timezone - } - elseif( empty( $parno )) - $parno = 6; - } - if( isset( $params['TZID'] )) - $parno = 6; - } - } -/** - * create timezone and standard/daylight components - * - * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone: - * - * BEGIN:VTIMEZONE - * TZID:Europe/Stockholm - * BEGIN:STANDARD - * DTSTART:20101031T020000 - * TZOFFSETFROM:+0200 - * TZOFFSETTO:+0100 - * TZNAME:CET - * END:STANDARD - * BEGIN:DAYLIGHT - * DTSTART:20100328T030000 - * TZOFFSETFROM:+0100 - * TZOFFSETTO:+0200 - * TZNAME:CEST - * END:DAYLIGHT - * END:VTIMEZONE - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-02-06 - * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi - * @param object $calendar, reference to an iCalcreator calendar instance - * @param string $timezone, a PHP5 (DateTimeZone) valid timezone - * @param array $xProp, *[x-propName => x-propValue], optional - * @param int $from an unix timestamp - * @param int $to an unix timestamp - * @return bool - */ - public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) { - if( !class_exists( 'DateTimeZone' )) - return FALSE; - if( empty( $timezone )) - return FALSE; - try { - $dtz = new DateTimeZone( $timezone ); - $transitions = $dtz->getTransitions(); - unset( $dtz ); - $utcTz = new DateTimeZone( 'UTC' ); - } - catch( Exception $e ) { - return FALSE; - } - if( empty( $to )) - $dates = array_keys( $calendar->getProperty( 'dtstart' )); - $transCnt = 2; // number of transitions in output if empty input $from/$to and an empty dates-array - $dateFrom = new DateTime( 'now' ); - $dateTo = new DateTime( 'now' ); - if( !empty( $from )) - $dateFrom->setTimestamp( $from ); - else { - if( !empty( $dates )) - $dateFrom = new DateTime( reset( $dates )); // set lowest date to the lowest dtstart date - $dateFrom->modify( '-1 month' ); // set $dateFrom to one month before the lowest date - } - $dateFrom->setTimezone( $utcTz ); // convert local date to UTC - if( !empty( $to )) - $dateTo->setTimestamp( $to ); - else { - if( !empty( $dates )) { - $dateTo = new DateTime( end( $dates )); // set highest date to the highest dtstart date - $to = $dateTo->getTimestamp(); // set mark that a highest date is found - } - $dateTo->modify( '+1 year' ); // set $dateTo to one year after the highest date - } - $dateTo->setTimezone( $utcTz ); // convert local date to UTC - $transTemp = array(); - $prevOffsetfrom = $stdCnt = $dlghtCnt = 0; - $stdIx = $dlghtIx = null; - $date = new DateTime( 'now', $utcTz ); - foreach( $transitions as $tix => $trans ) { // all transitions in date-time order!! - $date->setTimestamp( $trans['ts'] ); // set transition date (UTC) - if ( $date < $dateFrom ) { - $prevOffsetfrom = $trans['offset']; // previous trans offset will be 'next' trans offsetFrom - continue; - } - if( $date > $dateTo ) - break; // loop always (?) breaks here - if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) { - $trans['offsetfrom'] = $prevOffsetfrom; // i.e. set previous offsetto as offsetFrom - $date->modify( $trans['offsetfrom'].'seconds' ); // convert utc date to local date - $trans['time'] = array( 'year' => $date->format( 'Y' ) // set dtstart to array to ease up dtstart and (opt) rdate setting - , 'month' => $date->format( 'n' ) - , 'day' => $date->format( 'j' ) - , 'hour' => $date->format( 'G' ) - , 'min' => $date->format( 'i' ) - , 'sec' => $date->format( 's' )); - } - $prevOffsetfrom = $trans['offset']; - $trans['prevYear'] = $trans['time']['year']; - if( TRUE !== $trans['isdst'] ) { // standard timezone - if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] ) && // check for any rdate's (in strict year order) - ( $transTemp[$stdIx]['abbr'] == $trans['abbr'] ) && - ( $transTemp[$stdIx]['offsetfrom'] == $trans['offsetfrom'] ) && - ( $transTemp[$stdIx]['offset'] == $trans['offset'] ) && - (($transTemp[$stdIx]['prevYear'] + 1) == $trans['time']['year'] )) { - $transTemp[$stdIx]['prevYear'] = $trans['time']['year']; - $transTemp[$stdIx]['rdate'][] = $trans['time']; - continue; - } - $stdIx = $tix; - $stdCnt += 1; - } // end standard timezone - else { // daylight timezone - if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any rdate's (in strict year order) - ( $transTemp[$dlghtIx]['abbr'] == $trans['abbr'] ) && - ( $transTemp[$dlghtIx]['offsetfrom'] == $trans['offsetfrom'] ) && - ( $transTemp[$dlghtIx]['offset'] == $trans['offset'] ) && - (($transTemp[$dlghtIx]['prevYear'] + 1) == $trans['time']['year'] )) { - $transTemp[$dlghtIx]['prevYear'] = $trans['time']['year']; - $transTemp[$dlghtIx]['rdate'][] = $trans['time']; - continue; - } - $dlghtIx = $tix; - $dlghtCnt += 1; - } // end daylight timezone - if( empty( $to ) && ( $transCnt == count( $transTemp ))) { // store only $transCnt transitions - if( TRUE !== $transTemp[0]['isdst'] ) - $stdCnt -= 1; - else - $dlghtCnt -= 1; - array_shift( $transTemp ); - } // end if( empty( $to ) && ( $transCnt == count( $transTemp ))) - $transTemp[$tix] = $trans; - } // end foreach( $transitions as $tix => $trans ) - unset( $transitions ); - if( empty( $transTemp )) - return FALSE; - $tz = & $calendar->newComponent( 'vtimezone' ); - $tz->setproperty( 'tzid', $timezone ); - if( !empty( $xProp )) { - foreach( $xProp as $xPropName => $xPropValue ) - if( 'x-' == strtolower( substr( $xPropName, 0, 2 ))) - $tz->setproperty( $xPropName, $xPropValue ); - } - foreach( $transTemp as $trans ) { - $type = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight'; - $scomp = & $tz->newComponent( $type ); - $scomp->setProperty( 'dtstart', $trans['time'] ); -// $scomp->setProperty( 'x-utc-timestamp', $trans['ts'] ); // test ### - if( !empty( $trans['abbr'] )) - $scomp->setProperty( 'tzname', $trans['abbr'] ); - $scomp->setProperty( 'tzoffsetfrom', iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] )); - $scomp->setProperty( 'tzoffsetto', iCalUtilityFunctions::offsetSec2His( $trans['offset'] )); - if( isset( $trans['rdate'] )) - $scomp->setProperty( 'RDATE', $trans['rdate'] ); - } - return TRUE; - } -/** - * convert a date/datetime (array) to timestamp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.8 - 2008-10-30 - * @param array $datetime datetime/(date) - * @param string $tz timezone - * @return timestamp - */ - public static function _date2timestamp( $datetime, $tz=null ) { - $output = null; - if( !isset( $datetime['hour'] )) $datetime['hour'] = '0'; - if( !isset( $datetime['min'] )) $datetime['min'] = '0'; - if( !isset( $datetime['sec'] )) $datetime['sec'] = '0'; - foreach( $datetime as $dkey => $dvalue ) { - if( 'tz' != $dkey ) - $datetime[$dkey] = (integer) $dvalue; - } - if( $tz ) - $datetime['tz'] = $tz; - $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) : 0; - $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] ); - return $output; - } -/** - * ensures internal date-time/date format for input date-time/date in array format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.4 - 2012-03-18 - * @param array $datetime - * @param int $parno optional, default FALSE - * @return array - */ - public static function _date_time_array( $datetime, $parno=FALSE ) { - $output = array(); - foreach( $datetime as $dateKey => $datePart ) { - switch ( $dateKey ) { - case '0': case 'year': $output['year'] = $datePart; break; - case '1': case 'month': $output['month'] = $datePart; break; - case '2': case 'day': $output['day'] = $datePart; break; - } - if( 3 != $parno ) { - switch ( $dateKey ) { - case '0': - case '1': - case '2': break; - case '3': case 'hour': $output['hour'] = $datePart; break; - case '4': case 'min' : $output['min'] = $datePart; break; - case '5': case 'sec' : $output['sec'] = $datePart; break; - case '6': case 'tz' : $output['tz'] = $datePart; break; - } - } - } - if( 3 != $parno ) { - if( !isset( $output['hour'] )) - $output['hour'] = 0; - if( !isset( $output['min'] )) - $output['min'] = 0; - if( !isset( $output['sec'] )) - $output['sec'] = 0; - if( isset( $output['tz'] ) && ( 'Z' != $output['tz'] ) && - (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] ))) - $output['tz'] = 'Z'; - } - return $output; - } -/** - * ensures internal date-time/date format for input date-time/date in string fromat - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.30 - 2012-01-06 - * Modified to also return original string value by Yitzchok Lavi - * @param array $datetime - * @param int $parno optional, default FALSE - * @return array - */ - public static function _date_time_string( $datetime, $parno=FALSE ) { - // save original input string to return it later - $unparseddatetime = $datetime; - $datetime = (string) trim( $datetime ); - $tz = null; - $len = strlen( $datetime ) - 1; - if( 'Z' == substr( $datetime, -1 )) { - $tz = 'Z'; - $datetime = trim( substr( $datetime, 0, $len )); - } - elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date - ( '-' == substr( $datetime, -3, 1 )) || - ( ':' == substr( $datetime, -3, 1 )) || - ( '.' == substr( $datetime, -3, 1 ))) { - $continue = TRUE; - } - elseif( ( ctype_digit( substr( $datetime, -4, 4 ))) && // 4 pos offset - ( ' +' == substr( $datetime, -6, 2 )) || - ( ' -' == substr( $datetime, -6, 2 ))) { - $tz = substr( $datetime, -5, 5 ); - $datetime = substr( $datetime, 0, ($len - 5)); - } - elseif( ( ctype_digit( substr( $datetime, -6, 6 ))) && // 6 pos offset - ( ' +' == substr( $datetime, -8, 2 )) || - ( ' -' == substr( $datetime, -8, 2 ))) { - $tz = substr( $datetime, -7, 7 ); - $datetime = substr( $datetime, 0, ($len - 7)); - } - elseif( ( 6 < $len ) && ( ctype_digit( substr( $datetime, -6, 6 )))) { - $continue = TRUE; - } - elseif( 'T' == substr( $datetime, -7, 1 )) { - $continue = TRUE; - } - else { - $cx = $tx = 0; // 19970415T133000 US-Eastern - for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) { - $char = substr( $datetime, $cx, 1 ); - if(( ' ' == $char) || ctype_digit( $char)) - break; // if exists, tz ends here.. . ? - else - $tx--; // tz length counter - } - if( 0 > $tx ) { - $tz = substr( $datetime, $tx ); - $datetime = trim( substr( $datetime, 0, $len + $tx + 1 )); - } - } - if( 0 < substr_count( $datetime, '-' )) { - $datetime = str_replace( '-', '/', $datetime ); - } - elseif( ctype_digit( substr( $datetime, 0, 8 )) && - ( 'T' == substr( $datetime, 8, 1 )) && - ctype_digit( substr( $datetime, 9, 6 ))) { - } - $datestring = date( 'Y-m-d H:i:s', strtotime( $datetime )); - $tz = trim( $tz ); - $output = array(); - $output['year'] = substr( $datestring, 0, 4 ); - $output['month'] = substr( $datestring, 5, 2 ); - $output['day'] = substr( $datestring, 8, 2 ); - if(( 6 == $parno ) || ( 7 == $parno ) || ( !$parno && ( 'Z' == $tz ))) { - $output['hour'] = substr( $datestring, 11, 2 ); - $output['min'] = substr( $datestring, 14, 2 ); - $output['sec'] = substr( $datestring, 17, 2 ); - if( !empty( $tz )) - $output['tz'] = $tz; - } - elseif( 3 != $parno ) { - if(( '00' < substr( $datestring, 11, 2 )) || - ( '00' < substr( $datestring, 14, 2 )) || - ( '00' < substr( $datestring, 17, 2 ))) { - $output['hour'] = substr( $datestring, 11, 2 ); - $output['min'] = substr( $datestring, 14, 2 ); - $output['sec'] = substr( $datestring, 17, 2 ); - } - if( !empty( $tz )) - $output['tz'] = $tz; - } - // return original string in the array in case strtotime failed to make sense of it - $output['unparsedtext'] = $unparseddatetime; - return $output; - } -/** - * convert local startdate/enddate (Ymd[His]) to duration array - * - * uses this component dates if missing input dates - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.11 - 2010-10-21 - * @param array $startdate - * @param array $duration - * @return array duration - */ - public static function _date2duration( $startdate, $enddate ) { - $startWdate = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] ); - $endWdate = mktime( 0, 0, 0, $enddate['month'], $enddate['day'], $enddate['year'] ); - $wduration = $endWdate - $startWdate; - $dur = array(); - $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 )); - $wduration = $wduration % ( 7 * 24 * 60 * 60 ); - $dur['day'] = (int) floor( $wduration / ( 24 * 60 * 60 )); - $wduration = $wduration % ( 24 * 60 * 60 ); - $dur['hour'] = (int) floor( $wduration / ( 60 * 60 )); - $wduration = $wduration % ( 60 * 60 ); - $dur['min'] = (int) floor( $wduration / ( 60 )); - $dur['sec'] = (int) $wduration % ( 60 ); - return $dur; - } -/** - * ensures internal duration format for input in array format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.1.1 - 2007-06-24 - * @param array $duration - * @return array - */ - public static function _duration_array( $duration ) { - $output = array(); - if( is_array( $duration ) && - ( 1 == count( $duration )) && - isset( $duration['sec'] ) && - ( 60 < $duration['sec'] )) { - $durseconds = $duration['sec']; - $output['week'] = floor( $durseconds / ( 60 * 60 * 24 * 7 )); - $durseconds = $durseconds % ( 60 * 60 * 24 * 7 ); - $output['day'] = floor( $durseconds / ( 60 * 60 * 24 )); - $durseconds = $durseconds % ( 60 * 60 * 24 ); - $output['hour'] = floor( $durseconds / ( 60 * 60 )); - $durseconds = $durseconds % ( 60 * 60 ); - $output['min'] = floor( $durseconds / ( 60 )); - $output['sec'] = ( $durseconds % ( 60 )); - } - else { - foreach( $duration as $durKey => $durValue ) { - if( empty( $durValue )) continue; - switch ( $durKey ) { - case '0': case 'week': $output['week'] = $durValue; break; - case '1': case 'day': $output['day'] = $durValue; break; - case '2': case 'hour': $output['hour'] = $durValue; break; - case '3': case 'min': $output['min'] = $durValue; break; - case '4': case 'sec': $output['sec'] = $durValue; break; - } - } - } - if( isset( $output['week'] ) && ( 0 < $output['week'] )) { - unset( $output['day'], $output['hour'], $output['min'], $output['sec'] ); - return $output; - } - unset( $output['week'] ); - if( empty( $output['day'] )) - unset( $output['day'] ); - if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) { - if( !isset( $output['hour'] )) $output['hour'] = 0; - if( !isset( $output['min'] )) $output['min'] = 0; - if( !isset( $output['sec'] )) $output['sec'] = 0; - if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] )) - unset( $output['hour'], $output['min'], $output['sec'] ); - } - return $output; - } -/** - * ensures internal duration format for input in string format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.0.5 - 2007-03-14 - * @param string $duration - * @return array - */ - public static function _duration_string( $duration ) { - $duration = (string) trim( $duration ); - while( 'P' != strtoupper( substr( $duration, 0, 1 ))) { - if( 0 < strlen( $duration )) - $duration = substr( $duration, 1 ); - else - return false; // no leading P !?!? - } - $duration = substr( $duration, 1 ); // skip P - $duration = str_replace ( 't', 'T', $duration ); - $duration = str_replace ( 'T', '', $duration ); - $output = array(); - $val = null; - for( $ix=0; $ix < strlen( $duration ); $ix++ ) { - switch( strtoupper( substr( $duration, $ix, 1 ))) { - case 'W': - $output['week'] = $val; - $val = null; - break; - case 'D': - $output['day'] = $val; - $val = null; - break; - case 'H': - $output['hour'] = $val; - $val = null; - break; - case 'M': - $output['min'] = $val; - $val = null; - break; - case 'S': - $output['sec'] = $val; - $val = null; - break; - default: - if( !ctype_digit( substr( $duration, $ix, 1 ))) - return false; // unknown duration control character !?!? - else - $val .= substr( $duration, $ix, 1 ); - } - } - return iCalUtilityFunctions::_duration_array( $output ); - } -/** - * convert duration to date in array format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.8.7 - 2011-03-03 - * @param array $startdate - * @param array $duration - * @return array, date format - */ - public static function _duration2date( $startdate=null, $duration=null ) { - if( empty( $startdate )) return FALSE; - if( empty( $duration )) return FALSE; - $dateOnly = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE; - $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0; - $startdate['min'] = ( isset( $startdate['min'] )) ? $startdate['min'] : 0; - $startdate['sec'] = ( isset( $startdate['sec'] )) ? $startdate['sec'] : 0; - $dtend = 0; - if( isset( $duration['week'] )) - $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 ); - if( isset( $duration['day'] )) - $dtend += ( $duration['day'] * 24 * 60 * 60 ); - if( isset( $duration['hour'] )) - $dtend += ( $duration['hour'] * 60 *60 ); - if( isset( $duration['min'] )) - $dtend += ( $duration['min'] * 60 ); - if( isset( $duration['sec'] )) - $dtend += $duration['sec']; - $dtend = mktime( $startdate['hour'], $startdate['min'], ( $startdate['sec'] + $dtend ), $startdate['month'], $startdate['day'], $startdate['year'] ); - $dtend2 = array(); - $dtend2['year'] = date('Y', $dtend ); - $dtend2['month'] = date('m', $dtend ); - $dtend2['day'] = date('d', $dtend ); - $dtend2['hour'] = date('H', $dtend ); - $dtend2['min'] = date('i', $dtend ); - $dtend2['sec'] = date('s', $dtend ); - if( isset( $startdate['tz'] )) - $dtend2['tz'] = $startdate['tz']; - if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] ))) - unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] ); - return $dtend2; - } -/** - * if not preSet, if exist, remove key with expected value from array and return hit value else return elseValue - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-11-08 - * @param array $array - * @param string $expkey, expected key - * @param string $expval, expected value - * @param int $hitVal optional, return value if found - * @param int $elseVal optional, return value if not found - * @param int $preSet optional, return value if already preset - * @return int - */ - public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) { - if( $preSet ) - return $preSet; - if( !is_array( $array ) || ( 0 == count( $array ))) - return $elseVal; - foreach( $array as $key => $value ) { - if( strtoupper( $expkey ) == strtoupper( $key )) { - if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) { - unset( $array[$key] ); - return $hitVal; - } - } - } - return $elseVal; - } -/** - * creates formatted output for calendar component property data value type date/date-time - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-03-17 - * @param array $datetime - * @param int $parno, optional, default 6 - * @return string - */ - public static function _format_date_time( $datetime, $parno=6 ) { - if( !isset( $datetime['year'] ) && - !isset( $datetime['month'] ) && - !isset( $datetime['day'] ) && - !isset( $datetime['hour'] ) && - !isset( $datetime['min'] ) && - !isset( $datetime['sec'] )) - return ; - $output = null; - foreach( $datetime as $dkey => & $dvalue ) - if( 'tz' != $dkey ) $dvalue = (integer) $dvalue; - $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] ); - if( isset( $datetime['hour'] ) || - isset( $datetime['min'] ) || - isset( $datetime['sec'] ) || - isset( $datetime['tz'] )) { - if( isset( $datetime['tz'] ) && - !isset( $datetime['hour'] )) - $datetime['hour'] = 0; - if( isset( $datetime['hour'] ) && - !isset( $datetime['min'] )) - $datetime['min'] = 0; - if( isset( $datetime['hour'] ) && - isset( $datetime['min'] ) && - !isset( $datetime['sec'] )) - $datetime['sec'] = 0; - $output .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] ); - if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) { - $datetime['tz'] = trim( $datetime['tz'] ); - if( 'Z' == $datetime['tz'] ) - $output .= 'Z'; - $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ); - if( 0 != $offset ) { - $date = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year']); - $output = date( 'Ymd\THis\Z', $date ); - } - } - elseif( 7 == $parno ) - $output .= 'Z'; - } - return $output; - } -/** - * creates formatted output for calendar component property data value type duration - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.9.9 - 2011-06-17 - * @param array $duration ( week, day, hour, min, sec ) - * @return string - */ - public static function _format_duration( $duration ) { - if( isset( $duration['week'] ) || - isset( $duration['day'] ) || - isset( $duration['hour'] ) || - isset( $duration['min'] ) || - isset( $duration['sec'] )) - $ok = TRUE; - else - return; - if( isset( $duration['week'] ) && ( 0 < $duration['week'] )) - return 'P'.$duration['week'].'W'; - $output = 'P'; - if( isset($duration['day'] ) && ( 0 < $duration['day'] )) - $output .= $duration['day'].'D'; - if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) || - ( isset( $duration['min']) && ( 0 < $duration['min'] )) || - ( isset( $duration['sec']) && ( 0 < $duration['sec'] ))) - $output .= 'T'; - $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : ''; - $output .= ( isset( $duration['min']) && ( 0 < $duration['min'] )) ? $duration['min']. 'M' : ''; - $output .= ( isset( $duration['sec']) && ( 0 < $duration['sec'] )) ? $duration['sec']. 'S' : ''; - if( 'P' == $output ) - $output = 'PT0S'; - return $output; - } -/** - * checks if input array contains a date - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-01-20 - * @param array $input - * @return bool - */ - public static function _isArrayDate( $input ) { - if( !is_array( $input )) - return FALSE; - if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 )))) - return FALSE; - if( 7 == count( $input )) - return TRUE; - if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) - return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); - if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] )) - return FALSE; - if( in_array( 0, $input )) - return FALSE; - if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] )) - return FALSE; - if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) && - checkdate( (int) $input[1], (int) $input[2], (int) $input[0] )) - return TRUE; - $input = iCalUtilityFunctions::_date_time_string( $input[1].'/'.$input[2].'/'.$input[0], 3 ); // m - d - Y - if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) - return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); - return FALSE; - } -/** - * checks if input array contains a timestamp date - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-10-18 - * @param array $input - * @return bool - */ - public static function _isArrayTimestampDate( $input ) { - return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ; - } -/** - * controll if input string contains trailing UTC offset - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-10-19 - * @param string $input - * @return bool - */ - public static function _isOffset( $input ) { - $input = trim( (string) $input ); - if( 'Z' == substr( $input, -1 )) - return TRUE; - elseif(( 5 <= strlen( $input )) && - ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) && - ( '0000' < substr( $input, -4 )) && ( '9999' >= substr( $input, -4 ))) - return TRUE; - elseif(( 7 <= strlen( $input )) && - ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && - ( '000000' < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) - return TRUE; - return FALSE; - } -/** - * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone - * matching (MS) UCT offset and time zone descriptors - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.29 - 2012-01-11 - * @param string $timezone, input/output variable reference - * @return bool - */ - public static function ms2phpTZ( & $timezone ) { - if( !class_exists( 'DateTimeZone' )) - return FALSE; - if( empty( $timezone )) - return FALSE; - $search = str_replace( '"', '', $timezone ); - $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search ); - if( '(UTC' != substr( $search, 0, 4 )) - return FALSE; - if( FALSE === ( $pos = strpos( $search, ')' ))) - return FALSE; - $pos = strpos( $search, ')' ); - $searchOffset = substr( $search, 4, ( $pos - 4 )); - $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset )); - while( ' ' ==substr( $search, ( $pos + 1 ))) - $pos += 1; - $searchText = trim( str_replace( array( '(', ')', '&', ',', ' ' ), ' ', substr( $search, ( $pos + 1 )) )); - $searchWords = explode( ' ', $searchText ); - $timezone_abbreviations = DateTimeZone::listAbbreviations(); - $hits = array(); - foreach( $timezone_abbreviations as $name => $transitions ) { - foreach( $transitions as $cnt => $transition ) { - if( empty( $transition['offset'] ) || - empty( $transition['timezone_id'] ) || - ( $transition['offset'] != $searchOffset )) - continue; - $cWords = explode( '/', $transition['timezone_id'] ); - $cPrio = $hitCnt = $rank = 0; - foreach( $cWords as $cWord ) { - if( empty( $cWord )) - continue; - $cPrio += 1; - $sPrio = 0; - foreach( $searchWords as $sWord ) { - if( empty( $sWord ) || ( 'time' == strtolower( $sWord ))) - continue; - $sPrio += 1; - if( strtolower( $cWord ) == strtolower( $sWord )) { - $hitCnt += 1; - $rank += ( $cPrio + $sPrio ); - } - else - $rank += 10; - } - } - if( 0 < $hitCnt ) { - $hits[$rank][] = $transition['timezone_id']; - } - } - } - unset( $timezone_abbreviations ); - if( empty( $hits )) - return FALSE; - ksort( $hits ); - foreach( $hits as $rank => $tzs ) { - if( !empty( $tzs )) { - $timezone = reset( $tzs ); - return TRUE; - } - } - return FALSE; - } -/** - * transform offset in seconds to [-/+]hhmm[ss] - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2011-05-02 - * @param string $seconds - * @return string - */ - public static function offsetSec2His( $seconds ) { - if( '-' == substr( $seconds, 0, 1 )) { - $prefix = '-'; - $seconds = substr( $seconds, 1 ); - } - elseif( '+' == substr( $seconds, 0, 1 )) { - $prefix = '+'; - $seconds = substr( $seconds, 1 ); - } - else - $prefix = '+'; - $output = ''; - $hour = (int) floor( $seconds / 3600 ); - if( 10 > $hour ) - $hour = '0'.$hour; - $seconds = $seconds % 3600; - $min = (int) floor( $seconds / 60 ); - if( 10 > $min ) - $min = '0'.$min; - $output = $hour.$min; - $seconds = $seconds % 60; - if( 0 < $seconds) { - if( 9 < $seconds) - $output .= $seconds; - else - $output .= '0'.$seconds; - } - return $prefix.$output; - } -/** - * remakes a recur pattern to an array of dates - * - * if missing, UNTIL is set 1 year from startdate (emergency break) - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.10.19 - 2011-10-31 - * @param array $result, array to update, array([timestamp] => timestamp) - * @param array $recur, pattern for recurrency (only value part, params ignored) - * @param array $wdate, component start date - * @param array $startdate, start date - * @param array $enddate, optional - * @return array of recurrence (start-)dates as index - * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start - */ - public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) { - foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v; - $wdateStart = $wdate; - $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); - $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate ); - if( !$enddate ) { - $enddate = $startdate; - $enddate['year'] += 1; - } -// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."
\n";print_r($recur);echo "
\n";//test### - $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break - if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] )) - $recur['UNTIL'] = $enddate; // create break - if( isset( $recur['UNTIL'] )) { - $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] ); - if( $endDatets > $tdatets ) { - $endDatets = $tdatets; // emergency break - $enddate = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); - } - else - $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); - } - if( $wdatets > $endDatets ) { -// echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."
\n";//test - return array(); // nothing to do.. . - } - if( !isset( $recur['FREQ'] )) // "MUST be specified.. ." - $recur['FREQ'] = 'DAILY'; // ?? - $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ?? - $weekStart = (int) date( 'W', ( $wdatets + $wkst )); - if( !isset( $recur['INTERVAL'] )) - $recur['INTERVAL'] = 1; - $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence - /* find out how to step up dates and set index for interval count */ - $step = array(); - if( 'YEARLY' == $recur['FREQ'] ) - $step['year'] = 1; - elseif( 'MONTHLY' == $recur['FREQ'] ) - $step['month'] = 1; - elseif( 'WEEKLY' == $recur['FREQ'] ) - $step['day'] = 7; - else - $step['day'] = 1; - if( isset( $step['year'] ) && isset( $recur['BYMONTH'] )) - $step = array( 'month' => 1 ); - if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ?? - $step = array( 'day' => 7 ); - if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] )) - $step = array( 'day' => 1 ); - $intervalarr = array(); - if( 1 < $recur['INTERVAL'] ) { - $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); - $intervalarr = array( $intervalix => 0 ); - } - if( isset( $recur['BYSETPOS'] )) { // save start date + weekno - $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array(); -// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold
\n"; // test ### - if( is_array( $recur['BYSETPOS'] )) { - foreach( $recur['BYSETPOS'] as $bix => $bval ) - $recur['BYSETPOS'][$bix] = (int) $bval; - } - else - $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] ); - if( 'YEARLY' == $recur['FREQ'] ) { - $wdate['month'] = $wdate['day'] = 1; // start from beginning of year - $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); - iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year - } - elseif( 'MONTHLY' == $recur['FREQ'] ) { - $wdate['day'] = 1; // start from beginning of month - $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); - iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month - } - else - iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period -// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."
\n";//test### - $bysetposWold = (int) date( 'W', ( $wdatets + $wkst )); - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - $bysetposDold = $wdate['day']; - } - else - iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); - $year_old = null; - $daynames = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); - /* MAIN LOOP */ -// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."
\n";//test - while( TRUE ) { - if( isset( $endDatets ) && ( $wdatets > $endDatets )) - break; - if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) - break; - if( $year_old != $wdate['year'] ) { - $year_old = $wdate['year']; - $daycnts = array(); - $yeardays = $weekno = 0; - $yeardaycnt = array(); - foreach( $daynames as $dn ) - $yeardaycnt[$dn] = 0; - for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters - $daycnts[$m] = array(); - $weekdaycnt = array(); - foreach( $daynames as $dn ) - $weekdaycnt[$dn] = 0; - $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); - for( $d = 1; $d <= $mcnt; $d++ ) { - $daycnts[$m][$d] = array(); - if( isset( $recur['BYYEARDAY'] )) { - $yeardays++; - $daycnts[$m][$d]['yearcnt_up'] = $yeardays; - } - if( isset( $recur['BYDAY'] )) { - $day = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] )); - $day = $daynames[$day]; - $daycnts[$m][$d]['DAY'] = $day; - $weekdaycnt[$day]++; - $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day]; - $yeardaycnt[$day]++; - $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day]; - } - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) - $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year'])); - } - } - $daycnt = 0; - $yeardaycnt = array(); - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) { - $weekno = null; - for( $d=31; $d > 25; $d-- ) { // get last weekno for year - if( !$weekno ) - $weekno = $daycnts[12][$d]['weekno_up']; - elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) { - $weekno = $daycnts[12][$d]['weekno_up']; - break; - } - } - } - for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters - $weekdaycnt = array(); - foreach( $daynames as $dn ) - $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; - $monthcnt = 0; - $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); - for( $d = $mcnt; $d > 0; $d-- ) { - if( isset( $recur['BYYEARDAY'] )) { - $daycnt -= 1; - $daycnts[$m][$d]['yearcnt_down'] = $daycnt; - } - if( isset( $recur['BYMONTHDAY'] )) { - $monthcnt -= 1; - $daycnts[$m][$d]['monthcnt_down'] = $monthcnt; - } - if( isset( $recur['BYDAY'] )) { - $day = $daycnts[$m][$d]['DAY']; - $weekdaycnt[$day] -= 1; - $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day]; - $yeardaycnt[$day] -= 1; - $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day]; - } - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) - $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1); - } - } - } - /* check interval */ - if( 1 < $recur['INTERVAL'] ) { - /* create interval index */ - $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); - /* check interval */ - $currentKey = array_keys( $intervalarr ); - $currentKey = end( $currentKey ); // get last index - if( $currentKey != $intervalix ) - $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 )); - if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) && - ( 0 != $intervalarr[$intervalix] )) { - /* step up date */ -// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
\n";//test - iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); - continue; - } - else // continue within the selected interval - $intervalarr[$intervalix] = 0; -// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
\n";//test - } - $updateOK = TRUE; - if( $updateOK && isset( $recur['BYMONTH'] )) - $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH'] - , $wdate['month'] - ,($wdate['month'] - 13)); - if( $updateOK && isset( $recur['BYWEEKNO'] )) - $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO'] - , $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] - , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] ); - if( $updateOK && isset( $recur['BYYEARDAY'] )) - $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY'] - , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up'] - , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] ); - if( $updateOK && isset( $recur['BYMONTHDAY'] )) - $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY'] - , $wdate['day'] - , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] ); -// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
\n";//test### - if( $updateOK && isset( $recur['BYDAY'] )) { - $updateOK = FALSE; - $m = $wdate['month']; - $d = $wdate['day']; - if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no - $daynoexists = $daynosw = $daynamesw = FALSE; - if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] ) - $daynamesw = TRUE; - if( isset( $recur['BYDAY'][0] )) { - $daynoexists = TRUE; - if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] )) - $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] - , $daycnts[$m][$d]['monthdayno_up'] - , $daycnts[$m][$d]['monthdayno_down'] ); - elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) - $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] - , $daycnts[$m][$d]['yeardayno_up'] - , $daycnts[$m][$d]['yeardayno_down'] ); - } - if(( $daynoexists && $daynosw && $daynamesw ) || - ( !$daynoexists && !$daynosw && $daynamesw )) { - $updateOK = TRUE; -// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK
\n"; // test ### - } -//echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK
\n"; // test ### - } - else { - foreach( $recur['BYDAY'] as $bydayvalue ) { - $daynoexists = $daynosw = $daynamesw = FALSE; - if( isset( $bydayvalue['DAY'] ) && - ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] )) - $daynamesw = TRUE; - if( isset( $bydayvalue[0] )) { - $daynoexists = TRUE; - if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || - isset( $recur['BYMONTH'] )) - $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] - , $daycnts[$m][$d]['monthdayno_up'] - , $daycnts[$m][$d]['monthdayno_down'] ); - elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) - $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] - , $daycnts[$m][$d]['yeardayno_up'] - , $daycnts[$m][$d]['yeardayno_down'] ); - } -// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
\n"; // test ### - if(( $daynoexists && $daynosw && $daynamesw ) || - ( !$daynoexists && !$daynosw && $daynamesw )) { - $updateOK = TRUE; - break; - } - } - } - } -// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
\n"; // test ### - /* check BYSETPOS */ - if( $updateOK ) { - if( isset( $recur['BYSETPOS'] ) && - ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) { - if( isset( $recur['WEEKLY'] )) { - if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] ) - $bysetposw1[] = $wdatets; - else - $bysetposw2[] = $wdatets; - } - else { - if(( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && - ( $bysetposYold == $wdate['year'] )) || - ( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] ) && - (( $bysetposYold == $wdate['year'] ) && - ( $bysetposMold == $wdate['month'] ))) || - ( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) && - (( $bysetposYold == $wdate['year'] ) && - ( $bysetposMold == $wdate['month']) && - ( $bysetposDold == $wdate['day'] )))) { -// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."
\n";//test - $bysetposymd1[] = $wdatets; - } - else { -// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."
\n";//test - $bysetposymd2[] = $wdatets; - } - } - } - else { - /* update result array if BYSETPOS is set */ - $countcnt++; - if( $startdatets <= $wdatets ) { // only output within period - $result[$wdatets] = TRUE; -// echo "recur ".date('Y-m-d H:i:s',$wdatets)."
\n";//test - } -// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."
\n";//test - $updateOK = FALSE; - } - } - /* step up date */ - iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); - /* check if BYSETPOS is set for updating result array */ - if( $updateOK && isset( $recur['BYSETPOS'] )) { - $bysetpos = FALSE; - if( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && - ( $bysetposYold != $wdate['year'] )) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - } - elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] && - (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - } - elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY' == $recur['FREQ'] )) { - $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year'])); - if( $bysetposWold != $weekno ) { - $bysetposWold = $weekno; - $bysetpos = TRUE; - } - } - elseif( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) && - (( $bysetposYold != $wdate['year'] ) || - ( $bysetposMold != $wdate['month'] ) || - ( $bysetposDold != $wdate['day'] ))) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - $bysetposDold = $wdate['day']; - } - if( $bysetpos ) { - if( isset( $recur['BYWEEKNO'] )) { - $bysetposarr1 = & $bysetposw1; - $bysetposarr2 = & $bysetposw2; - } - else { - $bysetposarr1 = & $bysetposymd1; - $bysetposarr2 = & $bysetposymd2; - } -// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ### - foreach( $recur['BYSETPOS'] as $ix ) { - if( 0 > $ix ) // both positive and negative BYSETPOS allowed - $ix = ( count( $bysetposarr1 ) + $ix + 1); - $ix--; - if( isset( $bysetposarr1[$ix] )) { - if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period -// $testdate = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 ); // test ### -// $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ### -// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)"; // test ### - $result[$bysetposarr1[$ix]] = TRUE; -// echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ### - } - $countcnt++; - } - if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) - break; - } -// echo "
\n"; // test ### - $bysetposarr1 = $bysetposarr2; - $bysetposarr2 = array(); - } - } - } - } - public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) { - if( is_array( $BYvalue ) && - ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue ))) - return TRUE; - elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue )) - return TRUE; - else - return FALSE; - } - public static function _recurIntervalIx( $freq, $date, $wkst ) { - /* create interval index */ - switch( $freq ) { - case 'YEARLY': - $intervalix = $date['year']; - break; - case 'MONTHLY': - $intervalix = $date['year'].'-'.$date['month']; - break; - case 'WEEKLY': - $wdatets = iCalUtilityFunctions::_date2timestamp( $date ); - $intervalix = (int) date( 'W', ( $wdatets + $wkst )); - break; - case 'DAILY': - default: - $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day']; - break; - } - return $intervalix; - } -/** - * convert input format for exrule and rrule to internal format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.15 - 2012-01-31 - * @param array $rexrule - * @return array - */ - public static function _setRexrule( $rexrule ) { - $input = array(); - if( empty( $rexrule )) - return $input; - foreach( $rexrule as $rexrulelabel => $rexrulevalue ) { - $rexrulelabel = strtoupper( $rexrulelabel ); - if( 'UNTIL' != $rexrulelabel ) - $input[$rexrulelabel] = $rexrulevalue; - else { - iCalUtilityFunctions::_strDate2arr( $rexrulevalue ); - if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time - $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 6 ); - elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or date-time - $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 6 : 3; - $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_array( $rexrulevalue, $parno ); - } - elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual datetime/date 2006-08-03 10:12:18 - $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_string( $rexrulevalue ); - unset( $input['$rexrulelabel']['unparsedtext'] ); - } - if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] )) - $input[$rexrulelabel]['tz'] = 'Z'; - } - } - /* set recurrence rule specification in rfc2445 order */ - $input2 = array(); - if( isset( $input['FREQ'] )) - $input2['FREQ'] = $input['FREQ']; - if( isset( $input['UNTIL'] )) - $input2['UNTIL'] = $input['UNTIL']; - elseif( isset( $input['COUNT'] )) - $input2['COUNT'] = $input['COUNT']; - if( isset( $input['INTERVAL'] )) - $input2['INTERVAL'] = $input['INTERVAL']; - if( isset( $input['BYSECOND'] )) - $input2['BYSECOND'] = $input['BYSECOND']; - if( isset( $input['BYMINUTE'] )) - $input2['BYMINUTE'] = $input['BYMINUTE']; - if( isset( $input['BYHOUR'] )) - $input2['BYHOUR'] = $input['BYHOUR']; - if( isset( $input['BYDAY'] )) { - if( !is_array( $input['BYDAY'] )) // ensure upper case.. . - $input2['BYDAY'] = strtoupper( $input['BYDAY'] ); - else { - foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) { - if( 'DAY' == strtoupper( $BYDAYx )) - $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv ); - elseif( !is_array( $BYDAYv )) { - $input2['BYDAY'][$BYDAYx] = $BYDAYv; - } - else { - foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) { - if( 'DAY' == strtoupper( $BYDAYx2 )) - $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 ); - else - $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2; - } - } - } - } - } - if( isset( $input['BYMONTHDAY'] )) - $input2['BYMONTHDAY'] = $input['BYMONTHDAY']; - if( isset( $input['BYYEARDAY'] )) - $input2['BYYEARDAY'] = $input['BYYEARDAY']; - if( isset( $input['BYWEEKNO'] )) - $input2['BYWEEKNO'] = $input['BYWEEKNO']; - if( isset( $input['BYMONTH'] )) - $input2['BYMONTH'] = $input['BYMONTH']; - if( isset( $input['BYSETPOS'] )) - $input2['BYSETPOS'] = $input['BYSETPOS']; - if( isset( $input['WKST'] )) - $input2['WKST'] = $input['WKST']; - return $input2; - } -/** - * convert format for input date to internal date with parameters - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-03-18 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param string $tz optional - * @param array $params optional - * @param string $caller optional - * @param string $objName optional - * @param string $tzid optional - * @return array - */ - public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) { - $input = $parno = null; - $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE; - iCalUtilityFunctions::_strDate2arr( $year ); - if( iCalUtilityFunctions::_isArrayDate( $year )) { - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - unset( $year['tz'] ); - } - $hitval = ( isset( $year['tz'] ) || isset( $year[6] )) ? 7 : 6; - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval ); - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $year ), $parno ); - $input['value'] = iCalUtilityFunctions::_date_time_array( $year, $parno ); - } - elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - unset( $year['tz'] ); - } - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); - $hitval = ( isset( $year['tz'] )) ? 7 : 6; - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno ); - $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, $parno ); - } - elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - $parno = 6; - } - elseif( $tzid && iCalUtilityFunctions::_isOffset( substr( $year, -7 ))) { - if(( in_array( substr( $year, -5, 1 ), array( '+', '-' ))) && - ( '0000' < substr( $year, -4 )) && ( '9999' >= substr( $year, -4 ))) - $year = substr( $year, 0, ( strlen( $year ) - 5 )); - elseif(( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && - ( '000000' < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) - $year = substr( $year, 0, ( strlen( $year ) - 7 )); - $parno = 6; - } - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno ); - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno ); - $input['value'] = iCalUtilityFunctions::_date_time_string( $year, $parno ); - unset( $input['value']['unparsedtext'] ); - } - else { - if( is_array( $params )) { - if( $localtime ) unset ( $params['VALUE'], $params['TZID'] ); - $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); - } - elseif( is_array( $tz )) { - $input['params'] = iCalUtilityFunctions::_setParams( $tz, array( 'VALUE' => 'DATE-TIME' )); - $tz = FALSE; - } - elseif( is_array( $hour )) { - $input['params'] = iCalUtilityFunctions::_setParams( $hour, array( 'VALUE' => 'DATE-TIME' )); - $hour = $min = $sec = $tz = FALSE; - } - if( isset( $input['params']['TZID'] )) { - $tz = null; - $input['params']['VALUE'] = 'DATE-TIME'; - } - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); - $hitval = ( !empty( $tz )) ? 7 : 6; - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno ); - $input['value'] = array( 'year' => $year, 'month' => $month, 'day' => $day ); - if( 3 != $parno ) { - $input['value']['hour'] = ( $hour ) ? $hour : '0'; - $input['value']['min'] = ( $min ) ? $min : '0'; - $input['value']['sec'] = ( $sec ) ? $sec : '0'; - if( !empty( $tz )) - $input['value']['tz'] = $tz; - } - } - if( 3 == $parno ) { - $input['params']['VALUE'] = 'DATE'; - unset( $input['value']['tz'] ); - unset( $input['params']['TZID'] ); - } - elseif( isset( $input['params']['TZID'] )) - unset( $input['value']['tz'] ); - if( $localtime ) - unset( $input['value']['tz'], $input['params']['TZID'] ); - elseif(( !isset( $input['params']['VALUE'] ) || ( $input['params']['VALUE'] != 'DATE' )) && !isset( $input['params']['TZID'] ) && $tzid ) - $input['params']['TZID'] = $tzid; - if( isset( $input['value']['tz'] )) - $input['value']['tz'] = (string) $input['value']['tz']; - if( !empty( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && // real time zone in tz to TZID - ( !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))) { - $input['params']['TZID'] = $input['value']['tz']; - unset( $input['value']['tz'] ); - } - if( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) { - if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { // utc offset in TZID to tz - $input['value']['tz'] = $input['params']['TZID']; - unset( $input['params']['TZID'] ); - } - elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z - $input['value']['tz'] = 'Z'; - unset( $input['params']['TZID'] ); - } - } - return $input; - } -/** - * convert format for input date (UTC) to internal date with parameters - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-01-19 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return array - */ - public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - $input = null; - iCalUtilityFunctions::_strDate2arr( $year ); - if( iCalUtilityFunctions::_isArrayDate( $year )) { - $input['value'] = iCalUtilityFunctions::_date_time_array( $year, 7 ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); - } - elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { - $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, 7 ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); - } - elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 - $input['value'] = iCalUtilityFunctions::_date_time_string( $year, 7 ); - unset( $input['value']['unparsedtext'] ); - $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); - } - else { - $input['value'] = array( 'year' => $year - , 'month' => $month - , 'day' => $day - , 'hour' => $hour - , 'min' => $min - , 'sec' => $sec ); - $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); - } - $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default - if( !isset( $input['value']['hour'] )) - $input['value']['hour'] = 0; - if( !isset( $input['value']['min'] )) - $input['value']['min'] = 0; - if( !isset( $input['value']['sec'] )) - $input['value']['sec'] = 0; - if( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) { - if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) { // utc offset in TZID to tz - $input['value']['tz'] = $input['params']['TZID']; - unset( $input['params']['TZID'] ); - } - elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z - $input['value']['tz'] = 'Z'; - unset( $input['params']['TZID'] ); - } - } - if( !isset( $input['value']['tz'] ) || !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) - $input['value']['tz'] = 'Z'; - return $input; - } -/** - * check index and set (an indexed) content in multiple value array - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.12 - 2011-01-03 - * @param array $valArr - * @param mixed $value - * @param array $params - * @param array $defaults - * @param int $index - * @return void - */ - public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) { - if( !is_array( $valArr )) $valArr = array(); - if( $index ) - $index = $index - 1; - elseif( 0 < count( $valArr )) { - $keys = array_keys( $valArr ); - $index = end( $keys ) + 1; - } - else - $index = 0; - $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults )); - ksort( $valArr ); - } -/** - * set input (formatted) parameters- component property attributes - * - * default parameters can be set, if missing - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 1.x.x - 2007-05-01 - * @param array $params - * @param array $defaults - * @return array - */ - public static function _setParams( $params, $defaults=FALSE ) { - if( !is_array( $params)) - $params = array(); - $input = array(); - foreach( $params as $paramKey => $paramValue ) { - if( is_array( $paramValue )) { - foreach( $paramValue as $pkey => $pValue ) { - if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 ))) - $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 )); - } - } - elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 ))) - $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 )); - if( 'VALUE' == strtoupper( $paramKey )) - $input['VALUE'] = strtoupper( $paramValue ); - else - $input[strtoupper( $paramKey )] = $paramValue; - } - if( is_array( $defaults )) { - foreach( $defaults as $paramKey => $paramValue ) { - if( !isset( $input[$paramKey] )) - $input[$paramKey] = $paramValue; - } - } - return (0 < count( $input )) ? $input : null; - } -/** - * step date, return updated date, array and timpstamp - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-10-18 - * @param array $date, date to step - * @param int $timestamp - * @param array $step, default array( 'day' => 1 ) - * @return void - */ - public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) { - foreach( $step as $stepix => $stepvalue ) - $date[$stepix] += $stepvalue; - $timestamp = iCalUtilityFunctions::_date2timestamp( $date ); - $date = iCalUtilityFunctions::_timestamp2date( $timestamp, 6 ); - foreach( $date as $k => $v ) { - if( ctype_digit( $v )) - $date[$k] = (int) $v; - } - } -/** - * convert a date from specific string to array format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.8 - 2012-01-27 - * @param mixed $input - * @return bool, TRUE on success - */ - public static function _strDate2arr( & $input ) { - if( is_array( $input )) - return FALSE; - if( 5 > strlen( (string) $input )) - return FALSE; - $work = $input; - if( 2 == substr_count( $work, '-' )) - $work = str_replace( '-', '', $work ); - if( 2 == substr_count( $work, '/' )) - $work = str_replace( '/', '', $work ); - if( !ctype_digit( substr( $work, 0, 8 ))) - return FALSE; - if( !checkdate( (int) substr( $work, 4, 2 ), (int) substr( $work, 6, 2 ), (int) substr( $work, 0, 4 ))) - return FALSE; - $temp = array( 'year' => substr( $work, 0, 4 ) - , 'month' => substr( $work, 4, 2 ) - , 'day' => substr( $work, 6, 2 )); - if( 8 == strlen( $work )) { - $input = $temp; - return TRUE; - } - if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 ))) - $work = substr( $work, 9 ); - elseif( ctype_digit( substr( $work, 8, 1 ))) - $work = substr( $work, 8 ); - else - return FALSE; - if( 2 == substr_count( $work, ':' )) - $work = str_replace( ':', '', $work ); - if( !ctype_digit( substr( $work, 0, 4 ))) - return FALSE; - $temp['hour'] = substr( $work, 0, 2 ); - $temp['min'] = substr( $work, 2, 2 ); - if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) || - (( 0 > $temp['min'] ) || ( $temp['min'] > 59 ))) - return FALSE; - if( ctype_digit( substr( $work, 4, 2 ))) { - $temp['sec'] = substr( $work, 4, 2 ); - if(( 0 > $temp['sec'] ) || ( $temp['sec'] > 59 )) - return FALSE; - $len = 6; - } - else { - $temp['sec'] = 0; - $len = 4; - } - if( $len < strlen( $work)) - $temp['tz'] = trim( substr( $work, 6 )); - $input = $temp; - return TRUE; - } -/** - * convert timestamp to date array - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.4.16 - 2008-11-01 - * @param mixed $timestamp - * @param int $parno - * @return array - */ - public static function _timestamp2date( $timestamp, $parno=6 ) { - if( is_array( $timestamp )) { - if(( 7 == $parno ) && !empty( $timestamp['tz'] )) - $tz = $timestamp['tz']; - $timestamp = $timestamp['timestamp']; - } - $output = array( 'year' => date( 'Y', $timestamp ) - , 'month' => date( 'm', $timestamp ) - , 'day' => date( 'd', $timestamp )); - if( 3 != $parno ) { - $output['hour'] = date( 'H', $timestamp ); - $output['min'] = date( 'i', $timestamp ); - $output['sec'] = date( 's', $timestamp ); - if( isset( $tz )) - $output['tz'] = $tz; - } - return $output; - } -/** - * convert timestamp to duration in array format - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.6.23 - 2010-10-23 - * @param int $timestamp - * @return array, duration format - */ - public static function _timestamp2duration( $timestamp ) { - $dur = array(); - $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 )); - $timestamp = $timestamp % ( 7 * 24 * 60 * 60 ); - $dur['day'] = (int) floor( $timestamp / ( 24 * 60 * 60 )); - $timestamp = $timestamp % ( 24 * 60 * 60 ); - $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 )); - $timestamp = $timestamp % ( 60 * 60 ); - $dur['min'] = (int) floor( $timestamp / ( 60 )); - $dur['sec'] = (int) $timestamp % ( 60 ); - return $dur; - } -/** - * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0) - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.14 - 2012-01-24 - * @param mixed $date, date to alter - * @param string $tzFrom, PHP valid old timezone - * @param string $tzTo, PHP valid new timezone, default 'UTC' - * @param string $format, date output format, default 'Ymd\THis' - * @return bool - */ - public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) { - if( !class_exists( 'DateTime' ) || !class_exists( 'DateTimeZone' )) - return FALSE; - if( is_array( $date ) && isset( $date['timestamp'] )) - $timestamp = $date['timestamp']; - elseif( iCalUtilityFunctions::_isArrayDate( $date )) { - if(isset( $date['tz'] )) - unset( $date['tz'] ); - $date = iCalUtilityFunctions::_format_date_time( iCalUtilityFunctions::_date_time_array( $date )); - if( 'Z' == substr( $date, -1 )) - $date = substr( $date, 0, ( strlen( $date ) - 2 )); - if( FALSE === ( $timestamp = strtotime( $date ))) - return FALSE; - } - elseif( FALSE === ( $timestamp = @strtotime( $date ))) - return FALSE; - try { - $d = new DateTime( date( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tzFrom )); - $d->setTimezone( new DateTimeZone( $tzTo )); - } - catch (Exception $e) { - return FALSE; - } - $date = $d->format( $format ); - return TRUE; - } -/** - * convert (numeric) local time offset, ("+" / "-")HHmm[ss], to seconds correcting localtime to GMT - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.4 - 2012-01-11 - * @param string $offset - * @return integer - */ - public static function _tz2offset( $tz ) { - $tz = trim( (string) $tz ); - $offset = 0; - if((( 5 != strlen( $tz )) && ( 7 != strlen( $tz ))) || - (( '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) || - (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) || - (( 7 == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 )))) - return $offset; - $hours2sec = (int) substr( $tz, 1, 2 ) * 3600; - $min2sec = (int) substr( $tz, 3, 2 ) * 60; - $sec = ( 7 == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00'; - $offset = $hours2sec + $min2sec + $sec; - $offset = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset; - return $offset; - } -} -/*********************************************************************************/ -/* iCalcreator XML (rfc6321) helper functions */ -/*********************************************************************************/ -/** - * format iCal XML output, rfc6321, using PHP SimpleXMLElement - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.1 - 2012-02-22 - * @param object $calendar, iCalcreator vcalendar instance reference - * @return string - */ -function iCal2XML( & $calendar ) { - /** fix an SimpleXMLElement instance and create root element */ - $xmlstr = ''; - $xmlstr .= ''; - $xmlstr .= ''; - $xml = new SimpleXMLElement( $xmlstr ); - $vcalendar = $xml->addChild( 'vcalendar' ); - /** fix calendar properties */ - $properties = $vcalendar->addChild( 'properties' ); - $calProps = array( 'prodid', 'version', 'calscale', 'method' ); - foreach( $calProps as $calProp ) { - if( FALSE !== ( $content = $calendar->getProperty( $calProp ))) - _addXMLchild( $properties, $calProp, 'text', $content ); - } - while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE ))) - _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); - $langCal = $calendar->getConfig( 'language' ); - /** prepare to fix components with properties */ - $components = $vcalendar->addChild( 'components' ); - $comps = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' ); - $eventProps = array( 'dtstamp', 'dtstart', 'uid', - 'class', 'created', 'description', 'geo', 'last-modified', 'location', 'organizer', 'priority', - 'sequence', 'status', 'summary', 'transp', 'url', 'recurrence-id', 'rrule', 'dtend', 'duration', - 'attach', 'attendee', 'categories', 'comment', 'contact', 'exdate', 'request-status', 'related-to', 'resources', 'rdate', - 'x-prop' ); - $todoProps = array( 'dtstamp', 'uid', - 'class', 'completed', 'created', 'description', 'geo', 'last-modified', 'location', 'organizer', 'percent-complete', 'priority', - 'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule', 'dtstart', 'due', 'duration', - 'attach', 'attendee', 'categories', 'comment', 'contact', 'exdate', 'request-status', 'related-to', 'resources', 'rdate', - 'x-prop' ); - $journalProps = array( 'dtstamp', 'uid', - 'class', 'created', 'dtstart', 'last-modified', 'organizer', 'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule', - 'attach', 'attendee', 'categories', 'comment', 'contact', - 'description', - 'exdate', 'related-to', 'rdate', 'request-status', - 'x-prop' ); - $freebusyProps = array( 'dtstamp', 'uid', - 'contact', 'dtstart', 'dtend', 'duration', 'organizer', 'url', - 'attendee', 'comment', 'freebusy', 'request-status', - 'x-prop' ); - $timezoneProps = array( 'tzid', - 'last-modified', 'tzurl', - 'x-prop' ); - $alarmProps = array( 'action', 'description', 'trigger', 'summary', - 'attendee', - 'duration', 'repeat', 'attach', - 'x-prop' ); - $stddghtProps = array( 'dtstart', 'tzoffsetto', 'tzoffsetfrom', - 'rrule', - 'comment', 'rdate', 'tzname', - 'x-prop' ); - foreach( $comps as $compName ) { - switch( $compName ) { - case 'vevent': - $props = & $eventProps; - $subComps = array( 'valarm' ); - $subCompProps = & $alarmProps; - break; - case 'vtodo': - $props = & $todoProps; - $subComps = array( 'valarm' ); - $subCompProps = & $alarmProps; - break; - case 'vjournal': - $props = & $journalProps; - $subComps = array(); - $subCompProps = array(); - break; - case 'vfreebusy': - $props = & $freebusyProps; - $subComps = array(); - $subCompProps = array(); - break; - case 'vtimezone': - $props = & $timezoneProps; - $subComps = array( 'standard', 'daylight' ); - $subCompProps = & $stddghtProps; - break; - } // end switch( $compName ) - /** fix component properties */ - while( FALSE !== ( $component = $calendar->getComponent( $compName ))) { - $child = $components->addChild( $compName ); - $properties = $child->addChild( 'properties' ); - $langComp = $component->getConfig( 'language' ); - foreach( $props as $prop ) { - switch( $prop ) { - case 'attach': // may occur multiple times, below - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri'; - unset( $content['params']['VALUE'] ); - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'attendee': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); - } - break; - case 'exdate': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time'; - unset( $content['params']['VALUE'] ); - foreach( $content['value'] as & $exDate ) { - if( ( isset( $exDate['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $exDate['tz'] ) && - ( 'Z' != $exDate['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $exDate['tz'] ) ? $exDate['tz'] : $content['params']['TZID']; - $date = mktime( (int) $exDate['hour'], - (int) $exDate['min'], - (int) ($exDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $exDate['month'], - (int) $exDate['day'], - (int) $exDate['year'] ); - unset( $exDate['tz'] ); - $exDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $exDate['unparsedtext'] ); - } - } - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'freebusy': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] ); - break; - case 'request-status': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if( !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] ); - } - break; - case 'rdate': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - $type = 'date-time'; - if( isset( $content['params']['VALUE'] )) { - if( 'DATE' == $content['params']['VALUE'] ) - $type = 'date'; - elseif( 'PERIOD' == $content['params']['VALUE'] ) - $type = 'period'; - } - if( 'period' == $type ) { - foreach( $content['value'] as & $rDates ) { - if( ( isset( $rDates[0]['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) && - ( 'Z' != $rDates[0]['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDates[0]['hour'], - (int) $rDates[0]['min'], - (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDates[0]['month'], - (int) $rDates[0]['day'], - (int) $rDates[0]['year'] ); - unset( $rDates[0]['tz'] ); - $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDates[0]['unparsedtext'] ); - } - if( isset( $rDates[1]['year'] )) { - if( ( isset( $rDates[1]['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) && - ( 'Z' != $rDates[1]['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDates[1]['hour'], - (int) $rDates[1]['min'], - (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDates[1]['month'], - (int) $rDates[1]['day'], - (int) $rDates[1]['year'] ); - unset( $rDates[1]['tz'] ); - $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDates[1]['unparsedtext'] ); - } - } - } - } - elseif( 'date-time' == $type ) { - foreach( $content['value'] as & $rDate ) { - if( ( isset( $rDate['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDate['tz'] ) && - ( 'Z' != $rDate['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDate['hour'], - (int) $rDate['min'], - (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDate['month'], - (int) $rDate['day'], - (int) $rDate['year'] ); - unset( $rDate['tz'] ); - $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDate['unparsedtext'] ); - } - } - } - unset( $content['params']['VALUE'] ); - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'categories': - case 'comment': - case 'contact': - case 'description': - case 'related-to': - case 'resources': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); - } - break; - case 'x-prop': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); - break; - case 'created': // single occurence below, if set - case 'completed': - case 'dtstamp': - case 'last-modified': - $utcDate = TRUE; - case 'dtstart': - case 'dtend': - case 'due': - case 'recurrence-id': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) { - $type = 'date'; - unset( $content['value']['hour'], $content['value']['min'], $content['value']['sec'] ); - } - else { - $type = 'date-time'; - if( isset( $utcDate ) && !isset( $content['value']['tz'] )) - $content['value']['tz'] = 'Z'; - if( ( isset( $content['value']['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $content['value']['tz'] ) && - ( 'Z' != $content['value']['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $content['value']['tz'] ) ? $content['value']['tz'] : $content['params']['TZID']; - $date = mktime( (int) $content['value']['hour'], - (int) $content['value']['min'], - (int) ($content['value']['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $content['value']['month'], - (int) $content['value']['day'], - (int) $content['value']['year'] ); - unset( $content['value']['tz'], $content['params']['TZID'] ); - $content['value'] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $content['value']['unparsedtext'] ); - } - elseif( isset( $content['value']['tz'] ) && !empty( $content['value']['tz'] ) && - ( 'Z' != $content['value']['tz'] ) && !isset( $content['params']['TZID'] )) { - $content['params']['TZID'] = $content['value']['tz']; - unset( $content['value']['tz'] ); - } - } - unset( $content['params']['VALUE'] ); - if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] )) - unset( $content['params']['TZID'] ); - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - unset( $utcDate ); - break; - case 'duration': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] ); - break; - case 'rrule': - while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] ); - break; - case 'class': - case 'location': - case 'status': - case 'summary': - case 'transp': - case 'tzid': - case 'uid': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); - } - break; - case 'geo': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] ); - break; - case 'organizer': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) { - if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); - } - break; - case 'percent-complete': - case 'priority': - case 'sequence': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] ); - break; - case 'tzurl': - case 'url': - if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] ); - break; - } // end switch( $prop ) - } // end foreach( $props as $prop ) - /** fix subComponent properties, if any */ - foreach( $subComps as $subCompName ) { - while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) { - $child2 = $child->addChild( $subCompName ); - $properties = $child2->addChild( 'properties' ); - $langComp = $subcomp->getConfig( 'language' ); - foreach( $subCompProps as $prop ) { - switch( $prop ) { - case 'attach': // may occur multiple times, below - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri'; - unset( $content['params']['VALUE'] ); - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'attendee': - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] ); - } - break; - case 'comment': - case 'tzname': - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - if( !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); - } - break; - case 'rdate': - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - $type = 'date-time'; - if( isset( $content['params']['VALUE'] )) { - if( 'DATE' == $content['params']['VALUE'] ) - $type = 'date'; - elseif( 'PERIOD' == $content['params']['VALUE'] ) - $type = 'period'; - } - if( 'period' == $type ) { - foreach( $content['value'] as & $rDates ) { - if( ( isset( $rDates[0]['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) && - ( 'Z' != $rDates[0]['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDates[0]['hour'], - (int) $rDates[0]['min'], - (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDates[0]['month'], - (int) $rDates[0]['day'], - (int) $rDates[0]['year'] ); - unset( $rDates[0]['tz'] ); - $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDates[0]['unparsedtext'] ); - } - if( isset( $rDates[1]['year'] )) { - if( ( isset( $rDates[1]['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) && - ( 'Z' != $rDates[1]['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDates[1]['hour'], - (int) $rDates[1]['min'], - (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDates[1]['month'], - (int) $rDates[1]['day'], - (int) $rDates[1]['year'] ); - unset( $rDates[1]['tz'] ); - $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDates[1]['unparsedtext'] ); - } - } - } - } - elseif( 'date-time' == $type ) { - foreach( $content['value'] as & $rDate ) { - if( ( isset( $rDate['tz'] ) && // fix UTC-date if offset set - iCalUtilityFunctions::_isOffset( $rDate['tz'] ) && - ( 'Z' != $rDate['tz'] )) - || ( isset( $content['params']['TZID'] ) && - iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) && - ( 'Z' != $content['params']['TZID'] ))) { - $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID']; - $date = mktime( (int) $rDate['hour'], - (int) $rDate['min'], - (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )), - (int) $rDate['month'], - (int) $rDate['day'], - (int) $rDate['year'] ); - unset( $rDate['tz'] ); - $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 ); - unset( $rDate['unparsedtext'] ); - } - } - } - unset( $content['params']['VALUE'] ); - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'x-prop': - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] ); - break; - case 'action': // single occurence below, if set - case 'description': - case 'summary': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) { - if( $langComp ) - $content['params']['LANGUAGE'] = $langComp; - elseif( $langCal ) - $content['params']['LANGUAGE'] = $langCal; - } - _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] ); - } - break; - case 'dtstart': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time - _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] ); - } - break; - case 'duration': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] ); - break; - case 'repeat': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] ); - break; - case 'trigger': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) { - if( isset( $content['value']['year'] ) && - isset( $content['value']['month'] ) && - isset( $content['value']['day'] )) - $type = 'date-time'; - else - $type = 'duration'; - _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] ); - } - break; - case 'tzoffsetto': - case 'tzoffsetfrom': - if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] ); - break; - case 'rrule': - while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) - _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] ); - break; - } // switch( $prop ) - } // end foreach( $subCompProps as $prop ) - } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName ))) - } // end foreach( $subCombs as $subCompName ) - } // end while( FALSE !== ( $component = $calendar->getComponent( $compName ))) - } // end foreach( $comps as $compName) - return $xml->asXML(); -} -/** - * Add children to a SimpleXMLelement - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.1 - 2012-01-16 - * @param object $parent, reference to a SimpleXMLelement node - * @param string $name, new element node name - * @param string $type, content type, subelement(-s) name - * @param string $content, new subelement content - * @param array $params, new element 'attributes' - * @return void - */ -function _addXMLchild( & $parent, $name, $type, $content, $params=array()) { - /** create new child node */ - $child = $parent->addChild( strtolower( $name )); - /** fix attributes */ - if( is_array( $content ) && isset( $content['fbtype'] )) { - $params['FBTYPE'] = $content['fbtype']; - unset( $content['fbtype'] ); - } - if( isset( $params['VALUE'] )) - unset( $params['VALUE'] ); - if(( 'trigger' == $name ) && ( 'duration' == $type ) && ( TRUE !== $content['relatedStart'] )) - $params['RELATED'] = 'END'; - if( !empty( $params )) { - $parameters = $child->addChild( 'parameters' ); - foreach( $params as $param => $parVal ) { - $param = strtolower( $param ); - if( 'x-' == substr( $param, 0, 2 )) { - $p1 = $parameters->addChild( $param ); - $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal )); - } - else { - $p1 = $parameters->addChild( $param ); - switch( $param ) { - case 'altrep': - case 'dir': $ptype = 'uri'; break; - case 'delegated-from': - case 'delegated-to': - case 'member': - case 'sent-by': $ptype = 'cal-address'; break; - case 'rsvp': $ptype = 'boolean'; break ; - default: $ptype = 'text'; break; - } - if( is_array( $parVal )) { - foreach( $parVal as $pV ) - $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV )); - } - else - $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal )); - } - } - } - if( empty( $content ) && ( '0' != $content )) - return; - /** store content */ - switch( $type ) { - case 'binary': - $v = $child->addChild( $type, $content ); - break; - case 'boolean': - break; - case 'cal-address': - $v = $child->addChild( $type, $content ); - break; - case 'date': - if( array_key_exists( 'year', $content )) - $content = array( $content ); - foreach( $content as $date ) { - $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] ); - $v = $child->addChild( $type, $str ); - } - break; - case 'date-time': - if( array_key_exists( 'year', $content )) - $content = array( $content ); - foreach( $content as $dt ) { - if( !isset( $dt['hour'] )) $dt['hour'] = 0; - if( !isset( $dt['min'] )) $dt['min'] = 0; - if( !isset( $dt['sec'] )) $dt['sec'] = 0; - $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] ); - if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] )) - $str .= 'Z'; - $v = $child->addChild( $type, $str ); - } - break; - case 'duration': - $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : ''; - $v = $child->addChild( $type, $output.iCalUtilityFunctions::_format_duration( $content ) ); - break; - case 'geo': - $v1 = $child->addChild( 'latitude', number_format( (float) $content['latitude'], 6, '.', '' )); - $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' )); - break; - case 'integer': - $v = $child->addChild( $type, $content ); - break; - case 'period': - if( !is_array( $content )) - break; - foreach( $content as $period ) { - $v1 = $child->addChild( $type ); - $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] ); - if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] )) - $str .= 'Z'; - $v2 = $v1->addChild( 'start', $str ); - if( array_key_exists( 'year', $period[1] )) { - $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] ); - if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] )) - $str .= 'Z'; - $v2 = $v1->addChild( 'end', $str ); - } - else - $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_format_duration( $period[1] )); - } - break; - case 'recur': - foreach( $content as $rulelabel => $rulevalue ) { - $rulelabel = strtolower( $rulelabel ); - switch( $rulelabel ) { - case 'until': - if( isset( $rulevalue['hour'] )) - $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] ); - else - $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] ); - $v = $child->addChild( $rulelabel, $str ); - break; - case 'bysecond': - case 'byminute': - case 'byhour': - case 'bymonthday': - case 'byyearday': - case 'byweekno': - case 'bymonth': - case 'bysetpos': { - if( is_array( $rulevalue )) { - foreach( $rulevalue as $vix => $valuePart ) - $v = $child->addChild( $rulelabel, $valuePart ); - } - else - $v = $child->addChild( $rulelabel, $rulevalue ); - break; - } - case 'byday': { - if( isset( $rulevalue['DAY'] )) { - $str = ( isset( $rulevalue[0] )) ? $rulevalue[0] : ''; - $str .= $rulevalue['DAY']; - $p = $child->addChild( $rulelabel, $str ); - } - else { - foreach( $rulevalue as $valuePart ) { - if( isset( $valuePart['DAY'] )) { - $str = ( isset( $valuePart[0] )) ? $valuePart[0] : ''; - $str .= $valuePart['DAY']; - $p = $child->addChild( $rulelabel, $str ); - } - else - $p = $child->addChild( $rulelabel, $valuePart ); - } - } - break; - } - case 'freq': - case 'count': - case 'interval': - case 'wkst': - default: - $p = $child->addChild( $rulelabel, $rulevalue ); - break; - } // end switch( $rulelabel ) - } // end foreach( $content as $rulelabel => $rulevalue ) - break; - case 'rstatus': - $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', '')); - $v = $child->addChild( 'description', htmlspecialchars( $content['text'] )); - if( isset( $content['extdata'] )) - $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] )); - break; - case 'text': - if( !is_array( $content )) - $content = array( $content ); - foreach( $content as $part ) - $v = $child->addChild( $type, htmlspecialchars( $part )); - break; - case 'time': - break; - case 'uri': - $v = $child->addChild( $type, $content ); - break; - case 'utc-offset': - if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) { - $str = substr( $content, 0, 1 ); - $content = substr( $content, 1 ); - } - else - $str = '+'; - $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 ); - if( 4 < strlen( $content )) - $str .= ':'.substr( $content, 4 ); - $v = $child->addChild( $type, $str ); - break; - case 'unknown': - default: - if( is_array( $content )) - $content = implode( '', $content ); - $v = $child->addChild( 'unknown', htmlspecialchars( $content )); - break; - } -} -/** - * parse xml string into iCalcreator instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-31 - * @param string $xmlstr - * @param array $iCalcfg iCalcreator config array (opt) - * @return mixed iCalcreator instance or FALSE on error - */ -function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) { - libxml_use_internal_errors( TRUE ); - $xml = simplexml_load_string( $xmlstr ); - if( !$xml ) { - $str = ''; - $return = FALSE; - foreach( libxml_get_errors() as $error ) { - switch ( $error->level ) { - case LIBXML_ERR_FATAL: $str .= ' FATAL '; break; - case LIBXML_ERR_ERROR: $str .= ' ERROR '; break; - case LIBXML_ERR_WARNING: - default: $str .= ' WARNING '; break; - } - $str .= PHP_EOL.'Error when loading XML'; - if( !empty( $error->file )) - $str .= ', file:'.$error->file.', '; - $str .= ', line:'.$error->line; - $str .= ', ('.$error->code.') '.$error->message; - } - error_log( $str ); - if( LIBXML_ERR_WARNING != $error->level ) - return $return; - libxml_clear_errors(); - } - return xml2iCal( $xml, $iCalcfg ); -} -/** - * parse xml file into iCalcreator instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-20 - * @param string $xmlfile - * @param array$iCalcfg iCalcreator config array (opt) - * @return mixediCalcreator instance or FALSE on error - */ -function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) { - libxml_use_internal_errors( TRUE ); - $xml = simplexml_load_file( $xmlfile ); - if( !$xml ) { - $str = ''; - foreach( libxml_get_errors() as $error ) { - switch ( $error->level ) { - case LIBXML_ERR_FATAL: $str .= 'FATAL '; break; - case LIBXML_ERR_ERROR: $str .= 'ERROR '; break; - case LIBXML_ERR_WARNING: - default: $str .= 'WARNING '; break; - } - $str .= 'Failed loading XML'.PHP_EOL; - if( !empty( $error->file )) - $str .= ' file:'.$error->file.', '; - $str .= 'line:'.$error->line.PHP_EOL; - $str .= '('.$error->code.') '.$error->message.PHP_EOL; - } - error_log( $str ); - if( LIBXML_ERR_WARNING != $error->level ) - return FALSE; - libxml_clear_errors(); - } - return xml2iCal( $xml, $iCalcfg ); -} -/** - * parse SimpleXMLElement xCal into iCalcreator instance - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-27 - * @param object $xmlobj SimpleXMLElement - * @param array $iCalcfg iCalcreator config array (opt) - * @return mixed iCalcreator instance or FALSE on error - */ -function & XML2iCal( $xmlobj, $iCalcfg=array()) { - $iCal = new vcalendar( $iCalcfg ); - foreach( $xmlobj->children() as $icalendar ) { // vcalendar - foreach( $icalendar->children() as $calPart ) { // calendar properties and components - if( 'components' == $calPart->getName()) { - foreach( $calPart->children() as $component ) { // single components - if( 0 < $component->count()) - _getXMLComponents( $iCal, $component ); - } - } - elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) { - foreach( $calPart->children() as $calProp ) { // calendar properties - $propName = $calProp->getName(); - if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 ))) - continue; - $params = array(); - foreach( $calProp->children() as $calPropElem ) { // single calendar property - if( 'parameters' == $calPropElem->getName()) - $params = _getXMLParams( $calPropElem ); - else - $iCal->setProperty( $propName, reset( $calPropElem ), $params ); - } // end foreach( $calProp->children() as $calPropElem ) - } // end foreach( $calPart->properties->children() as $calProp ) - } // end if( 0 < $calPart->properties->count()) - } // end foreach( $icalendar->children() as $calPart ) - } // end foreach( $xmlobj->children() as $icalendar ) - return $iCal; -} -/** - * parse SimpleXMLElement xCal property parameters and return iCalcreator property parameter array - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-15 - * @param object $parameters SimpleXMLElement - * @return array iCalcreator property parameter array - */ -function _getXMLParams( & $parameters ) { - if( 1 > $parameters->count()) - return array(); - $params = array(); - foreach( $parameters->children() as $parameter ) { // single parameter key - $key = strtoupper( $parameter->getName()); - $value = array(); - foreach( $parameter->children() as $paramValue ) // skip parameter value type - $value[] = reset( $paramValue ); - if( 2 > count( $value )) - $params[$key] = html_entity_decode( reset( $value )); - else - $params[$key] = $value; - } - return $params; -} -/** - * parse SimpleXMLElement xCal components, create iCalcreator component and update - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-15 - * @param array $iCal iCalcreator calendar instance - * @param object $component SimpleXMLElement - * @return void - */ -function _getXMLComponents( & $iCal, & $component ) { - $compName = $component->getName(); - $comp = & $iCal->newComponent( $compName ); - $subComponents = array( 'valarm', 'standard', 'daylight' ); - foreach( $component->children() as $compPart ) { // properties and (opt) subComponents - if( 1 > $compPart->count()) - continue; - if( in_array( $compPart->getName(), $subComponents )) - _getXMLComponents( $comp, $compPart ); - elseif( 'properties' == $compPart->getName()) { - foreach( $compPart->children() as $property ) // properties as single property - _getXMLProperties( $comp, $property ); - } - } // end foreach( $component->children() as $compPart ) -} -/** - * parse SimpleXMLElement xCal property, create iCalcreator component property - * - * @author Kjell-Inge Gustafsson, kigkonsult - * @since 2.11.2 - 2012-01-27 - * @param array $iCal iCalcreator calendar instance - * @param object $component SimpleXMLElement - * @return void - */ -function _getXMLProperties( & $iCal, & $property ) { - $propName = $property->getName(); - $value = $params = array(); - $valueType = ''; - foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s) - $valueType = $propPart->getName(); - if( 'parameters' == $valueType) { - $params = _getXMLParams( $propPart ); - continue; - } - switch( $valueType ) { - case 'binary': - $value = reset( $propPart ); - break; - case 'boolean': - break; - case 'cal-address': - $value = reset( $propPart ); - break; - case 'date': - $params['VALUE'] = 'DATE'; - case 'date-time': - if(( 'exdate' == $propName ) || ( 'rdate' == $propName )) - $value[] = reset( $propPart ); - else - $value = reset( $propPart ); - break; - case 'duration': - $value = reset( $propPart ); - break; -// case 'geo': - case 'latitude': - case 'longitude': - $value[$valueType] = reset( $propPart ); - break; - case 'integer': - $value = reset( $propPart ); - break; - case 'period': - if( 'rdate' == $propName ) - $params['VALUE'] = 'PERIOD'; - $pData = array(); - foreach( $propPart->children() as $periodPart ) - $pData[] = reset( $periodPart ); - if( !empty( $pData )) - $value[] = $pData; - break; -// case 'rrule': - case 'freq': - case 'count': - case 'until': - case 'interval': - case 'wkst': - $value[$valueType] = reset( $propPart ); - break; - case 'bysecond': - case 'byminute': - case 'byhour': - case 'bymonthday': - case 'byyearday': - case 'byweekno': - case 'bymonth': - case 'bysetpos': - $value[$valueType][] = reset( $propPart ); - break; - case 'byday': - $byday = reset( $propPart ); - if( 2 == strlen( $byday )) - $value[$valueType][] = array( 'DAY' => $byday ); - else { - $day = substr( $byday, -2 ); - $key = substr( $byday, 0, ( strlen( $byday ) - 2 )); - $value[$valueType][] = array( $key, 'DAY' => $day ); - } - break; -// case 'rstatus': - case 'code': - $value[0] = reset( $propPart ); - break; - case 'description': - $value[1] = reset( $propPart ); - break; - case 'data': - $value[2] = reset( $propPart ); - break; - case 'text': - $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart )); - $value['text'][] = html_entity_decode( $text ); - break; - case 'time': - break; - case 'uri': - $value = reset( $propPart ); - break; - case 'utc-offset': - $value = str_replace( ':', '', reset( $propPart )); - break; - case 'unknown': - default: - $value = html_entity_decode( reset( $propPart )); - break; - } // end switch( $valueType ) - } // end foreach( $property->children() as $propPart ) - if( 'freebusy' == $propName ) { - $fbtype = $params['FBTYPE']; - unset( $params['FBTYPE'] ); - $iCal->setProperty( $propName, $fbtype, $value, $params ); - } - elseif( 'geo' == $propName ) - $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params ); - elseif( 'request-status' == $propName ) { - if( !isset( $value[2] )) - $value[2] = FALSE; - $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params ); - } - else { - if( isset( $value['text'] ) && is_array( $value['text'] )) { - if(( 'categories' == $propName ) || ( 'resources' == $propName )) - $value = $value['text']; - else - $value = reset( $value['text'] ); - } - $iCal->setProperty( $propName, $value, $params ); - } -} -/** - * Additional functions to use with vtimezone components - * For use with - * iCalcreator (kigkonsult.se/iCalcreator/index.php) - * copyright (c) 2011 Yitzchok Lavi - * icalcreator@onebigsystem.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -/** - * Additional functions to use with vtimezone components - * - * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')! - * - * @author Yitzchok Lavi - * adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult - * @version 1.0.2 - 2011-02-24 - * - */ -/** - * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the - * timezone, according to the VTIMEZONE information in the input array. - * - * $param array $timezonesarray, output from function getTimezonesAsDateArrays (below) - * $param string $tzid, time zone identifier - * $param mixed $timestamp, timestamp or a UTC datetime (in array format) - * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname' - * - */ -function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) { - if( is_array( $timestamp )) { -//$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); - $timestamp = gmmktime( - $timestamp['hour'], - $timestamp['min'], - $timestamp['sec'], - $timestamp['month'], - $timestamp['day'], - $timestamp['year'] - ) ; -//echo ' '."\n".' '.$timestamp.''.$disp.' '."\n".' '; // test ### - } - $tzoffset = array(); - // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates) - $tzoffset['offsetHis'] = '+0000'; - $tzoffset['offsetSec'] = 0; - $tzoffset['tzname'] = '?'; - if( !isset( $timezonesarray[$tzid] )) - return $tzoffset; - $tzdatearray = $timezonesarray[$tzid]; - if ( is_array($tzdatearray) ) { - sort($tzdatearray); // just in case - if ( $timestamp < $tzdatearray[0]['timestamp'] ) { - // our date is before the first change - $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ; - $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ; - $tzoffset['tzname'] = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case - } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) { - // our date is after the last change (we do this so our scan can stop at the last record but one) - $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ; - $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ; - $tzoffset['tzname'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ; - } else { - // our date somewhere in between - // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it - // we don't include the last date in our loop as there isn't one after it to check - for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) { - if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) { - $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ; - $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ; - $tzoffset['tzname'] = $tzdatearray[$i]['tzafter']['tzname'] ; - break; - } - } - } - } - return $tzoffset; -} -/** - * Returns an array containing all the timezone data in the vcalendar object - * - * @param object $vcalendar, iCalcreator calendar instance - * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname) - * based on the timezone data in the vcalendar object - * - */ -function getTimezonesAsDateArrays($vcalendar) { - $timezonedata = array(); - while( $vtz = $vcalendar->getComponent( 'vtimezone' )) { - $tzid = $vtz->getProperty('tzid'); - $alltzdates = array(); - while ( $vtzc = $vtz->getComponent( 'standard' )) { - $newtzdates = expandTimezoneDates($vtzc); - $alltzdates = array_merge($alltzdates, $newtzdates); - } - while ( $vtzc = $vtz->getComponent( 'daylight' )) { - $newtzdates = expandTimezoneDates($vtzc); - $alltzdates = array_merge($alltzdates, $newtzdates); - } - sort($alltzdates); - $timezonedata[$tzid] = $alltzdates; - } - return $timezonedata; -} -/** - * Returns an array containing time zone data from vtimezone standard/daylight instances - * - * @param object $vtzc, an iCalcreator calendar standard/daylight instance - * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname) - * - */ -function expandTimezoneDates($vtzc) { - $tzdates = array(); - // prepare time zone "description" to attach to each change - $tzbefore = array(); - $tzbefore['offsetHis'] = $vtzc->getProperty('tzoffsetfrom') ; - $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']); - if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 ))) - $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec']; - $tzafter = array(); - $tzafter['offsetHis'] = $vtzc->getProperty('tzoffsetto') ; - $tzafter['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']); - if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 ))) - $tzafter['offsetSec'] = '+'.$tzafter['offsetSec']; - if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname'))) - $tzafter['tzname'] = $tzafter['offsetHis']; - // find out where to start from - $dtstart = $vtzc->getProperty('dtstart'); - $dtstarttimestamp = mktime( - $dtstart['hour'], - $dtstart['min'], - $dtstart['sec'], - $dtstart['month'], - $dtstart['day'], - $dtstart['year'] - ) ; - if( !isset( $dtstart['unparsedtext'] )) // ?? - $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] ); - if ( $dtstarttimestamp == 0 ) { - // it seems that the dtstart string may not have parsed correctly - // let's set a timestamp starting from 1902, using the time part of the original string - // so that the time will change at the right time of day - // at worst we'll get midnight again - $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ; - $dtstarttimestamp = strtotime("19020101",0); - $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp); - } - // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp - $diff = -1 * $tzbefore['offsetSec']; - $dtstarttimestamp += $diff; - // add this (start) change to the array of changes - $tzdates[] = array( - 'timestamp' => $dtstarttimestamp, - 'tzbefore' => $tzbefore, - 'tzafter' => $tzafter - ); - $datearray = getdate($dtstarttimestamp); - // save original array to use time parts, because strtotime (used below) apparently loses the time - $changetime = $datearray ; - // generate dates according to an RRULE line - $rrule = $vtzc->getProperty('rrule') ; - if ( is_array($rrule) ) { - if ( $rrule['FREQ'] == 'YEARLY' ) { - // calculate transition dates starting from DTSTART - $offsetchangetimestamp = $dtstarttimestamp; - // calculate transition dates until 10 years in the future - $stoptimestamp = strtotime("+10 year",time()); - // if UNTIL is set, calculate until then (however far ahead) - if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) { - $stoptimestamp = mktime( - $rrule['UNTIL']['hour'], - $rrule['UNTIL']['min'], - $rrule['UNTIL']['sec'], - $rrule['UNTIL']['month'], - $rrule['UNTIL']['day'], - $rrule['UNTIL']['year'] - ) ; - } - $count = 0 ; - $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ; - $daynames = array( - 'SU' => 'Sunday', - 'MO' => 'Monday', - 'TU' => 'Tuesday', - 'WE' => 'Wednesday', - 'TH' => 'Thursday', - 'FR' => 'Friday', - 'SA' => 'Saturday' - ); - // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates - while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) { - // break up the timestamp into its parts - $datearray = getdate($offsetchangetimestamp); - if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) { - // set the month - $datearray['mon'] = $rrule['BYMONTH'] ; - } - if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) { - // set specific day of month - $datearray['mday'] = $rrule['BYMONTHDAY']; - } elseif ( is_array($rrule['BYDAY']) ) { - // find the Xth WKDAY in the month - // the starting point for this process is the first of the month set above - $datearray['mday'] = 1 ; - // turn $datearray as it is now back into a timestamp - $offsetchangetimestamp = mktime( - $datearray['hours'], - $datearray['minutes'], - $datearray['seconds'], - $datearray['mon'], - $datearray['mday'], - $datearray['year'] - ); - if ($rrule['BYDAY'][0] > 0) { - // to find Xth WKDAY in month, we find last WKDAY in month before - // we do that by finding first WKDAY in this month and going back one week - // then we add X weeks (below) - $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp); - $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp); - } else { - // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month - // we do that by going forward one month and going to WKDAY there - // then we subtract X weeks (below) - $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp); - $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp); - } - // now move forward or back the appropriate number of weeks, into the month we want - $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp); - $datearray = getdate($offsetchangetimestamp); - } - // convert the date parts back into a timestamp, setting the time parts according to the - // original time data which we stored - $offsetchangetimestamp = mktime( - $changetime['hours'], - $changetime['minutes'], - $changetime['seconds'] + $diff, - $datearray['mon'], - $datearray['mday'], - $datearray['year'] - ); - // add this change to the array of changes - $tzdates[] = array( - 'timestamp' => $offsetchangetimestamp, - 'tzbefore' => $tzbefore, - 'tzafter' => $tzafter - ); - // update counters (timestamp and count) - $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp); - $count += 1 ; - } - } - } - // generate dates according to RDATE lines - while ($rdates = $vtzc->getProperty('rdate')) { - if ( is_array($rdates) ) { - - foreach ( $rdates as $rdate ) { - // convert the explicit change date to a timestamp - $offsetchangetimestamp = mktime( - $rdate['hour'], - $rdate['min'], - $rdate['sec'] + $diff, - $rdate['month'], - $rdate['day'], - $rdate['year'] - ) ; - // add this change to the array of changes - $tzdates[] = array( - 'timestamp' => $offsetchangetimestamp, - 'tzbefore' => $tzbefore, - 'tzafter' => $tzafter - ); - } - } - } - return $tzdates; -} -?> \ No newline at end of file diff --git a/dav/iCalcreator/lgpl.txt b/dav/iCalcreator/lgpl.txt deleted file mode 100755 index 5ab7695ab..000000000 --- a/dav/iCalcreator/lgpl.txt +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/dav/iCalcreator/releaseNotes-2.12.txt b/dav/iCalcreator/releaseNotes-2.12.txt deleted file mode 100755 index 2a145dd34..000000000 --- a/dav/iCalcreator/releaseNotes-2.12.txt +++ /dev/null @@ -1,88 +0,0 @@ -2.10.24 ###################### -upd returnCalendar, only create header 'content-length' when gziping output - -2.10.26 ###################### -Concatenate iCalcreator, utilityFunction and helper functions in iCalcreator.class.php file - -2.10.27 ###################### -parsing dates with extended (MS outlook) time zones - -2.10.28 ###################### -bug in function selectComponents, (continue if) missing dtstart - -2.10.29 ###################### -new function ms2phpTZ, (very) simple mapping of MS outlook time zones to PHP - -2.10.30 ###################### thanks Yitzchok -external (iCalUtilityFunctions) time zone helper contributions:getTimezonesAsDateArrays (expandTimezoneDates) and getTzOffsetForDate functions - -2.11.1 ###################### -creation of rfc6321 XML output, input iCalcreator instance -new (helper) function iCal2XML ('inner' _addXMLchild), - -2.11.2 ###################### -parse of rfc6321 XML input (string/file), output iCalcreator instance -new (helper) functions: XMLstr2iCal, XMLfile2iCal, XML2iCal -('inner' functions:_getXMLParams, _getXMLComponents, _getXMLProperties) - -2.11.3 ###################### -bug in getProperty, management of properties with multiple ocurrences - -2.11.4 ###################### -bug in _tz2offset, plus/minus error - -2.11.5 ###################### -updated _setDate _setDate2, setExdate, setRdate, utc offset management, Z-suffix - -2.11.7 ###################### -bug in function getConfig, 'directory' or 'filename' couldn't accept '0' (zero) - -2.11.8 ###################### -upd createTimezone (_setTZrrule), fixing from/to UTC offset and rdate(-s) -upd functions: _format_date_time, _setDate, _setDate2, setRdate, setExdate, - _setRexrule, _isArrayDate -new function: _strDate2arr - -2.11.9 ###################### -check x-props names, start with 'x-'/'X-' - -2.11.10 ###################### -function parse, management of list content in TEXT properties - -2.11.11 ###################### -always update PRODID when (re-)setting 'unique_id' - -2.11.12 ###################### -update management of (Attendee) parameter value lists -update rfc5545 parameters with 'DQUOTE' settings -function createAttendee and _createParams - -2.11.13 ###################### -upd _size75, correct line break when property content ends with '0' at pos 76 - -2.11.14 ###################### -upd function transformDateTime, now accepting input date in array format - -2.11.15 ###################### -bug in function _setRexrule, UNTIL in DATE format - -2.11.16 ###################### -property ATTACH and large file attachments - -2.11.17 ###################### -bug in ATTENDEE, parsing error - -2.11.19 ###################### -upd using.html - -2.11.20 ###################### -upd createComponent, sorting standard/daylight subComponents - -2.11.21 ###################### -bug in selectComponents: long 'event' starting before period - -2.11.23 ###################### -bug: create FREEBUSY, empty property - -2.11.24 ###################### -bug: set/create RELATED-TO mgnt diff --git a/dav/iCalcreator/releaseSummary.txt b/dav/iCalcreator/releaseSummary.txt deleted file mode 100755 index c78b19394..000000000 --- a/dav/iCalcreator/releaseSummary.txt +++ /dev/null @@ -1,31 +0,0 @@ -A major subrelease: -- Concatenate iCalcreator, utilityFunction and helper functions in iCalcreator.class.php file -- new functionality: --- external time zone helper contributions:getTimezonesAsDateArrays - (expandTimezoneDates) and getTzOffsetForDate functions --- create and parse of rfc6321 XML --- ms2phpTZ, mapping of MS outlook time zones to PHP --- createComponent, sorting standard/daylight subComponents -- uppdates: --- parsing dates with extended (MS outlook) time zones --- returnCalendar, only create header 'content-length' when gziping output --- _setDate _setDate2, setExdate, setRdate, utc offset management, Z-suffix --- createTimezone (_setTZrrule), fixing from/to UTC offset and rdate(-s) --- checking x-props names, start with 'x-'/'X-' --- parse, management of list content in TEXT properties --- all rfc5545 parameters with 'DQUOTE' settings --- transformDateTime, accepting input date in array format --- property ATTACH and large file attachments -- fixed bugs: --- selectComponents, (continue if) missing dtstart --- selectComponents: long 'event' starting before period --- getProperty, management of properties with multiple ocurrences --- _tz2offset, plus/minus error --- getConfig, 'directory' or 'filename' couldn't accept '0' (zero) --- _size75, correct line break when property content ends with '0' at pos 76-- --- _setRexrule, UNTIL in DATE format --- ATTENDEE, parsing error --- setFREEBUSY, empty property --- set/create RELATED-TO mgnt --- update of PRODID when (re-)setting 'unique_id' -- updated using manual diff --git a/dav/iCalcreator/summary.html b/dav/iCalcreator/summary.html deleted file mode 100755 index 40e5f6c5c..000000000 --- a/dav/iCalcreator/summary.html +++ /dev/null @@ -1,387 +0,0 @@ - - - -iCalcreator 2.12 summary - - - - - - - - -

iCalcreator v2.12

-iCalcreator v2.12
-copyright (c) 2007-2012 Kjell-Inge Gustafsson, kigkonsult
-kigkonsult.se iCalcreator
-kigkonsult.se contact
-
-iCalcreator is a PHP class package managing iCal files, supporting (non-)calendar -systems and applications to process and communicate calendar information like -events, agendas, tasks, reports, totos and journaling information. -

-This is a short summary how to use iCalcreator; create, parse, edit, select and output functionality. -

-The iCalcreator package, built of a calendar class with support of a function class and helper functions, are calendar -component property oriented. Development environment is PHP version 5.x but coding is done -to meet 4.x backward compatibility and may work. Some functions requires PHP >= 5.2.0. -

-The iCalcreator main class, utility class and helper functions are included in the "iCalcreator.class.php" file. -

-More iCalcreator supplementary and "howto" information will be found at -kigkonsult.se iCalcreator implement examples and test pages. -A strong recommendation is to have the document user's manual -open in parallell when exploiting the link. - -

iCal

-A short iCal description is found at Wikipedia. If You are not familiar with iCal, read this first!
-Knowledge of calendar protocol rfc5545/rfc5546 is to recommend;
-rfc5545 - - Internet Calendaring and Scheduling Core Object Specification (iCalendar)
-rfc5546 - - iCalendar Transport-Independent Interoperability Protocol (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries
-rfc5545 and -rfc5546 -obsoletes, respectively, -rfc2445 and -rfc2446. -
- -

xCal

-iCalcreator also supports xCal (iCal xml)
-rfc6321 - - "xCal: The XML Format for iCalendar" -
- -

SUPPORT

-The main support channel is using iCalcreator -Sourceforge forum. -
-
-kigkonsult offer services for software support, design and development of customizations and adaptations of PHP/MySQL solutions -with a special focus on software long term utility and reliability, -supported through our agile acquire/design/transition process model. -
- -

DONATE

-You can show your appreciation for our free software, -and can support future development by making a donation to the kigkonsult GPL/LGPL projects. -
-
-Make a donation of any size by clicking here. -Thanks in advance! -
- -

Contact

-Use the contact page -for queries, improvement/development issues or professional support and development. -Please note that paid support or consulting service has the highest priority. -
- -

Downloads and usage examples

-On kigkonsult.se can you download the -complete manual -and review -coding and test examples. -
- -

INSTALL

-Unpack to any folder
-- add this folder to your include-path
-- or unpack to your application-(include)-folder
-Add "require_once '[folder/]iCalcreator.class.php';" to your php-script. -
-
-If using PHP version 5.1 or higher, the default timezone need to be set/altered, now "Europe/Stockholm", -line 50 in the iCalcreator.class.php file. -
-When creating a new calendar/component instance, review config settings. -
-
-To really boost performance, visit kigkonsult.se contact page for information. -
-
- - -

CREATE

- -

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se" ); // set a (site) unique id -$v = new vcalendar( $config ); // create a new calendar instance -$tz = "Europe/Stockholm"; // define time zone - -$v->setProperty( "method", "PUBLISH" ); // required of some calendar software -$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software -$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software -$v->setProperty( "X-WR-TIMEZONE", $tz ); // required of some calendar software -.. . -$xprops = array( "X-LIC-LOCATION" => $tz ); // required of some calendar software -iCalUtilityFunctions::createTimezone( $v, $tz, $xprops ); // create timezone component(-s) opt. 1 -.. . // based on present date -.. . -$vevent = & $v->newComponent( "vevent" ); // create an event calendar component -$vevent->setProperty( "dtstart", array( "year"=>2007, "month"=>4, "day"=>1, "hour"=>19, "min"=>0, "sec"=>0 )); -$vevent->setProperty( "dtend", array( "year"=>2007, "month"=>4, "day"=>1, "hour"=>22, "min"=>30, "sec"=>0 )); -$vevent->setProperty( "LOCATION", "Central Placa" ); // property name - case independent -$vevent->setProperty( "summary", "PHP summit" ); -$vevent->setProperty( "description", "This is a description" ); -$vevent->setProperty( "comment", "This is a comment" ); -$vevent->setProperty( "attendee", "attendee1@icaldomain.net" ); -.. . -$valarm = & $vevent->newComponent( "valarm" ); // create an event alarm -$valarm->setProperty("action", "DISPLAY" ); -$valarm->setProperty("description", $vevent->getProperty( "description" ); -.. . // reuse the event description -.. . -$d = sprintf( '%04d%02d%02d %02d%02d%02d', 2007, 3, 31, 15, 0, 0 ); -iCalUtilityFunctions::transformDateTime( $d, $tz, "UTC", "Ymd\THis\Z"); -$valarm->setProperty( "trigger", $d ); // create alarm trigger (in UTC datetime) -.. . -$vevent = & $v->newComponent( "vevent" ); // create next event calendar component -$vevent->setProperty( "dtstart", "20070401", array("VALUE" => "DATE"));// alt. date format, now for an all-day event -$vevent->setProperty( "organizer" , "boss@icaldomain.com" ); -$vevent->setProperty( "summary", "ALL-DAY event" ); -$vevent->setProperty( "description", "This is a description for an all-day event" ); -$vevent->setProperty( "resources", "COMPUTER PROJECTOR" ); -$vevent->setProperty( "rrule", array( "FREQ" => "WEEKLY", "count" => 4));// weekly, four occasions -$vevent->parse( "LOCATION:1CP Conference Room 4350" ); // supporting parse of strict rfc5545 formatted text -.. . -.. .// all calendar components are described in rfc5545 -.. .// a complete iCalcreator function list (ex. setProperty) in iCalcreator manual -.. . -iCalUtilityFunctions::createTimezone( $v, $tz, $xprops); // create timezone component(-s) opt. 2 -.. . // based on all start dates in events (i.e. dtstart) -.. . -.. . -

-
-
- -

PARSE

-

iCal, rfc5545 / rfc2445

-

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se" ); // set a (site) unique id, required if any component UID is missing -$v = new vcalendar( $config ); // create a new calendar instance - - /* start parse of local iCal file */ -$config = array( "directory" => "calendar", "filename" => "file.ics" ); -$v->setConfig( $config ); // set directory and file name -$v->parse(); - - /* start parse of remote iCal file */ -$v->setConfig( "url", "http://www.aDomain.net/file.ics" ); // iCalcreator also support parse of remote files -$v->parse(); - -$v->setProperty( "method", "PUBLISH" ); // required of some calendar software -$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software -$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software -$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software - -.. . -$v->sort(); // ensure start date order -.. . -.. . // continue process (edit, parse,select) the iCalcreator instance -.. . -

- -

xCal, rfc6321 (XML)

-

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se" ); // set a (site) unique id, required if any component UID is missing -.. . -$filename = 'xmlfile.xml'; // use a local xCal file -// $filename = 'http://kigkonsult.se/xcal.php?a=1&b=2&c=3';// or a remote xCal resource -if( FALSE === ( $v = XMLfile2iCal( $filename, $config ))) // convert the XML resource to an iCalcreator instance - exit( "Error when parsing $filename" ); -.. . -.. . // continue process (edit, parse,select) the iCalcreator instance -.. . -

-
- -

EDIT

-

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se", "directory" => "calendar", "filename" => "file.ics" ); - // set the (site) unique id, the import directory and file name -$v = new vcalendar( $config ); // create a new calendar instance - -$v->parse(); - -$v->setProperty( "method", "PUBLISH" ); // required of some calendar software -$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software -$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software -$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software - -while( $vevent = $v->getComponent( "vevent" )) { // read events, one by one - $uid = $vevent->getProperty( "uid" ); // uid required, one occurrence (unique id/key for component) - .. . - $dtstart = $vevent->getProperty( "dtstart" ); // dtstart required, one occurrence - .. . - if( $description = $vevent->getProperty( "description", 1 )) { // description optional, first occurrence - .. . // edit the description - $vevent->setProperty( "description", $description, FALSE, 1 ); // update/replace the description - } - while( $comment = $vevent->getProperty( "comment" )) { // comment optional, may occur more than once - .. . // manage comments - } - .. . - while( $vevent->deleteProperty( "attendee" )) - continue; // remove all ATTENDEE properties .. . - .. . - $v->setComponent ( $vevent, $uid ); // update/replace event in calendar with UID as key -} -.. . -.. .// a complete iCalcreator function list (ex. getProperty, deleteProperty) in iCalcreator manual -.. . -

-
-
- -

SELECT

-

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se" ); // set a (site) unique id -$v = new vcalendar( $config ); // create a new calendar instance - -$v->setConfig( "url", "http://www.aDomain.net/file.ics" ); // iCalcreator also support remote files -$v->parse(); -$v->sort(); // ensure start date order - -$v->setProperty( "method", "PUBLISH" ); // required of some calendar software -$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software -$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software -$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software -

-

Select components based on specific date period

-

$eventArray = $v->selectComponents(); // select components occurring today - // (including components with recurrence pattern) -foreach( $eventArray as $year => $yearArray) { - foreach( $yearArray as $month => $monthArray ) { - foreach( $monthArray as $day => $dailyEventsArray ) { - foreach( $dailyEventsArray as $vevent ) { - $currddate = $event->getProperty( "x-current-dtstart" ); - // if member of a recurrence set (2nd occurrence etc) - // returns array( "x-current-dtstart" - // , <(string) date("Y-m-d [H:i:s][timezone/UTC offset]")>) - $dtstart = $vevent->getProperty( "dtstart" ); // dtstart required, one occurrence, (orig. start date) - $summary = $vevent->getProperty( "summary" ); - $description = $vevent->getProperty( "description" ); - .. . - .. . - } - } - } -} -

-

Select specific property values

-

$valueOccur = $v->getProperty( "RESOURCES" ); // fetch specific property (unique) values and occurrences - // ATTENDEE, CATEGORIES, DTSTART, LOCATION, - // ORGANIZER, PRIORITY, RESOURCES, STATUS, - // SUMMARY, UID -foreach( $valueOccur as $uniqueValue => $occurCnt ) { - echo "The RESOURCES value <b>$uniqueValue</b> occurs <b>$occurCnt</b> times<br />"; - .. . -} -

-

Select components based on specific property value

-

$selectSpec = array( "CATEGORIES" => "course1" ); -$specComps = $v->selectComponents( $selectSpec ); // selects components based on specific property value(-s) - // ATTENDEE, CATEGORIES, LOCATION, ORGANIZER, - // PRIORITY, RESOURCES, STATUS, SUMMARY, UID -foreach( $specComps as $comp ) { - .. . -} -

-
-
- -

OUTPUT

-

require_once( "iCalcreator.class.php" ); -$config = array( "unique_id" => "kigkonsult.se" ); // set a (site) unique id -$v = new vcalendar( $config ); // create a new calendar instance - -$v->setProperty( "method", "PUBLISH" ); // required of some calendar software -$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software -$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software -$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software -.. . -.. . // continue process (edit, parse,select) the iCalcreator instance -.. . -

-

opt 1

-

.. . -$v->returnCalendar(); // redirect calendar file to browser -

- -

opt 2

-

.. . -$config = array( "directory" => "depot", "filename" => "calendar.ics" ); -$v->setConfig( $config ); // set output directory and file name -$v->saveCalendar(); // save calendar to (local) file -.. . -

- -

opt 3, xCal

-

.. . -$xmlstr = iCal2XML( $v ); // create well-formed XML, rfc6321 -... -

-
-
- - -

COPYRIGHT AND LICENSE

- -

Copyright

-iCalcreator v2.12
-copyright (c) 2007-2012 Kjell-Inge Gustafsson, kigkonsult
-kigkonsult.se iCalcreator
-kigkonsult.se contact
-
- -

License

- -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. -

-This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. -

-You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -or download it here. - - \ No newline at end of file diff --git a/dav/jqueryui/jquery-ui-1.8.20.custom.css b/dav/jqueryui/jquery-ui-1.8.21.custom.css similarity index 94% rename from dav/jqueryui/jquery-ui-1.8.20.custom.css rename to dav/jqueryui/jquery-ui-1.8.21.custom.css index 089d68e17..d7a784220 100644 --- a/dav/jqueryui/jquery-ui-1.8.20.custom.css +++ b/dav/jqueryui/jquery-ui-1.8.21.custom.css @@ -1,5 +1,5 @@ /*! - * jQuery UI CSS Framework 1.8.20 + * 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. @@ -39,7 +39,7 @@ /*! - * jQuery UI CSS Framework 1.8.20 + * 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. @@ -285,7 +285,28 @@ /* Overlays */ .ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); } .ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*! - * jQuery UI Datepicker 1.8.20 + * 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. diff --git a/dav/jqueryui/jquery-ui-1.8.20.custom.min.js b/dav/jqueryui/jquery-ui-1.8.21.custom.min.js similarity index 68% rename from dav/jqueryui/jquery-ui-1.8.20.custom.min.js rename to dav/jqueryui/jquery-ui-1.8.21.custom.min.js index bb6ed07d3..424d9cf08 100644 --- a/dav/jqueryui/jquery-ui-1.8.20.custom.min.js +++ b/dav/jqueryui/jquery-ui-1.8.21.custom.min.js @@ -1,9 +1,21 @@ -/*! jQuery UI - v1.8.20 - 2012-04-30 +/*! 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.20",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.dialog.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("
")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("
").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.20"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
'+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
'+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
'+this._get(a,"weekHeader")+"
'+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
"+(j?""+(g[0]>0&&N==g[1]-1?'
':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.20",window["DP_jQuery_"+dpuuid]=$})(jQuery);; \ No newline at end of file +(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($('
'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
'+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
'+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
'+this._get(a,"weekHeader")+"
'+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
"+(j?""+(g[0]>0&&N==g[1]-1?'
':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);; \ No newline at end of file diff --git a/dav/jqueryui/jquery.ui.datepicker-de.js b/dav/jqueryui/jquery.ui.datepicker-de.js new file mode 100644 index 000000000..ac2d516aa --- /dev/null +++ b/dav/jqueryui/jquery.ui.datepicker-de.js @@ -0,0 +1,23 @@ +/* German initialisation for the jQuery UI date picker plugin. */ +/* Written by Milian Wolff (mail@milianw.de). */ +jQuery(function($){ + $.datepicker.regional['de'] = { + closeText: 'schließen', + prevText: '<zurück', + nextText: 'Vor>', + currentText: 'heute', + monthNames: ['Januar','Februar','März','April','Mai','Juni', + 'Juli','August','September','Oktober','November','Dezember'], + monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun', + 'Jul','Aug','Sep','Okt','Nov','Dez'], + dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], + dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], + dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'], + weekHeader: 'Wo', + dateFormat: 'dd.mm.yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['de']); +}); diff --git a/dav/layout.fnk.php b/dav/layout.fnk.php index 550f90b6a..597e773d2 100644 --- a/dav/layout.fnk.php +++ b/dav/layout.fnk.php @@ -8,36 +8,8 @@ function wdcal_addRequiredHeaders() { $a = get_app(); - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; - - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; - - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; - - switch (get_config("system", "language")) { - case "de": - $a->page['htmlhead'] .= '' . "\r\n"; - break; - default: - $a->page['htmlhead'] .= '' . "\r\n"; - } - - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; -} - -/** - * - */ -function wdcal_addRequiredHeadersEdit() -{ - $a = get_app(); - - $a->page['htmlhead'] .= '' . "\r\n"; - $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; @@ -48,12 +20,145 @@ function wdcal_addRequiredHeadersEdit() $a->page['htmlhead'] .= '' . "\r\n"; $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + + switch (get_config("system", "language")) { + case "de": + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; + break; + default: + $a->page['htmlhead'] .= '' . "\r\n"; + } + + $a->page['htmlhead'] .= '' . "\r\n"; + $a->page['htmlhead'] .= '' . "\r\n"; +} + + + +/** + * @param int $calendar_id + */ +function wdcal_print_user_ics($calendar_id) +{ + $calendar_id = IntVal($calendar_id); + + $a = get_app(); + header("Content-type: text/plain"); + + $str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\n"; + $cals = q("SELECT * FROM %s%scalendars WHERE `id` = %d AND `namespace` = %d AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id, CALDAV_NAMESPACE_PRIVATE, $a->user["uid"]); + if (count($cals) > 0) { + $objs = q("SELECT * FROM %s%scalendarobjects WHERE `calendar_id` = %d ORDER BY `firstOccurence`", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id); + + foreach ($objs as $obj) { + preg_match("/BEGIN:VEVENT(.*)END:VEVENT/siu", $obj["calendardata"], $matches); + $str2 = preg_replace("/([^\\r])\\n/siu", "\\1\r\n", $matches[0]); + $str2 = preg_replace("/MAILTO:.*[^:a-z0-9_\+äöüß\\n\\n@-]+.*(:|\\r\\n[^ ])/siU", "\\1", $str2); + $str .= $str2 . "\r\n"; + } + } + $str .= "END:VCALENDAR\r\n"; + + echo $str; + killme(); } /** - * @param array|DBClass_friendica_calendars[] $calendars - * @param array $calendar_preselected + * @param int $calendar_id + * @return string + */ +function wdcal_import_user_ics($calendar_id) { + $a = get_app(); + $calendar_id = IntVal($calendar_id); + $o = ""; + + $server = dav_create_server(true, true, false); + $calendar = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_WRITE); + if (!$calendar) goaway($a->get_baseurl() . "/dav/wdcal/"); + + if (isset($_REQUEST["save"])) { + check_form_security_token_redirectOnErr('/dav/settings/', 'icsimport'); + + if ($_FILES["ics_file"]["tmp_name"] != "" && is_uploaded_file($_FILES["ics_file"]["tmp_name"])) try { + $text = file_get_contents($_FILES["ics_file"]["tmp_name"]); + + /** @var Sabre_VObject_Component_VCalendar $vObject */ + $vObject = Sabre_VObject_Reader::read($text); + $comp = $vObject->getComponents(); + $imported = array(); + foreach ($comp as $c) try { + /** @var Sabre_VObject_Component_VEvent $c */ + $uid = $c->__get("UID")->value; + if (!isset($imported[$uid])) $imported[$uid] = ""; + $imported[$uid] .= $c->serialize(); + } catch (Exception $e) { + notice(t("Something went wrong when trying to import the file. Sorry. Maybe some events were imported anyway.")); + } + + if (isset($_REQUEST["overwrite"])) { + $children = $calendar->getChildren(); + foreach ($children as $child) { + /** @var Sabre_CalDAV_CalendarObject $child */ + $child->delete(); + } + $i = 1; + } else { + $i = 0; + $children = $calendar->getChildren(); + foreach ($children as $child) { + /** @var Sabre_CalDAV_CalendarObject $child */ + $name = $child->getName(); + if (preg_match("/import\-([0-9]+)\.ics/siu", $name, $matches)) { + if ($matches[1] > $i) $i = $matches[1]; + }; + } + $i++; + } + + foreach ($imported as $object) try { + + $str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Friendica//DAV-Plugin//EN\r\n"; + $str .= trim($object); + $str .= "\r\nEND:VCALENDAR\r\n"; + + $calendar->createFile("import-" . $i . ".ics", $str); + $i++; + } catch (Exception $e) { + notice(t("Something went wrong when trying to import the file. Sorry.")); + } + + $o = t("The ICS-File has been imported."); + } catch (Exception $e) { + notice(t("Something went wrong when trying to import the file. Sorry. Maybe some events were imported anyway.")); + } else { + notice(t("No file was uploaded.")); + } + } + + + $o .= "" . t("Go back to the calendar") . "

"; + + $num = q("SELECT COUNT(*) num FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $calendar_id); + + $o .= "

" . t("Import a ICS-file") . "

"; + $o .= '
'; + $o .= "\n"; + $o .= "
\n"; + if ($num[0]["num"] > 0) $o .= "
\n"; + $o .= ""; + $o .= '
'; + + return $o; +} + + +/** + * @param array|Sabre_CalDAV_Calendar[] $calendars + * @param array|int[] $calendars_selected * @param string $data_feed_url * @param string $view * @param int $theme @@ -64,14 +169,17 @@ function wdcal_addRequiredHeadersEdit() * @param bool $show_nav * @return string */ -function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url, $view = "week", $theme = 0, $height_diff = 175, $readonly = false, $curr_day = "", $add_params = array(), $show_nav = true) +function wdcal_printCalendar($calendars, $calendars_selected, $data_feed_url, $view = "week", $theme = 0, $height_diff = 175, $readonly = false, $curr_day = "", $add_params = array(), $show_nav = true) { $a = get_app(); $localization = wdcal_local::getInstanceByUser($a->user["uid"]); - $cals_avail = array(); - foreach ($calendars as $c) $cals_avail[] = array("ns" => $c->namespace, "id" => $c->namespace_id, "displayname" => $c->displayname); + if (count($calendars_selected) == 0) foreach ($calendars as $c) { + $prop = $c->getProperties(array("id")); + $calendars_selected[] = $prop["id"]; + } + $opts = array( "view" => $view, "theme" => $theme, @@ -96,12 +204,13 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url,
Available Calendars:'; - foreach ($cals_avail as $cal) { - $x .= '
@@ -179,166 +288,58 @@ function wdcal_printCalendar($calendars, $calendar_preselected, $data_feed_url, /** - * @param string $uri - * @param string $recurr_uri + * @param int $calendar_id + * @param int $calendarobject_id * @return string */ -function wdcal_getDetailPage($uri, $recurr_uri) +function wdcal_getDetailPage($calendar_id, $calendarobject_id) { $a = get_app(); - $details = null; - $cals = dav_getMyCals($a->user["uid"]); - foreach ($cals as $c) { - $cs = wdcal_calendar_factory($a->user["uid"], $c->namespace, $c->namespace_id); - $p = $cs->getPermissionsItem($a->user["uid"], $uri, $recurr_uri); - if ($p["read"]) try { - $redirect = $cs->getItemDetailRedirect($uri); - if ($redirect !== null) goaway($redirect); - $details = $cs->getItemByUri($uri); - } catch (Exception $e) { - notification(t("Error") . ": " . $e); - goaway($a->get_baseurl() . "/dav/wdcal/"); - } + try { + $details = null; + $server = dav_create_server(true, true, false); + $cal = dav_get_current_user_calendar_by_id($server, $calendar_id, DAV_ACL_READ); + $obj = Sabre_CalDAV_Backend_Common::loadCalendarobjectById($calendarobject_id); + dav_get_current_user_calendarobject($server, $cal, $obj["uri"], DAV_ACL_READ); // Check permissions + + $calbackend = wdcal_calendar_factory_by_id($calendar_id); + $redirect = $calbackend->getItemDetailRedirect($calendar_id, $calendarobject_id); + + if ($redirect !== null) goaway($a->get_baseurl() . $redirect); + + $details = $obj; + } catch (Exception $e) { + info(t("Error") . ": " . $e); + goaway($a->get_baseurl() . "/dav/wdcal/"); } - - return $uri . " / " . $recurr_uri . "
" . print_r($details, true); + return print_r($details, true); } + /** - * @param string $uri - * @param string $recurr_uri + * @param int $calendar_id + * @param int $uri * @return string */ -function wdcal_getEditPage($uri, $recurr_uri = "") +function wdcal_getEditPage($calendar_id, $uri) { - $a = get_app(); $localization = wdcal_local::getInstanceByUser($a->user["uid"]); - if ($uri != "" && $uri != "new") { - $o = q("SELECT * FROM %s%sjqcalendar WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri) - ); - if (count($o) != 1) return t('Not found'); - $event = $o[0]; + return wdcal_getEditPage_str($localization, $a->get_baseurl(), $calendar_id, $uri); +} - $calendarSource = wdcal_calendar_factory($a->user["uid"], $event["namespace"], $event["namespace_id"]); +/** + * @return string + */ +function wdcal_getNewPage() +{ + $a = get_app(); + $localization = wdcal_local::getInstanceByUser($a->user["uid"]); - $permissions = $calendarSource->getPermissionsItem($a->user["uid"], $uri, $recurr_uri, $event); - - if (!$permissions["write"]) return t('No access'); - - $n = q("SELECT * FROM %s%snotifications WHERE `uid` = %d AND `ical_uri` = '%s' AND `ical_recurr_uri` = '%s'", - CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $a->user["uid"], dbesc($uri), dbesc($recurr_uri) - ); - if (count($n) > 0) { - $notification_type = $n[0]["rel_type"]; - $notification_value = -1 * $n[0]["rel_value"]; - $notification = true; - } else { - if ($event["IsAllDayEvent"]) { - $notification_type = "hour"; - $notification_value = 24; - } else { - $notification_type = "minute"; - $notification_value = 60; - } - $notification = false; - } - - - } elseif (isset($_REQUEST["start"]) && $_REQUEST["start"] > 0) { - $event = array( - "id" => 0, - "Subject" => $_REQUEST["title"], - "Location" => "", - "Description" => "", - "StartTime" => wdcal_php2MySqlTime($_REQUEST["start"]), - "EndTime" => wdcal_php2MySqlTime($_REQUEST["end"]), - "IsAllDayEvent" => $_REQUEST["isallday"], - "Color" => null, - "RecurringRule" => null, - ); - if ($_REQUEST["isallday"]) { - $notification_type = "hour"; - $notification_value = 24; - } else { - $notification_type = "hour"; - $notification_value = 1; - } - - $notification = true; - } else { - $event = array( - "id" => 0, - "Subject" => "", - "Location" => "", - "Description" => "", - "StartTime" => date("Y-m-d H:i:s"), - "EndTime" => date("Y-m-d H:i:s", time() + 3600), - "IsAllDayEvent" => "0", - "Color" => "#5858ff", - "RecurringRule" => null, - ); - $notification_type = "hour"; - $notification_value = 1; - $notification = true; - } - - $postto = $a->get_baseurl() . "/dav/wdcal/" . ($uri == "new" ? "new/" : $uri . "/edit/"); - - $out = "" . t("Go back to the calendar") . "

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

'; - - - $out .= ""; - - $out .= "
"; - - return $out; + return wdcal_getEditPage_str($localization, $a->get_baseurl(), 0, 0); } @@ -355,11 +356,67 @@ function wdcal_getSettingsPage(&$a) } if (isset($_REQUEST["save"])) { - check_form_security_token_redirectOnErr($a->get_baseurl() . '/dav/settings/', 'calprop'); + check_form_security_token_redirectOnErr('/dav/settings/', 'calprop'); set_pconfig($a->user["uid"], "dav", "dateformat", $_REQUEST["wdcal_date_format"]); info(t('The new values have been saved.')); } + if (isset($_REQUEST["save_cals"])) { + check_form_security_token_redirectOnErr('/dav/settings/', 'calprop'); + + $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($a->user["uid"])); + foreach ($r as $cal) { + $backend = wdcal_calendar_factory($cal["namespace"], $cal["namespace_id"], $cal["uri"], $cal); + $change_sql = ""; + $col = substr($_REQUEST["color"][$cal["id"]], 1); + if (strtolower($col) != strtolower($cal["calendarcolor"])) $change_sql .= ", `calendarcolor` = '" . dbesc($col) . "'"; + if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual")) { + if ($_REQUEST["uri"][$cal["id"]] != $cal["uri"]) $change_sql .= ", `uri` = '" . dbesc($_REQUEST["uri"][$cal["id"]]) . "'"; + if ($_REQUEST["name"][$cal["id"]] != $cal["displayname"]) $change_sql .= ", `displayname` = '" . dbesc($_REQUEST["name"][$cal["id"]]) . "'"; + } + if ($change_sql != "") { + q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 $change_sql WHERE `id` = %d AND `namespace_id` = %d AND `namespace_id` = %d", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $cal["id"], CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"])); + info(t('The calendar has been updated.')); + } + } + + if (isset($_REQUEST["uri"]["new"]) && $_REQUEST["uri"]["new"] != "" && $_REQUEST["name"]["new"] && $_REQUEST["name"]["new"] != "") { + $order = q("SELECT MAX(`calendarorder`) ord FROM %s%scalendars WHERE `namespace_id` = %d AND `namespace_id` = %d", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"])); + $neworder = $order[0]["ord"] + 1; + q("INSERT INTO %s%scalendars (`namespace`, `namespace_id`, `calendarorder`, `calendarcolor`, `displayname`, `timezone`, `uri`, `has_vevent`, `ctag`) + VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', 1, 1)", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"]), $neworder, dbesc(strtolower(substr($_REQUEST["color"]["new"], 1))), + dbesc($_REQUEST["name"]["new"]), dbesc($a->timezone), dbesc($_REQUEST["uri"]["new"]) + ); + info(t('The new calendar has been created.')); + } + } + + if (isset($_REQUEST["remove_cal"])) { + check_form_security_token_redirectOnErr('/dav/settings/', 'del_cal', 't'); + + $c = q("SELECT * FROM %s%scalendars WHERE `id` = %d AND `namespace_id` = %d AND `namespace_id` = %d", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]), CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"])); + if (count($c) != 1) killme(); + + $calobjs = q("SELECT `id` FROM %s%scalendarobjects WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"])); + + $newcal = q("SELECT * FROM %s%scalendars WHERE `id` != %d AND `namespace_id` = %d AND `namespace_id` = %d ORDER BY `calendarcolor` LIMIT 0,1", + CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"]), CALDAV_NAMESPACE_PRIVATE, IntVal($a->user["uid"])); + if (count($newcal) != 1) killme(); + + q("UPDATE %s%scalendarobjects SET `calendar_id` = %d WHERE `calendar_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($newcal[0]["id"]), IntVal($c[0]["id"])); + + foreach ($calobjs as $calobj) renderCalDavEntry_calobj_id($calobj["id"]); + + q("DELETE FROM %s%scalendars WHERE `id` = %s", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($_REQUEST["remove_cal"])); + q("UPDATE %s%scalendars SET `ctag` = `ctag` + 1 WHERE `id` = " . CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $newcal[0]["id"]); + + info(t('The calendar has been deleted.')); + } + $o = ""; $o .= "" . t("Go back to the calendar") . "

"; @@ -384,6 +441,58 @@ function wdcal_getSettingsPage(&$a) $o .= ''; $o .= ''; + + $o .= '

' . t('Calendars') . '

'; + $o .= '
'; + $o .= "\n"; + $o .= ""; + + $r = q("SELECT * FROM %s%scalendars WHERE `namespace` = " . CALDAV_NAMESPACE_PRIVATE . " AND `namespace_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($a->user["uid"])); + $private_max = 0; + $num_non_virtual = 0; + foreach ($r as $x) { + $backend = wdcal_calendar_factory($x["namespace"], $x["namespace_id"], $x["uri"], $x); + if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual")) $num_non_virtual++; + } + foreach ($r as $x) { + $p = explode("private-", $x["uri"]); + if (count($p) == 2 && $p[1] > $private_max) $private_max = $p[1]; + + $backend = wdcal_calendar_factory($x["namespace"], $x["namespace_id"], $x["uri"], $x); + $disabled = (is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") ? "disabled" : ""); + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= "\n"; + $o .= "\n"; + } + + $private_max++; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= ""; + $o .= "\n"; + + $o .= "
TypeColorNameURI (for CalDAV)ICS
" . escape_tags($backend->getBackendTypeName()) . "Export"; + if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") && $num_non_virtual > 1) $o .= " / Import"; + $o .= ""; + if (!is_subclass_of($backend, "Sabre_CalDAV_Backend_Virtual") && $num_non_virtual > 1) $o .= "Delete"; + $o .= "
"; + $o .= ""; + $o .= ''; + $o .= '
'; + $baseurl = $a->get_baseurl(); + $o .= ""; + + $o .= "

" . t("Limitations") . "

"; $o .= "- The native friendica events are embedded as read-only, half-transparent in the calendar.
"; diff --git a/dav/main.php b/dav/main.php index 6635d18ac..03f342bd2 100644 --- a/dav/main.php +++ b/dav/main.php @@ -7,6 +7,7 @@ function dav_install() register_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook'); register_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook'); register_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook'); + register_hook('cron', 'addon/dav/dav.php', 'dav_cron'); } @@ -15,6 +16,7 @@ function dav_uninstall() unregister_hook('event_created', 'addon/dav/dav.php', 'dav_event_created_hook'); unregister_hook('event_updated', 'addon/dav/dav.php', 'dav_event_updated_hook'); unregister_hook('profile_tabs', 'addon/dav/dav.php', 'dav_profile_tabs_hook'); + unregister_hook('cron', 'addon/dav/dav.php', 'dav_cron'); } @@ -24,45 +26,29 @@ function dav_module() function dav_include_files() { - require_once (__DIR__ . "/common/dbclasses/dbclass_animexx.class.php"); - require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendars.class.php"); - require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.jqcalendar.class.php"); - require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.notifications.class.php"); - require_once (__DIR__ . "/common/dbclasses/dbclass.friendica.calendarobjects.class.php"); - - /* - require_once (__DIR__ . "/SabreDAV/lib/Sabre.includes.php"); - require_once (__DIR__ . "/SabreDAV/lib/Sabre/VObject/includes.php"); - require_once (__DIR__ . "/SabreDAV/lib/Sabre/DAVACL/includes.php"); - require_once (__DIR__ . "/SabreDAV/lib/Sabre/CalDAV/includes.php"); - */ require_once (__DIR__ . "/SabreDAV/lib/Sabre/autoload.php"); - $tz_before = date_default_timezone_get(); - require_once (__DIR__ . "/iCalcreator/iCalcreator.class.php"); - date_default_timezone_set($tz_before); - require_once (__DIR__ . "/common/calendar.fnk.php"); + require_once (__DIR__ . "/common/calendar_rendering.fnk.php"); require_once (__DIR__ . "/common/dav_caldav_backend_common.inc.php"); - require_once (__DIR__ . "/common/dav_caldav_backend.inc.php"); + require_once (__DIR__ . "/common/dav_caldav_backend_private.inc.php"); + require_once (__DIR__ . "/common/dav_caldav_backend_virtual.inc.php"); require_once (__DIR__ . "/common/dav_caldav_root.inc.php"); require_once (__DIR__ . "/common/dav_user_calendars.inc.php"); require_once (__DIR__ . "/common/dav_carddav_root.inc.php"); require_once (__DIR__ . "/common/dav_carddav_backend_std.inc.php"); require_once (__DIR__ . "/common/dav_user_addressbooks.inc.php"); - require_once (__DIR__ . "/common/virtual_cal_source_backend.inc.php"); + require_once (__DIR__ . "/common/dav_caldav_calendar_virtual.inc.php"); require_once (__DIR__ . "/common/wdcal_configuration.php"); - require_once (__DIR__ . "/common/wdcal_cal_source.inc.php"); - require_once (__DIR__ . "/common/wdcal_cal_source_private.inc.php"); + require_once (__DIR__ . "/common/wdcal_backend.inc.php"); require_once (__DIR__ . "/dav_friendica_principal.inc.php"); require_once (__DIR__ . "/dav_friendica_auth.inc.php"); - require_once (__DIR__ . "/dav_carddav_backend_friendica_community.inc.php"); - require_once (__DIR__ . "/dav_caldav_backend_friendica.inc.php"); - require_once (__DIR__ . "/virtual_cal_source_friendica.inc.php"); - require_once (__DIR__ . "/wdcal_cal_source_friendicaevents.inc.php"); + require_once (__DIR__ . "/dav_carddav_backend_virtual_friendica.inc.php"); + require_once (__DIR__ . "/dav_caldav_backend_virtual_friendica.inc.php"); require_once (__DIR__ . "/FriendicaACLPlugin.inc.php"); + require_once (__DIR__ . "/common/wdcal_edit.inc.php"); require_once (__DIR__ . "/calendar.friendica.fnk.php"); require_once (__DIR__ . "/layout.fnk.php"); } @@ -88,72 +74,34 @@ function dav_init(&$a) } wdcal_create_std_calendars(); - + wdcal_addRequiredHeaders(); if ($a->argc >= 2 && $a->argv[1] == "wdcal") { if ($a->argc >= 3 && $a->argv[2] == "feed") { wdcal_print_feed($a->get_baseurl() . "/dav/wdcal/"); killme(); - } elseif ($a->argc >= 3 && strlen($a->argv[2]) > 0) { - wdcal_addRequiredHeadersEdit(); - } else { - wdcal_addRequiredHeaders(); } return; } + if ($a->argc >= 2 && $a->argv[1] == "getExceptionDates") { + echo wdcal_getEditPage_exception_selector(); + killme(); + } if ($a->argc >= 2 && $a->argv[1] == "settings") { return; } - $authBackend = new Sabre_DAV_Auth_Backend_Friendica(); - $principalBackend = new Sabre_DAVACL_PrincipalBackend_Friendica($authBackend); - $caldavBackend_std = new Sabre_CalDAV_Backend_Std(); - $caldavBackend_community = new Sabre_CalDAV_Backend_Friendica(); - $carddavBackend_std = new Sabre_CardDAV_Backend_Std(); - $carddavBackend_community = new Sabre_CardDAV_Backend_FriendicaCommunity(); - if (isset($_SERVER["PHP_AUTH_USER"])) { - $tree = new Sabre_DAV_SimpleCollection('root', array( - new Sabre_DAV_SimpleCollection('principals', array( - new Sabre_CalDAV_Principal_Collection($principalBackend, "principals/users"), - )), - new Sabre_CalDAV_AnimexxCalendarRootNode($principalBackend, array( - $caldavBackend_std, - $caldavBackend_community, - )), - new Sabre_CardDAV_AddressBookRootFriendica($principalBackend, array( - $carddavBackend_std, - $carddavBackend_community, - )), - )); - } else { - $tree = new Sabre_DAV_SimpleCollection('root', array()); + if (isset($_REQUEST["test"])) { + renderAllCalDavEntries(); } -// The object tree needs in turn to be passed to the server class - $server = new Sabre_DAV_Server($tree); - - $url = parse_url($a->get_baseurl()); - $server->setBaseUri(CALDAV_URL_PREFIX); - - $authPlugin = new Sabre_DAV_Auth_Plugin($authBackend, 'SabreDAV'); - $server->addPlugin($authPlugin); - - $aclPlugin = new Sabre_DAVACL_Plugin_Friendica(); - $aclPlugin->defaultUsernamePath = "principals/users"; - $server->addPlugin($aclPlugin); - - $caldavPlugin = new Sabre_CalDAV_Plugin(); - $server->addPlugin($caldavPlugin); - - $carddavPlugin = new Sabre_CardDAV_Plugin(); - $server->addPlugin($carddavPlugin); + $server = dav_create_server(); $browser = new Sabre_DAV_Browser_Plugin(); $server->addPlugin($browser); - $server->exec(); killme(); @@ -170,41 +118,55 @@ function dav_content() } $x = ""; - - if ($a->argv[1] == "settings") { - return wdcal_getSettingsPage($a); - } elseif ($a->argv[1] == "wdcal") { - if ($a->argc >= 3 && strlen($a->argv[2]) > 0) { - $uri = $a->argv[2]; - - if ($uri == "new") { - $o = ""; - if (isset($_REQUEST["save"])) { - check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit"); - $o .= wdcal_postEditPage("new", "", $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/"); - } - $o .= wdcal_getEditPage("new"); - return $o; - } else { - $recurr_uri = ""; // @TODO - if (isset($a->argv[3]) && $a->argv[3] == "edit") { + try { + if ($a->argv[1] == "settings") { + return wdcal_getSettingsPage($a); + } elseif ($a->argv[1] == "wdcal") { + if (isset($a->argv[2]) && strlen($a->argv[2]) > 0) { + if ($a->argv[2] == "new") { $o = ""; if (isset($_REQUEST["save"])) { check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit"); - $o .= wdcal_postEditPage($uri, $recurr_uri, $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/"); + $ret = wdcal_postEditPage("new", "", $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/"); + if ($ret["ok"]) notice($ret["msg"]); + else info($ret["msg"]); + goaway($a->get_baseurl() . "/dav/wdcal/"); } - $o .= wdcal_getEditPage($uri, $recurr_uri); + $o .= wdcal_getNewPage(); return $o; } else { - return wdcal_getDetailPage($uri, $recurr_uri); + $calendar_id = IntVal($a->argv[2]); + if (isset($a->argv[3]) && $a->argv[3] == "ics-export") { + wdcal_print_user_ics($calendar_id); + } elseif (isset($a->argv[3]) && $a->argv[3] == "ics-import") { + return wdcal_import_user_ics($calendar_id); + } elseif (isset($a->argv[3]) && $a->argv[3] > 0) { + if (isset($a->argv[4]) && $a->argv[4] == "edit") { + $o = ""; + if (isset($_REQUEST["save"])) { + check_form_security_token_redirectOnErr($a->get_baseurl() . "/dav/wdcal/", "caledit"); + $ret = wdcal_postEditPage($a->argv[3], $a->user["uid"], $a->timezone, $a->get_baseurl() . "/dav/wdcal/"); + if ($ret["ok"]) notice($ret["msg"]); + else info($ret["msg"]); + goaway($a->get_baseurl() . "/dav/wdcal/"); + } + $o .= wdcal_getEditPage($calendar_id, $a->argv[3]); + return $o; + } else { + return wdcal_getDetailPage($calendar_id, $a->argv[3]); + } + } else { + // @TODO Edit Calendar + } } + } else { + $server = dav_create_server(true, true, false); + $cals = dav_get_current_user_calendars($server, DAV_ACL_READ); + $x = wdcal_printCalendar($cals, array(), $a->get_baseurl() . "/dav/wdcal/feed/", "week", 0, 200); } - } else { - $cals = dav_getMyCals($a->user["uid"]); - $cals_show = array(); - foreach ($cals as $e) $cals_show[] = array("ns" => $e->namespace, "id" => $e->namespace_id, "displayname" => $e->displayname); - $x = wdcal_printCalendar($cals, $cals_show, $a->get_baseurl() . "/dav/wdcal/feed/", "week", 0, 200); } + } catch (DAVVersionMismatchException $e) { + $x = t("The current version of this plugin has not been set up correctly. Please contact the system administrator of your installation of friendica to fix this."); } return $x; } @@ -218,8 +180,8 @@ function dav_event_created_hook(&$a, &$b) { dav_include_files(); // @TODO Updating the cache instead of completely invalidating and rebuilding it - FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS); - FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE); + Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS); + Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE); } /** @@ -230,8 +192,8 @@ function dav_event_updated_hook(&$a, &$b) { dav_include_files(); // @TODO Updating the cache instead of completely invalidating and rebuilding it - FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS); - FriendicaVirtualCalSourceBackend::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE); + Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_CONTACTS); + Sabre_CalDAV_Backend_Friendica::invalidateCache($a->user["uid"], CALDAV_FRIENDICA_MINE); } /** @@ -248,6 +210,56 @@ function dav_profile_tabs_hook(&$a, &$b) ); } + +/** + * @param App $a + * @param object $b + */ +function dav_cron(&$a, &$b) +{ + dav_include_files(); + + $r = q("SELECT * FROM %s%snotifications WHERE `notified` = 0 AND `alert_date` <= NOW()", CALDAV_SQL_DB, CALDAV_SQL_PREFIX); + foreach ($r as $not) { + q("UPDATE %s%snotifications SET `notified` = 1 WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["id"]); + $event = q("SELECT * FROM %s%sjqcalendar WHERE `calendarobject_id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["calendarobject_id"]); + $calendar = q("SELECT * FROM %s%scalendars WHERE `id` = %d", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, $not["calendar_id"]); + $users = array(); + if (count($calendar) != 1 || count($event) == 0) continue; + switch ($calendar[0]["namespace"]) { + case CALDAV_NAMESPACE_PRIVATE: + $user = q("SELECT * FROM user WHERE `uid` = %d AND `blocked` = 0", $calendar[0]["namespace_id"]); + if (count($user) != 1) continue; + $users[] = $user[0]; + break; + } + switch ($not["action"]) { + case "email": + case "display": // @TODO implement "Display" + foreach ($users as $user) { + $find = array("%to%", "%event%", "%url%"); + $repl = array($user["username"], $event[0]["Summary"], $a->get_baseurl() . "/dav/wdcal/" . $calendar[0]["id"] . "/" . $not["calendarobject_id"] . "/"); + $text_text = str_replace($find, $repl, "Hi %to%!\n\nThe event \"%event%\" is about to begin:\n%url%"); + $text_html = str_replace($find, $repl, "Hi %to%!
\n
\nThe event \"%event%\" is about to begin:
\n%url%"); + $params = array( + 'fromName' => FRIENDICA_PLATFORM, + 'fromEmail' => t('noreply') . '@' . $a->get_hostname(), + 'replyTo' => t('noreply') . '@' . $a->get_hostname(), + 'toEmail' => $user["email"], + 'messageSubject' => t("Notification: " . $event[0]["Summary"]), + 'htmlVersion' => $text_html, + 'textVersion' => $text_text, + 'additionalMailHeader' => "", + ); + require_once('include/enotify.php'); + enotify::send($params); + } + break; + } + } +} + + /** * @param App $a * @param null|object $o @@ -256,6 +268,7 @@ function dav_plugin_admin_post(&$a = null, &$o = null) { check_form_security_token_redirectOnErr('/admin/plugins/dav', 'dav_admin_save'); + dav_include_files(); require_once(__DIR__ . "/database-init.inc.php"); if (isset($_REQUEST["install"])) { @@ -263,15 +276,23 @@ function dav_plugin_admin_post(&$a = null, &$o = null) if (count($errs) == 0) info(t('The database tables have been installed.') . EOL); else notice(t("An error occurred during the installation.") . EOL); } + if (isset($_REQUEST["upgrade"])) { + $errs = dav_upgrade_tables(); + if (count($errs) == 0) { + renderAllCalDavEntries(); + info(t('The database tables have been updated.') . EOL); + } + else notice(t("An error occurred during the update.") . EOL); + } } /** * @param App $a - * @param null|object $o + * @param string $o */ function dav_plugin_admin(&$a, &$o) { - + dav_include_files(); require_once(__DIR__ . "/database-init.inc.php"); $dbstatus = dav_check_tables(); @@ -286,18 +307,30 @@ function dav_plugin_admin(&$a, &$o) $o .= t('Installed'); break; case 1: - $o .= t('Upgrade needed') . "

"; + $o .= "" . t('Upgrade needed') . "
" . t("Please back up all calendar data (the tables beginning with dav_*) before proceeding. While all calendar events should be converted to the new database structure, it's always safe to have a backup. Below, you can have a look at the database-queries that will be made when pressing the 'update'-button.") . "

"; break; case -1: $o .= t('Not installed') . "

"; break; + case -2: + default: + $o .= t('Unknown') . "

" . t("Something really went wrong. I cannot recover from this state automatically, sorry. Please go to the database backend, back up the data, and delete all tables beginning with 'dav_' manually. Afterwards, this installation routine should be able to reinitialize the tables automatically."); + break; } $o .= "

"; $o .= "

" . t("Troubleshooting") . "

"; $o .= "

" . t("Manual creation of the database tables:") . "

"; $o .= "" . t("Show SQL-statements") . ""; } diff --git a/dav/virtual_cal_source_friendica.inc.php b/dav/virtual_cal_source_friendica.inc.php deleted file mode 100644 index 7434fbbc0..000000000 --- a/dav/virtual_cal_source_friendica.inc.php +++ /dev/null @@ -1,169 +0,0 @@ -setConfig('unique_id', $hostname); - - $v->setProperty('method', 'PUBLISH'); - $v->setProperty("x-wr-calname", "AnimexxCal"); - $v->setProperty("X-WR-CALDESC", "Animexx Calendar"); - $v->setProperty("X-WR-TIMEZONE", $timezone); - - if ($row["adjust"]) { - $start = datetime_convert('UTC', date_default_timezone_get(), $row["start"]); - $finish = datetime_convert('UTC', date_default_timezone_get(), $row["finish"]); - } else { - $start = $row["start"]; - $finish = $row["finish"]; - } - $allday = (strpos($start, "00:00:00") !== false && strpos($finish, "00:00:00") !== false); - - /* - - if ($allday) { - $dat = Datetime::createFromFormat("Y-m-d H:i:s", $finish_tmp); - $dat->sub(new DateInterval("P1D")); - $finish = datetime_convert("UTC", date_default_timezone_get(), $dat->format("Y-m-d H:i:s")); - var_dump($finish); - } - */ - - // 2012-06-29 - change to Friendica new event behaviour where summary is present and required, - // but use desc for older events where summary wasn't present or required (but desc was) - - $subject = (($row["summary"]) ? $row["summary"] : substr(preg_replace("/\[[^\]]*\]/", "", $row["desc"]), 0, 100)); - $description = (($row["desc"]) ? preg_replace("/\[[^\]]*\]/", "", $row["desc"]) : $row["summary"]); - - $vevent = dav_create_vevent(wdcal_mySql2icalTime($row["start"]), wdcal_mySql2icalTime($row["finish"]), false); - $vevent->setLocation(icalendar_sanitize_string($row["location"])); - $vevent->setSummary(icalendar_sanitize_string($subject)); - $vevent->setDescription(icalendar_sanitize_string($description)); - - $v->setComponent($vevent); - $ical = $v->createCalendar(); - return array( - "uid" => $uid, - "namespace" => CALDAV_NAMESPACE_FRIENDICA_NATIVE, - "namespace_id" => $namespace_id, - "date" => $row["edited"], - "data_uri" => "friendica-" . $namespace_id . "-" . $row["id"] . "@" . $hostname, - "data_subject" => $subject, - "data_location" => $row["location"], - "data_description" => $description, - "data_start" => $start, - "data_end" => $finish, - "data_allday" => $allday, - "data_type" => $row["type"], - "ical" => $ical, - "ical_size" => strlen($ical), - "ical_etag" => md5($ical), - ); - - } - - /** - * @static - * @param int $uid - * @param int $namespace_id - * @param string|int $date_from - * @param string|int $date_to - * @throws Sabre_DAV_Exception_NotFound - * @return array - */ - static public function getItemsByTime($uid = 0, $namespace_id = 0, $date_from = "", $date_to = "") - { - $uid = IntVal($uid); - $namespace_id = IntVal($namespace_id); - - switch ($namespace_id) { - case CALDAV_FRIENDICA_MINE: - $sql_where = " AND cid = 0"; - break; - case CALDAV_FRIENDICA_CONTACTS: - $sql_where = " AND cid > 0"; - break; - default: - throw new Sabre_DAV_Exception_NotFound(); - } - - if ($date_from != "") { - if (is_numeric($date_from)) $sql_where .= " AND `finish` >= '" . date("Y-m-d H:i:s", $date_from) . "'"; - else $sql_where .= " AND `finish` >= '" . dbesc($date_from) . "'"; - } - if ($date_to != "") { - if (is_numeric($date_to)) $sql_where .= " AND `start` <= '" . date("Y-m-d H:i:s", $date_to) . "'"; - else $sql_where .= " AND `start` <= '" . dbesc($date_to) . "'"; - } - - $ret = array(); - $a = get_app(); - $host = $a->get_hostname(); - - - $r = q("SELECT * FROM `event` WHERE `uid` = %d " . $sql_where . " ORDER BY `start`", $uid); - foreach ($r as $row) $ret[] =self::row2array($row, $a->timezone, $host, $uid, $namespace_id); - - return $ret; - } - - - /** - * @static - * @param int $uid - * @param string $uri - * @throws Sabre_DAV_Exception_NotFound - * @return array - */ - static public function getItemsByUri($uid = 0, $uri) - { - $x = explode("-", $uri); - if ($x[0] != "friendica") throw new Sabre_DAV_Exception_NotFound(); - - $namespace_id = IntVal($x[1]); - switch ($namespace_id) { - case CALDAV_FRIENDICA_MINE: - $sql_where = " AND cid = 0"; - break; - case CALDAV_FRIENDICA_CONTACTS: - $sql_where = " AND cid > 0"; - break; - default: - throw new Sabre_DAV_Exception_NotFound(); - } - - $a = get_app(); - $host = $a->get_hostname(); - - $r = q("SELECT * FROM `event` WHERE `uid` = %d AND id = %d " . $sql_where, $uid, IntVal($x[2])); - if (count($r) != 1) throw new Sabre_DAV_Exception_NotFound(); - $ret =self::row2array($r[0], $a->timezone, $host, $uid, $namespace_id); - - return $ret; - } - - -} \ No newline at end of file diff --git a/dav/wdcal.css b/dav/wdcal.css index c64f0cf45..18c25c585 100644 --- a/dav/wdcal.css +++ b/dav/wdcal.css @@ -10,4 +10,34 @@ div.colorPicker-picker { display: inline-block; } .ui-datepicker th { padding: 10px 2px; } .ui-datepicker select.ui-datepicker-year { min-width: 0; width: 50px !important; } #cal_start_time, #cal_end_time { width: 5em; margin-left: 1em; } -#cal_start_date, #cal_end_date { width: 6em;} \ No newline at end of file +#cal_start_date, #cal_end_date { width: 6em;} + + +label.block { + background: none repeat scroll 0 0 #CCCCCC; + border: 1px solid #EEEEEC; + box-shadow: 3px 3px 5px 0 #111111; + color: #111111; + display: inline-block; + font-size: small; + margin: 0 10px 1em 0; + padding: 3px 5px; + width: 38%; + vertical-align: top; +} + +label.plain { + background: none; + border: none; + box-shadow: none; + color: black; + display: inline; + margin: 0; + padding: 0; +} + +.rec_exceptions { display: inline-block; } +.rec_exceptions_holder { display: inline-block; } +.rec_exceptions .remover { } +#rec_until_count { width: 50px; } +#rec_until_date { width: 100px; } \ No newline at end of file diff --git a/dav/wdcal_cal_source_friendicaevents.inc.php b/dav/wdcal_cal_source_friendicaevents.inc.php deleted file mode 100644 index ed44bf965..000000000 --- a/dav/wdcal_cal_source_friendicaevents.inc.php +++ /dev/null @@ -1,164 +0,0 @@ -calendarDb->uid) return array("read"=> true, "write"=> false); - return array("read"=> false, "write"=> false); - } - - /** - * @param int $user - * @param string $item_uri - * @param string $recurrence_uri - * @param null|array $item_arr - * @return array - */ - public function getPermissionsItem($user, $item_uri, $recurrence_uri, $item_arr = null) - { - $cal_perm = $this->getPermissionsCalendar($user); - if (!$cal_perm["read"]) return array("read"=> false, "write"=> false); - return array("read"=> true, "write"=> false); - } - - - /** - * @param string $uri - * @param array $start - * @param array $end - * @param string $subject - * @param bool $allday - * @param string $description - * @param string $location - * @param null $color - * @param string $timezone - * @param bool $notification - * @param null $notification_type - * @param null $notification_value - * @throws Sabre_DAV_Exception_MethodNotAllowed - */ - public function updateItem($uri, $start, $end, $subject = "", $allday = false, $description = "", $location = "", $color = null, $timezone = "", $notification = true, $notification_type = null, $notification_value = null) - { - throw new Sabre_DAV_Exception_MethodNotAllowed(); - } - - /** - * @param array $start - * @param array $end - * @param string $subject - * @param bool $allday - * @param string $description - * @param string $location - * @param null $color - * @param string $timezone - * @param bool $notification - * @param null $notification_type - * @param null $notification_value - * @throws Sabre_DAV_Exception_MethodNotAllowed - * @return array|string - */ - public function addItem($start, $end, $subject, $allday = false, $description = "", $location = "", $color = null, - $timezone = "", $notification = true, $notification_type = null, $notification_value = null) - { - throw new Sabre_DAV_Exception_MethodNotAllowed(); - } - - /** - * @param array $row - * @return array - */ - private function virtualData2wdcal($row) { - $end = wdcal_mySql2PhpTime($row["data_end"]); - if ($row["data_allday"]) $end--; - $start = wdcal_mySql2PhpTime($row["data_start"]); - $a = get_app(); - $arr = array( - "uri" => $row["data_uri"], - "subject" => escape_tags($row["data_subject"]), - "start" => $start, - "end" => $end, - "is_allday" => ($row["data_allday"] == 1), - "is_moredays" => (date("Ymd", $start) != date("Ymd", $end)), - "is_recurring" => ($row["data_type"] == "birthday"), - "color" => "#ff0000", - "is_editable" => false, - "is_editable_quick" => false, - "location" => $row["data_location"], - "attendees" => '', - "has_notification" => false, - "url_detail" => $a->get_baseurl() . "/dav/wdcal/" . $row["data_uri"] . "/", - "url_edit" => "", - "special_type" => ($row["data_type"] == "birthday" ? "birthday" : ""), - ); - return $arr; - } - - /** - * @param string $sd - * @param string $ed - * @param string $base_path - * @return array - */ - public function listItemsByRange($sd, $ed, $base_path) - { - $usr_id = IntVal($this->calendarDb->uid); - - $evs = FriendicaVirtualCalSourceBackend::getItemsByTime($usr_id, $this->namespace_id, $sd, $ed); - $events = array(); - foreach ($evs as $row) $events[] = $this->virtualData2wdcal($row); - - return $events; - } - - /** - * @param string $uri - * @throws Sabre_DAV_Exception_MethodNotAllowed - * @return void - */ - public function removeItem($uri) { - throw new Sabre_DAV_Exception_MethodNotAllowed(); - } - - /** - * @param string $uri - * @return array - */ - public function getItemByUri($uri) - { - $usr_id = IntVal($this->calendarDb->uid); - $row = FriendicaVirtualCalSourceBackend::getItemsByUri($usr_id, $uri); - return $this->virtualData2wdcal($row); - } - - /** - * @param string $uri - * @return string - */ - public function getItemDetailRedirect($uri) { - $x = explode("@", $uri); - $y = explode("-", $x[0]); - $a = get_app(); - if (count($y) != 3) { - goaway($a->get_baseurl() . "/dav/wdcal/"); - killme(); - } - $a = get_app(); - $item = q("SELECT `id` FROM `item` WHERE `event-id` = %d AND `uid` = %d AND deleted = 0", IntVal($y[2]), $a->user["uid"]); - if (count($item) == 0) return "/events/"; - return "/display/" . $a->user["nickname"] . "/" . IntVal($item[0]["id"]); - } -}