Removed SabreDAV from baikal source.

This commit is contained in:
Evert Pot 2013-02-09 13:01:58 +00:00
parent ac21caca77
commit f65926f10c
252 changed files with 0 additions and 38367 deletions

View file

@ -1,999 +0,0 @@
1.8.0-stable (2012-11-08)
* The zip release ships with sabre/vobject 2.0.5.
* BC Break: Moved the entire codebase to PHP namespaces.
* BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks,
Principals) now has consistent naming conventions. There's a
BackendInterface, and an AbstractBackend class.
* BC Break: Changed a bunch of constructor signatures in the CalDAV
package, to reduce dependencies on the ACL package.
* BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method,
so sharees can figure out who is also on a shared calendar.
* Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support
for principal-property-search on any node.
* Added: Simple console script to fire up a fileserver in the current
directory using PHP 5.4's built-in webserver.
* Added: Sharee's can now also read out the list of invites for a shared
calendar.
* Added: The Proxy principal classes now both implement an interface, for
greater flexiblity.
1.7.2-stable (2012-11-08)
* The zip release ships with sabre/vobject 2.0.5.
* Added: ACL plugin advertises support for 'calendarserver-principal-
property-search'.
* Fixed: [#153] Allowing for relative http principals in iMip requests.
* Added: Support for cs:first-name and cs:last-name properties in sharing
invites.
* Fixed: Made a bunch of properties protected, where they were private
before.
* Added: Some non-standard properties for sharing to improve
compatibility.
* Fixed: some bugfixes in postgres sql script.
* Fixed: When requesting some properties using PROPFIND, they could show
up as both '200 Ok' and '403 Forbidden'.
* Fixed: calendar-proxy principals were not checked for deeper principal
membership than 1 level.
* Fixed: setGroupMemberSet argument now correctly receives relative
principal urls, instead of the absolute ones.
* Fixed: Server class will filter out any bonus properties if any extra
were returned. This means the implementor of the IProperty class can be
a bit lazier when implementing.
Note: bug numbers after this line refer to Google Code tickets. We're using
github now.
1.7.1-stable (2012-10-07)
* Fixed: include path problem in the migration script.
1.7.0-stable (2012-10-06)
* 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)
* 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.
* 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).
* Removed: Sabre.includes.php was deprecated, and is now removed.
* Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please
use Sabre_DAV_Server and check the examples in the examples/ directory.
* Changed: The Sabre_VObject library now spawned into it's own project!
The VObject library is still included in the SabreDAV zip package.
* Added: Experimental interfaces to allow implementation of caldav-sharing.
Note that no implementation is provided yet, just the api hooks.
* Added: Free-busy reporting compliant with the caldav-scheduling
standard. This allows iCal and other clients to fetch other users'
free-busy data.
* Added: Experimental NotificationSupport interface to add
caldav notifications.
* Added: VCF Export plugin. If enabled, it can generate an export of an
entire addressbook.
* Added: Support for PATCH using a SabreDAV format, to live-patch files.
* Added: Support for Prefer: return-minimal and Brief: t headers for
PROPFIND and PROPPATCH requests.
* 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.
* Changed: We are now using the composer autoloader.
* Changed: The CalDAV backend now all implement an interface.
* Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is
now the basis of every property class.
* Update: Caching results for principal lookups. This should cut down
queries and performance for a number of heavy requests.
* Update: ObjectTree caches lookups much more aggresively, which will help
especially speeding up a bunch of REPORT queries.
* Added: Support for the schedule-calendar-transp property.
* 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: Tons of timezone name mappings for Microsoft Exchange.
* Added: Support for an 'exception' event in the server class.
* 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: Issue 219: serialize() now reorders correctly.
* Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes
if there is whitespace in $dom.
* Fixed: Returning 409 Conflict instead of 500 when an attempt is made to
create a file as a child of something that's not a collection.
* Fixed: Issue 237: xml-encoding values in SabreDAV error responses.
* Fixed: Returning 403, instead of 501 when an unknown REPORT is
requested.
* Fixed: Postfixing slash on {DAV:}owner properties.
* Fixed: Several embarrassing spelling mistakes in docblocks.
1.6.5-stable (2012-10-04)
* Fixed: Workaround for line-ending bug OS X 10.8 addressbook has.
* Added: Ability to allow users to set SSL certificates for the Client
class. (Thanks schiesbn!).
* Fixed: Directory indexes with lots of nodes should be a lot faster.
* Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with
Sabre_DAV_Client, and no valid properties are returned.
* Fixed: Issue with filtering on alarms in tasks.
1.6.4-stable (2012-08-02)
* 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.
* Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the
calendar-query REPORT and free-busy calculations.
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.
* Fixed: Issue 205: Parsing an iCalendar 0-second date interval.
* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making
sure every iCalendar object only contains 1 component, and disallowing
vcards, forcing every component to have a UID.
* Fixed: Basic validation for vcards in the CardDAV plugin.
* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it
from updating events.
* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in
a recurring event could result in an endless loop.
* Fixed: All uri fields are now a maximum of 200 characters. The Bynari
outlook plugin used much longer strings so this should improve
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.
* Fixed: Recurrence rules of events are now taken into consideration when
doing time-range queries on alarms.
* Fixed: Added a workaround for the fact that php's DateInterval cannot
parse weeks and days at the same time.
* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's
version number from various outputs.
* Fixed: DTSTART values would be incorrect when expanding events.
* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY
BYDAY recurrences.
* Fixed: Issue 203: A problem with overridden events hitting the exact
date and time of a subsequent event in the recurrence set.
* Fixed: There was a problem with recurrence rules, for example the 5th
tuesday of the month, if this day did not exist.
* Added: New HTTP status codes from draft-nottingham-http-new-status-04.
1.6.1-stable (2012-03-05)
* Added: createFile and put() can now return an ETag.
* Added: Sending back an ETag on for operations on CardDAV backends. This
should help with OS X 10.6 Addressbook compatibility.
* Fixed: Fixed a bug where an infinite loop could occur in the recurrence
iterator if the recurrence was YEARLY, with a BYMONTH rule, and either
BYDAY or BYMONTHDAY match the first day of the month.
* Fixed: Events that are excluded using EXDATE are still counted in the
COUNT= parameter in the RRULE property.
* Added: Support for time-range filters on VALARM components.
* Fixed: Correctly filtering all-day events.
* Fixed: Sending back correct mimetypes from the browser plugin (thanks
Jürgen).
* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency.
* Fixed: Calendardata would be destroyed when performing a MOVE request.
1.6.0-stable (2012-02-22)
* BC Break: Now requires PHP 5.3
* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also
implement the getSupportedPrivilegeSet method. See website for details.
* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to
Sabre_VObject_DateTimeParser.
* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods:
'searchPrincipals' and 'updatePrincipal'.
* BC Break: Sabre_DAV_ILockable is removed and all related per-node
locking functionality.
* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of
Sabre_DAV_Exception_NotFound. The former will be removed in a later
version.
* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead.
* BC Break: Sabre_CalDAV_Server is now deprecated, check out the
documentation on how to setup a caldav server with just
Sabre_DAV_Server.
* BC Break: Default Principals PDO backend now needs a new field in the
'principals' table. See the website for details.
* Added: Ability to create new calendars and addressbooks from within the
browser plugin.
* Added: Browser plugin: icons for various nodes.
* Added: Support for FREEBUSY reports!
* Added: Support for creating principals with admin-level privileges.
* Added: Possibility to let server send out invitation emails on behalf of
CalDAV client, using Sabre_CalDAV_Schedule_IMip.
* Changed: beforeCreateFile event now passes data argument by reference.
* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now
be specified in Sabre_VObject_Property::$classMap.
* Added: Ability for plugins to tell the ACL plugin which principal
plugins are searchable.
* Added: [DAVACL] Per-node overriding of supported privileges. This allows
for custom privileges where needed.
* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin,
which allows for easy searching for principals, based on their
properties.
* Added: Sabre_VObject_Component::getComponents() to return a list of only
components and not properties.
* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV,
DAVACL, HTTP, VObject) as an alternative to the autoloader. This often
works much faster.
* Added: Support for the 'Me card', which allows Addressbook.app users
specify which vcard is their own.
* Added: Support for updating principal properties in the DAVACL principal
backends.
* Changed: Major refactoring in the calendar-query REPORT code. Should
make things more flexible and correct.
* Changed: The calendar-proxy-[read|write] principals will now only appear
in the tree, if they actually exist in the Principal backend. This should
reduce some problems people have been having with this.
* Changed: Sabre_VObject_Element_* classes are now renamed to
Sabre_VObject_Property. Old classes are retained for backwards
compatibility, but this will be removed in the future.
* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports
based on lists of events.
* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times
for recurring events.
* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT.
* Fixed: Issue 154: Encoding of VObject parameters with no value was
incorrect.
* Added: Support for {DAV:}acl-restrictions property from RFC3744.
* Added: The contentlength for calendar objects can now be supplied by a
CalDAV backend, allowing for more optimizations.
* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath.
* Fixed: {DAV:}getcontentlength may now be not specified.
* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths
from clients. This means that + will now be treated as a literal rather
than a space, and this should improve compatibility with the Windows
built-in client.
* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402
status codes.
* Added: Some mysql unique constraints to example files.
* Fixed: Correctly formatting HTTP dates.
* Fixed: Issue 94: Sending back Last-Modified header for 304 responses.
* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal,
Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar.
* Changed: Properties are now also automatically mapped to their
appropriate classes, if they are created using the add() or __set()
methods.
* Changed: Cloning VObject objects now clones the entire tree, rather than
just the default shallow copy.
* Added: Support for recurrence expansion in the CALDAV:calendar-multiget
and CALDAV:calendar-query REPORTS.
* Changed: CalDAV PDO backend now sorts calendars based on the internal
'calendarorder' field.
* Added: Issue 181: Carddav backends may no optionally not supply the carddata in
getCards, if etag and size are specified. This may speed up certain
requests.
* Added: More arguments to beforeWriteContent and beforeCreateFile (see
WritingPlugins wiki document).
* Added: Hook for iCalendar validation. This allows us to validate
iCalendar objects when they're uploaded. At the moment we're just
validating syntax.
* Added: VObject now support Windows Timezone names correctly (thanks
mrpace2).
* Added: If a timezonename could not be detected, we fall back on the
default PHP timezone.
* Added: Now a Composer package (thanks willdurand).
* Fixed: Support for \N as a newline character in the VObject reader.
* Added: afterWriteContent, afterCreateFile and afterUnbind events.
* Added: Postgresql example files. Not part of the unittests though, so
use at your own risk.
* Fixed: Issue 182: Removed backticks from sql queries, so it will work
with Postgres.
1.5.9-stable (2012-04-16)
* Fixed: Issue with parsing timezone identifiers that were surrounded by
quotes. (Fixes emClient compatibility).
1.5.8-stable (2012-02-22)
* Fixed: Issue 95: Another timezone parsing issue, this time in
calendar-query.
1.5.7-stable (2012-02-19)
* Fixed: VObject properties are now always encoded before components.
* Fixed: Sabre_DAVACL had issues with multiple levels of privilege
aggregration.
* Changed: Added 'GuessContentType' plugin to fileserver.php example.
* Fixed: The Browser plugin will now trigger the correct events when
creating files.
* Fixed: The ICSExportPlugin now considers ACL's.
* Added: Made it optional to supply carddata from an Addressbook backend
when requesting getCards. This can make some operations much faster, and
could result in much lower memory use.
* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file.
* Fixed: Issue 191: beforeUnlock was triggered twice.
1.5.6-stable (2012-01-07)
* Fixed: Issue 174: VObject could break UTF-8 characters.
* Fixed: pear package installation issues.
1.5.5-stable (2011-12-16)
* Fixed: CalDAV time-range filter workaround for recurring events.
* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple
files to be locked at the same time.
1.5.4-stable (2011-10-28)
* Fixed: GuessContentType plugin now supports mixed case file extensions.
* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME').
* Changed: Sending back HTTP 204 after a PUT request on an existing resource
instead of HTTP 200. This should fix Evolution CardDAV client
compatibility.
* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available.
* Added: All VObject elements now have a reference to their parent node.
1.5.3-stable (2011-09-28)
* Fixed: Sabre_DAV_Collection was missing from the includes file.
* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in
uppercase.
* Fixed: Issue 153: Support for files with mixed newline styles in
Sabre_VObject.
* Fixed: Issue 159: Automatically converting any vcard and icalendardata
to UTF-8.
* Added: Sabre_DAV_SimpleFile class for easy static file creation.
* Added: Issue 158: Support for the CARDDAV:supported-address-data
property.
1.5.2-stable (2011-09-21)
* Fixed: carddata and calendardata MySQL fields are now of type
'mediumblob'. 'TEXT' was too small sometimes to hold all the data.
* Fixed: {DAV:}supported-report-set is now correctly reporting the reports
for IAddressBook.
* Added: Sabre_VObject_Property::add() to add duplicate parameters to
properties.
* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject
interfaces.
* Fixed: Issue 140: Not returning 201 Created if an event cancelled the
creation of a file.
* Fixed: Issue 150: Faster URLUtil::encodePath() implementation.
* Fixed: Issue 144: Browser plugin could interfere with
TemporaryFileFilterPlugin if it was loaded first.
* Added: It's not possible to specify more 'alternate uris' in principal
backends.
1.5.1-stable (2011-08-24)
* Fixed: Issue 137. Hiding action interface in HTML browser for
non-collections.
* Fixed: addressbook-query is now correctly returned from the
{DAV:}supported-report-set property.
* Fixed: Issue 142: Bugs in groupwareserver.php example.
* Fixed: Issue 139: Rejecting PUT requests with Content-Range.
1.5.0-stable (2011-08-12)
* Added: CardDAV support.
* Added: An experimental WebDAV client.
* Added: MIME-Directory grouping support in the VObject library. This is
very useful for people attempting to parse vcards.
* BC Break: Adding parameters with the VObject libraries now overwrites
the previous parameter, rather than just add it. This makes more sense
for 99% of the cases.
* BC Break: lib/Sabre.autoload.php is now removed in favor of
lib/Sabre/autoload.php.
* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in
a future version. Use Sabre_DAV_Collection instead.
* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be
removed in a future version. Use Sabre_DAV_SimpleCollection instead.
* Fixed: Problem with overriding tablenames for the CalDAV backend.
* Added: Clark-notation parser to XML utility.
* Added: unset() support to VObject components.
* Fixed: Refactored CalDAV property fetching to be faster and simpler.
* Added: Central string-matcher for CalDAV and CardDAV plugins.
* Added: i;unicode-casemap support
* Fixed: VObject bug: wouldn't parse parameters if they weren't specified
in uppercase.
* Fixed: VObject bug: Parameters now behave more like Properties.
* Fixed: VObject bug: Parameters with no value are now correctly parsed.
* Changed: If calendars don't specify which components they allow, 'all'
components are assumed (e.g.: VEVENT, VTODO, VJOURNAL).
* Changed: Browser plugin now uses POST variable 'sabreAction' instead of
'action' to reduce the chance of collisions.
1.4.4-stable (2011-07-07)
* Fixed: Issue 131: Custom CalDAV backends could break in certain cases.
* Added: The option to override the default tablename all PDO backends
use. (Issue 60).
* Fixed: Issue 124: 'File' authentication backend now takes realm into
consideration.
* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This
allows users to update the {DAV:}group-member-set property.
* Added: Helper functions for DateTime-values in Sabre_VObject package.
* Added: VObject library can now automatically map iCalendar properties to
custom classes.
1.4.3-stable (2011-04-25)
* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug.
* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please
change the DATETIME field to an INT to ensure this field will work
correctly.
* Change: Sabre_DAV_Property_Principal is now renamed to
Sabre_DAVACL_Property_Principal.
* Added: API level support for ACL HTTP method.
* Fixed: Bug in serializing {DAV:}acl property.
* Added: deserializer for {DAV:}resourcetype property.
* Added: deserializer for {DAV:}acl property.
* Added: deserializer for {DAV:}principal property.
1.4.2-beta (2011-04-01)
* Added: It's not possible to disable listing of nodes that are denied
read access by ACL.
* Fixed: Changed a few properties in CalDAV classes from private to
protected.
* Fixed: Issue 119: Terrible things could happen when relying on
guessBaseUri, the server was running on the root of the domain and a user
tried to access a file ending in .php. This is a slight BC break.
* Fixed: Issue 118: Lock tokens in If headers without a uri should be
treated as the request uri, not 'all relevant uri's.
* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in
cases where there were similar named locked files in a directory.
1.4.1-beta (2011-02-26)
* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks.
* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when
running on apache, so a few workarounds were added.
* Change: Slightly changed CalDAV Backend API's, to allow for heavy
optimizations. This is non-bc breaking.
1.4.0-beta (2011-02-12)
* Added: Partly RFC3744 ACL support.
* Added: Calendar-delegation (caldav-proxy) support.
* BC break: In order to fix Issue 99, a new argument had to be added to
Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for
details.
* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be
removed in a later version. Use PDO or the new File class instead.
* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked
deprecated, and will be removed in a future version. Please use
Sabre_VObject instead.
* Removed: All principal-related functionality has been removed from the
Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin.
* Added: VObject library, for easy vcard/icalendar parsing using a natural
interface.
* Added: Ability to automatically generate full .ics feeds off calendars.
To use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your
calendar url.
* Added: Plugins can now specify a pluginname, for easy access using
Sabre_DAV_Server::getPlugin().
* Added: beforeGetProperties event.
* Added: updateProperties event.
* Added: Principal listings and calendar-access can now be done privately,
disallowing users from accessing or modifying other users' data.
* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If
it's an array with node-objects, a Root collection will automatically be
created, and the nodes are used as top-level children.
* Added: The principal base uri is now customizable. It used to be
hardcoded to 'principals/[user]'.
* Added: getSupportedReportSet method in ServerPlugin class. This allows
you to easily specify which reports you're implementing.
* Added: A '..' link to the HTML browser.
* Fixed: Issue 99: Locks on child elements were ignored when their parent
nodes were deleted.
* Fixed: Issue 90: lockdiscovery property and LOCK response now include a
{DAV}lockroot element.
* Fixed: Issue 96: support for 'default' collation in CalDAV text-match
filters.
* Fixed: Issue 102: Ensuring that copy and move with identical source and
destination uri's fails.
* Fixed: Issue 105: Supporting MKCALENDAR with no body.
* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util.
* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a
string)
* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the
root node.
* Added: Global way to easily supply new resourcetypes for certain node
classes.
* Fixed: Issue 59: Allowing the user to override the authentication realm
in Sabre_CalDAV_Server.
* Update: Issue 97: Looser time-range checking if there's a recurrence
rule in an event. This fixes 'missing recurring events'.
1.3.0 (2010-10-14)
* Added: childExists method to Sabre_DAV_ICollection. This is an api
break, so if you implement Sabre_DAV_ICollection directly, add the method.
* Changed: Almost all HTTP method implementations now take a uri argument,
including events. This allows for internal rerouting of certain calls.
If you have custom plugins, make sure they use this argument. If they
don't, they will likely still work, but it might get in the way of
future changes.
* Changed: All getETag methods MUST now surround the etag with
double-quotes. This was a mistake made in all previous SabreDAV
versions. If you don't do this, any If-Match, If-None-Match and If:
headers using Etags will work incorrectly. (Issue 85).
* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to
easily implement basic authentication.
* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden
instead.
* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection
instead.
* Added: Browser plugin now uses {DAV:}displayname if this property is
available.
* Added: Cache layer in the ObjectTree.
* Added: Tree classes now have a delete and getChildren method.
* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if
the date is an exact match.
* Fixed: Support for multiple ETags in If-Match and If-None-Match headers.
* Fixed: Improved baseUrl handling.
* Fixed: Issue 67: Non-seekable stream support in ::put()/::get().
* Fixed: Issue 65: Invalid dates are now ignored.
* Updated: Refactoring in Sabre_CalDAV to make everything a bit more
ledgable.
* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on
Windows.
* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to
'file size'-1.
1.2.4 (2010-07-13)
* Fixed: Issue 62: Guessing baseUrl fails when url contains a
query-string.
* Added: Apache configuration sample for CGI/FastCGI setups.
* Fixed: Issue 64: Only returning calendar-data when it was actually
requested.
1.2.3 (2010-06-26)
* Fixed: Issue 57: Supporting quotes around etags in If-Match and
If-None-Match
1.2.2 (2010-06-21)
* Updated: SabreDAV now attempts to guess the BaseURI if it's not set.
* Updated: Better compatibility with BitKinex
* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET
requests.
* Fixed: Issue with certain encoded paths in Browser Plugin.
1.2.1 (2010-06-07)
* Fixed: Issue 50, patch by Mattijs Hoitink.
* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter.
* Fixed: Issue 38, Allowing custom filters to be added to
TemporaryFileFilter.
* Fixed: Issue 53, ETags in the If: header were always failing. This
behaviour is now corrected.
* Added: Apache Authentication backend, in case authentication through
.htaccess is desired.
* Updated: Small improvements to example files.
1.2.0 (2010-05-24)
* Fixed: Browser plugin now displays international characters.
* Changed: More properties in CalDAV classes are now protected instead of
private.
1.2.0beta3 (2010-05-14)
* Fixed: Custom properties were not properly sent back for allprops
requests.
* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007.
* Changed: Removed CalDAV items from includes.php, and added a few missing
ones.
1.2.0beta2 (2010-05-04)
* Fixed: Issue 46: Fatal error for some non-existent nodes.
* Updated: some example sql to include email address.
* Added: 208 and 508 statuscodes from RFC5842.
* Added: Apache2 configuration examples
1.2.0beta1 (2010-04-28)
* Fixed: redundant namespace declaration in resourcetypes.
* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable
interface is used.
* Changed: using http://sabredav.org/ns for all custom xml properties.
* Added: email address property to principals.
* Updated: CalendarObject validation.
1.2.0alpha4 (2010-04-24)
* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since,
If-Unmodified-Since.
* Changed: Brand new build system. Functionality is split up between
Sabre, Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to
that a new non-pear package will be created with all this functionality
combined.
* Changed: Autoloader moved to Sabre/autoload.php.
* Changed: The Allow: header is now more accurate, with appropriate HTTP
methods per uri.
* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few
places where Sabre_DAV_Exception_NotImplemented was used.
1.2.0alpha3 (2010-04-20)
* Update: Complete rewrite of property updating. Now easier to use and
atomic.
* Fixed: Issue 16, automatically adding trailing / to baseUri.
* Added: text/plain is used for .txt files in GuessContentType plugin.
* Added: support for principal-property-search and
principal-search-property-set reports.
* Added: Issue 31: Hiding exception information by default. Can be turned
on with the Sabre_DAV_Server::$debugExceptions property.
1.2.0alpha2 (2010-04-08)
* Added: Calendars are now private and can only be read by the owner.
* Fixed: double namespace declaration in multistatus responses.
* Added: MySQL database dumps. MySQL is now also supported next to SQLite.
* Added: expand-properties REPORT from RFC 3253.
* Added: Sabre_DAV_Property_IHref interface for properties exposing urls.
* Added: Issue 25: Throwing error on broken Finder behaviour.
* Changed: Authentication backend is now aware of current user.
1.2.0alpha1 (2010-03-31)
* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded
special characters.
* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes
Office 2010 compatibility.
* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease
debugging.
* Fixed: Issue 36: Incorrect variable name, throwing error in some
requests.
* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales.
* Added: More unittests.
* Added: SabreDAV version to all error responses.
* Added: URLUtil class for decoding urls.
* Changed: Now using pear.sabredav.org pear channel.
* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method.
1.1.2-alpha (2010-03-18)
* Added: RFC5397 - current-user-principal support.
* Fixed: Issue 27: encoding entities in property responses.
* Added: naturalselection script now allows the user to specify a 'minimum
number of bytes' for deletion. This should reduce load due to less
crawling
* Added: Full support for the calendar-query report.
* Added: More unittests.
* Added: Support for complex property deserialization through the static
::unserialize() method.
* Added: Support for modifying calendar-component-set
* Fixed: Issue 29: Added TIMEOUT_INFINITE constant
1.1.1-alpha (2010-03-11)
* Added: RFC5689 - Extended MKCOL support.
* Fixed: Evolution support for CalDAV.
* Fixed: PDO-locks backend was pretty much completely broken. This is
100% unittested now.
* Added: support for ctags.
* Fixed: Comma's between HTTP methods in 'Allow' method.
* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a
datadirectory must always be specified from now on.
* Changed: Moved Sabre_DAV_Server::parseProps to
Sabre_DAV_XMLUtil::parseProperties.
* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection.
* Changed: Sabre_DAV_Exception_PermissionDenied is now
Sabre_DAV_Exception_Forbidden.
* Changed: Sabre_CalDAV_ICalendarCollection is removed.
* Added: Sabre_DAV_IExtendedCollection.
* Added: Many more unittests.
* Added: support for calendar-timezone property.
1.1.0-alpha (2010-03-01)
* Note: This version is forked from version 1.0.5, so release dates may be
out of order.
* Added: CalDAV - RFC 4791
* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for
this.
* Added: PDO authentication backend.
* Added: Example sql for auth, caldav, locks for sqlite.
* Added: Sabre_DAV_Browser_GuessContentType plugin
* Changed: Authentication plugin refactored, making it possible to
implement non-digest authentication.
* Fixed: Better error display in browser plugin.
* Added: Support for {DAV:}supported-report-set
* Added: XML utility class with helper functions for the WebDAV protocol.
* Added: Tons of unittests
* Added: PrincipalCollection and Principal classes
* Added: Sabre_DAV_Server::getProperties for easy property retrieval
* Changed: {DAV:}resourceType defaults to 0
* Changed: Any non-null resourceType now gets a / appended to the href
value. Before this was just for {DAV:}collection's, but this is now also
the case for for example {DAV:}principal.
* Changed: The Href property class can now optionally create non-relative
uri's.
* Changed: Sabre_HTTP_Response now returns false if headers are already
sent and header-methods are called.
* Fixed: Issue 19: HEAD requests on Collections
* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response
* Fixed: Issue 18: Doesn't work with Evolution Contacts
1.0.15-stable (2010-05-28)
* Added: Issue 31: Hiding exception information by default. Can be turned
on with the Sabre_DAV_Server::$debugExceptions property.
* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also
the case in the upcoming 1.2.0, so it will improve future compatibility.
1.0.14-stable (2010-04-15)
* Fixed: double namespace declaration in multistatus responses.
1.0.13-stable (2010-03-30)
* Fixed: Issue 40: Last references to basename/dirname
1.0.12-stable (2010-03-30)
* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded
special characters.
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
* Fixed: Issue 39: Basename fails on non-utf-8 locales.
* Added: More unittests.
* Added: SabreDAV version to all error responses.
* Added: URLUtil class for decoding urls.
* Updated: Now using pear.sabredav.org pear channel.
1.0.11-stable (2010-03-23)
* Non-public release. This release is identical to 1.0.10, but it is used
to test releasing packages to pear.sabredav.org.
1.0.10-stable (2010-03-22)
* Fixed: Issue 34: Invalid Lock-Token header response.
* Added: Issue 35: Addign SabreDAV version to HTTP OPTIONS responses.
1.0.9-stable (2010-03-19)
* Fixed: Issue 27: Entities not being encoded in PROPFIND responses.
* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant.
1.0.8-stable (2010-03-03)
* Fixed: Issue 21: typos causing errors
* Fixed: Issue 23: Comma's between methods in Allow header.
* Added: Sabre_DAV_ICollection interface, to aid in future compatibility.
* Added: Sabre_DAV_Exception_Forbidden exception. This will replace
Sabre_DAV_Exception_PermissionDenied in the future, and can already be
used to ensure future compatibility.
1.0.7-stable (2010-02-24)
* Fixed: Issue 19 regression for MS Office
1.0.6-stable (2010-02-23)
* Fixed: Issue 19: HEAD requests on Collections
1.0.5-stable (2010-01-22)
* Fixed: Fatal error when a malformed url was used for unlocking, in
conjuction with Sabre.autoload.php due to a incorrect filename.
* Fixed: Improved unittests and build system
1.0.4-stable (2010-01-11)
* Fixed: needed 2 different releases. One for googlecode and one for
pearfarm. This is to retain the old method to install SabreDAV until
pearfarm becomes the standard installation method.
1.0.3-stable (2010-01-11)
* Added: RFC4709 support (davmount)
* Added: 6 unittests
* Added: naturalselection. A tool to keep cache directories below a
specified theshold.
* Changed: Now using pearfarm.org channel server.
1.0.1-stable (2009-12-22)
* Fixed: Issue 15: typos in examples
* Fixed: Minor pear installation issues
1.0.0-stable (2009-11-02)
* Added: SimpleDirectory class. This class allows creating static
directory structures with ease.
* Changed: Custom complex properties and exceptions now get an instance of
Sabre_DAV_Server as their first argument in serialize()
* Changed: Href complex property now prepends server's baseUri
* Changed: delete before an overwriting copy/move is now handles by server
class instead of tree classes
* Changed: events must now explicitly return false to stop execution.
Before, execution would be stopped by anything loosely evaluating to
false.
* Changed: the getPropertiesForPath method now takes a different set of
arguments, and returns a different response. This allows plugin
developers to return statuses for properties other than 200 and 404. The
hrefs are now also always calculated relative to the baseUri, and not
the uri of the request.
* Changed: generatePropFindResponse is renamed to generateMultiStatus, and
now takes a list of properties similar to the response of
getPropertiesForPath. This was also needed to improve flexibility for
plugin development.
* Changed: Auth plugins are no longer included. They were not yet stable
quality, so they will probably be reintroduced in a later version.
* Changed: PROPPATCH also used generateMultiStatus now.
* Removed: unknownProperties event. This is replaced by the
afterGetProperties event, which should provide more flexibility.
* Fixed: Only calling getSize() on IFile instances in httpHead()
* Added: beforeBind event. This is invoked upon file or directory creation
* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an
existing resource.
* Added: beforeUnbind event. This is invoked right before deletion of any
resource.
* Added: afterGetProperties event. This event can be used to make
modifications to property responses.
* Added: beforeLock and beforeUnlock events.
* Added: afterBind event.
* Fixed: Copy and Move could fail in the root directory. This is now
fixed.
* Added: Plugins can now be retrieved by their classname. This is useful
for inter-plugin communication.
* Added: The Auth backend can now return usernames and user-id's.
* Added: The Auth backend got a getUsers method
* Added: Sabre_DAV_FSExt_Directory now returns quota info
0.12.1-beta (2009-09-11)
* Fixed: UNLOCK bug. Unlock didn't work at all
0.12-beta (2009-09-10)
* Updated: Browser plugin now shows multiple {DAV:}resourcetype values
if available.
* Added: Experimental PDO backend for Locks Manager
* Fixed: Sending Content-Length: 0 for every empty response. This
improves NGinx compatibility.
* Fixed: Last modification time is reported in UTC timezone. This improves
Finder compatibility.
0.11-beta (2009-08-11)
* Updated: Now in Beta
* Updated: Pear package no longer includes docs/ directory. These just
contained rfc's, which are publically available. This reduces the
package from ~800k to ~60k
* Added: generatePropfindResponse now takes a baseUri argument
* Added: ResourceType property can now contain multiple resourcetypes.
* Fixed: Issue 13.
0.10-alpha (2009-08-03)
* Added: Plugin to automatically map GET requests to non-files to
PROPFIND (Sabre_DAV_Browser_MapGetToPropFind). This should allow
easier debugging of complicated WebDAV setups.
* Added: Sabre_DAV_Property_Href class. For future use.
* Added: Ability to choose to use auth-int, auth or both for HTTP Digest
authentication. (Issue 11)
* Changed: Made more methods in Sabre_DAV_Server public.
* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests
to non-existent files. (Issue 12)
* Added: Central list of defined xml namespace prefixes. This can reduce
Bandwidth and legibility for xml bodies with user-defined namespaces.
* Added: now a PEAR-compatible package again, thanks to Michael Gauthier
* Changed: moved default copy and move logic from ObjectTree to Tree class
0.9-alpha (2009-07-21)
* Changed: Major refactoring, removed most of the logic from the Tree
objects. The Server class now directly works with the INode, IFile
and IDirectory objects. If you created your own Tree objects,
this will most likely break in this release.
* Changed: Moved all the Locking logic from the Tree and Server classes
into a separate plugin.
* Changed: TemporaryFileFilter is now a plugin.
* Added: Comes with an autoloader script. This can be used instead of
the includer script, and is preferred by some people.
* Added: AWS Authentication class.
* Added: simpleserversetup.py script. This will quickly get a fileserver
up and running.
* Added: When subscribing to events, it is now possible to supply a
priority. This is for example needed to ensure that the Authentication
Plugin is used before any other Plugin.
* Added: 22 new tests.
* Added: Users-manager plugin for .htdigest files. Experimental and
subject to change.
* Added: RFC 2324 HTTP 418 status code
* Fixed: Exclusive locks could in some cases be picked up as shared locks
* Fixed: Digest auth for non-apache servers had a bug (still not actually
tested this well).
0.8-alpha (2009-05-30)
* Changed: Renamed all exceptions! This is a compatibility break. Every
Exception now follows Sabre_DAV_Exception_FileNotFound convention
instead of Sabre_DAV_FileNotFoundException.
* Added: Browser plugin now allows uploading and creating directories
straight from the browser.
* Added: 12 more unittests
* Fixed: Locking bug, which became prevalent on Windows Vista.
* Fixed: Netdrive support
* Fixed: TemporaryFileFilter filtered out too many files. Fixed some
of the regexes.
* Fixed: Added README and ChangeLog to package
0.7-alpha (2009-03-29)
* Added: System to return complex properties from PROPFIND.
* Added: support for {DAV:}supportedlock.
* Added: support for {DAV:}lockdiscovery.
* Added: 6 new tests.
* Added: New plugin system.
* Added: Simple HTML directory plugin, for browser access.
* Added: Server class now sends back standard pre-condition error xml
bodies. This was new since RFC4918.
* Added: Sabre_DAV_Tree_Aggregrate, which can 'host' multiple Tree objects
into one.
* Added: simple basis for HTTP REPORT method. This method is not used yet,
but can be used by plugins to add reports.
* Changed: ->getSize is only called for files, no longer for collections.
r303
* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter
* Changed: Sabre_DAV_TemporaryFileFilter is now called
Sabre_DAV_Tree_TemporaryFileFilter.
* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server
class, and using a public property instead.
* Fixed: bug related to parsing proppatch and propfind requests. Didn't
show up in most clients, but it needed fixing regardless. (r255)
* Fixed: auth-int is now properly supported within HTTP Digest.
* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918
sec 8.2.
* Fixed: TemporaryFileFilter now lets through GET's if they actually
exist on the backend. (r274)
* FIxed: Some methods didn't get passed through in the FilterTree (r283).
* Fixed: LockManager is now slightly more complex, Tree classes slightly
less. (r287)
0.6-alpha (2009-02-16)
* Added: Now uses streams for files, instead of strings.
This means it won't require to hold entire files in memory, which can be
an issue if you're dealing with big files. Note that this breaks
compatibility for put() and createFile methods.
* Added: HTTP Digest Authentication helper class.
* Added: Support for HTTP Range header
* Added: Support for ETags within If: headers
* Added: The API can now return ETags and override the default Content-Type
* Added: starting with basic framework for unittesting, using PHPUnit.
* Added: 49 unittests.
* Added: Abstraction for the HTTP request.
* Updated: Using Clark Notation for tags in properties. This means tags
are serialized as {namespace}tagName instead of namespace#tagName
* Fixed: HTTP_BasicAuth class now works as expected.
* Fixed: DAV_Server uses / for a default baseUrl.
* Fixed: Last modification date is no longer ignored in PROPFIND.
* Fixed: PROPFIND now sends back information about the requestUri even
when "Depth: 1" is specified.
0.5-alpha (2009-01-14)
* Added: Added a very simple example for implementing a mapping to PHP
file streams. This should allow easy implementation of for example a
WebDAV to FTP proxy.
* Added: HTTP Basic Authentication helper class.
* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and
will be a start towards the creating of a testing framework.
* Updated: Backwards compatibility break: all require_once() statements
are removed
from all the files. It is now recommended to use autoloading of
classes, or just including lib/Sabre.includes.php. This fix was made
to allow easier integration into applications not using this standard
inclusion model.
* Updated: Better in-file documentation.
* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager.
* Updated: Fixes a shared-lock bug.
* Updated: Removed ?> from the bottom of each php file.
* Updated: Split up some operations from Sabre_DAV_Server to
Sabre_HTTP_Response.
* Fixed: examples are now actually included in the pear package.
0.4-alpha (2008-11-05)
* Passes all litmus tests!
* Added: more examples
* Added: Custom property support
* Added: Shared lock support
* Added: Depth support to locks
* Added: Locking on unmapped urls (non-existent nodes)
* Fixed: Advertising as WebDAV class 3 support
0.3-alpha (2008-06-29)
* Fully working in MS Windows clients.
* Added: temporary file filter: support for smultron files.
* Added: Phing build scripts
* Added: PEAR package
* Fixed: MOVE bug identified using finder.
* Fixed: Using gzuncompress instead of gzdecode in the temporary file
filter. This seems more common.
0.2-alpha (2008-05-27)
* Somewhat working in Windows clients
* Added: Working PROPPATCH method (doesn't support custom properties yet)
* Added: Temporary filename handling system
* Added: Sabre_DAV_IQuota to return quota information
* Added: PROPFIND now reads the request body and only supplies the
requested properties
0.1-alpha (2008-04-04)
* First release!
* Passes litmus: basic, http and copymove test.
* Fully working in Finder and DavFSv2
Project started: 2007-12-13

View file

@ -1,28 +0,0 @@
Copyright (C) 2007-2012 Rooftop Solutions.
Copyright (C) 2007-2009 FileMobile inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the SabreDAV nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,30 +0,0 @@
# 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.

View file

@ -1,155 +0,0 @@
<?php
namespace Sabre\CalDAV\Backend;
use Sabre\VObject;
use Sabre\CalDAV;
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* Checkout the BackendInterface for all the methods that must be implemented.
*
* @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 AbstractBackend implements BackendInterface {
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param mixed $calendarId
* @param array $mutations
* @return bool|array
*/
public function updateCalendar($calendarId, array $mutations) {
return false;
}
/**
* 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) {
$result = array();
$objects = $this->getCalendarObjects($calendarId);
$validator = new \Sabre\CalDAV\CalendarQueryValidator();
foreach($objects as $object) {
if ($this->validateFilterForObject($object, $filters)) {
$result[] = $object['uri'];
}
}
return $result;
}
/**
* This method validates if a filters (as passed to calendarQuery) matches
* the given object.
*
* @param array $object
* @param array $filters
* @return bool
*/
protected function validateFilterForObject(array $object, array $filters) {
// Unfortunately, setting the 'calendardata' here is optional. If
// it was excluded, we actually need another call to get this as
// well.
if (!isset($object['calendardata'])) {
$object = $this->getCalendarObject($object['calendarid'], $object['uri']);
}
$data = is_resource($object['calendardata'])?stream_get_contents($object['calendardata']):$object['calendardata'];
$vObject = VObject\Reader::read($data);
$validator = new CalDAV\CalendarQueryValidator();
return $validator->validate($vObject, $filters);
}
}

View file

@ -1,231 +0,0 @@
<?php
namespace Sabre\CalDAV\Backend;
/**
* Every CalDAV backend must at least implement this interface.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface BackendInterface {
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri);
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
public function createCalendar($principalUri,$calendarUri,array $properties);
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param mixed $calendarId
* @param array $mutations
* @return bool|array
*/
public function updateCalendar($calendarId, array $mutations);
/**
* Delete a calendar and all it's objects
*
* @param mixed $calendarId
* @return void
*/
public function deleteCalendar($calendarId);
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* ' "abcdef"')
* * calendarid - The calendarid as it was passed to this function.
* * size - The size of the calendar objects, in bytes.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param mixed $calendarId
* @return array
*/
public function getCalendarObjects($calendarId);
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* @param mixed $calendarId
* @param string $objectUri
* @return array
*/
public function getCalendarObject($calendarId,$objectUri);
/**
* Creates a new calendar object.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Updates an existing calendarobject, based on it's uri.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Deletes an existing calendar object.
*
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId,$objectUri);
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre\CalDAV\CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on either VEVENT or VTODO.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* @param mixed $calendarId
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters);
}

View file

@ -1,47 +0,0 @@
<?php
namespace Sabre\CalDAV\Backend;
/**
* Adds caldav notification support to a backend.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* Notifications are defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
*
* These notifications are basically a list of server-generated notifications
* displayed to the user. Users can dismiss notifications by deleting them.
*
* The primary usecase is to allow for calendar-sharing.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface NotificationSupport extends BackendInterface {
/**
* Returns a list of notifications for a given principal url.
*
* The returned array should only consist of implementations of
* \Sabre\CalDAV\Notifications\INotificationType.
*
* @param string $principalUri
* @return array
*/
public function getNotificationsForPrincipal($principalUri);
/**
* This deletes a specific notifcation.
*
* This may be called by a client once it deems a notification handled.
*
* @param string $principalUri
* @param \Sabre\CalDAV\Notifications\INotificationType $notification
* @return void
*/
public function deleteNotification($principalUri, \Sabre\CalDAV\Notifications\INotificationType $notification);
}

View file

@ -1,689 +0,0 @@
<?php
namespace Sabre\CalDAV\Backend;
use Sabre\VObject;
use Sabre\CalDAV;
use Sabre\DAV;
/**
* PDO CalDAV backend
*
* This backend is used to store calendar-data in a PDO database, such as
* sqlite or MySQL
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class PDO extends AbstractBackend {
/**
* We need to specify a max date, because we need to stop *somewhere*
*
* On 32 bit system the maximum for a signed integer is 2147483647, so
* MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
* in 2038-01-19 to avoid problems when the date is converted
* to a unix timestamp.
*/
const MAX_DATE = '2038-01-01';
/**
* pdo
*
* @var \PDO
*/
protected $pdo;
/**
* The table name that will be used for calendars
*
* @var string
*/
protected $calendarTableName;
/**
* The table name that will be used for calendar objects
*
* @var string
*/
protected $calendarObjectTableName;
/**
* List of CalDAV properties, and how they map to database fieldnames
* Add your own properties by simply adding on to this array.
*
* Note that only string-based properties are supported here.
*
* @var array
*/
public $propertyMap = array(
'{DAV:}displayname' => 'displayname',
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
);
/**
* Creates the backend
*
* @param \PDO $pdo
* @param string $calendarTableName
* @param string $calendarObjectTableName
*/
public function __construct(\PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects') {
$this->pdo = $pdo;
$this->calendarTableName = $calendarTableName;
$this->calendarObjectTableName = $calendarObjectTableName;
}
/**
* 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) {
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'uri';
$fields[] = 'ctag';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
// Making fields a comma-delimited list
$fields = implode(', ', $fields);
$stmt = $this->pdo->prepare("SELECT " . $fields . " FROM ".$this->calendarTableName." WHERE principaluri = ? ORDER BY calendarorder ASC");
$stmt->execute(array($principalUri));
$calendars = array();
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$components = array();
if ($row['components']) {
$components = explode(',',$row['components']);
}
$calendar = array(
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0',
'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Property\SupportedCalendarComponentSet($components),
'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Property\ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
);
foreach($this->propertyMap as $xmlName=>$dbName) {
$calendar[$xmlName] = $row[$dbName];
}
$calendars[] = $calendar;
}
return $calendars;
}
/**
* 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 string
*/
public function createCalendar($principalUri, $calendarUri, array $properties) {
$fieldNames = array(
'principaluri',
'uri',
'ctag',
'transparent',
);
$values = array(
':principaluri' => $principalUri,
':uri' => $calendarUri,
':ctag' => 1,
':transparent' => 0,
);
// Default value
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
$fieldNames[] = 'components';
if (!isset($properties[$sccs])) {
$values[':components'] = 'VEVENT,VTODO';
} else {
if (!($properties[$sccs] instanceof CalDAV\Property\SupportedCalendarComponentSet)) {
throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
}
$values[':components'] = implode(',',$properties[$sccs]->getValue());
}
$transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
if (isset($properties[$transp])) {
$values[':transparent'] = $properties[$transp]->getValue()==='transparent';
}
foreach($this->propertyMap as $xmlName=>$dbName) {
if (isset($properties[$xmlName])) {
$values[':' . $dbName] = $properties[$xmlName];
$fieldNames[] = $dbName;
}
}
$stmt = $this->pdo->prepare("INSERT INTO ".$this->calendarTableName." (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")");
$stmt->execute($values);
return $this->pdo->lastInsertId();
}
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param string $calendarId
* @param array $mutations
* @return bool|array
*/
public function updateCalendar($calendarId, array $mutations) {
$newValues = array();
$result = array(
200 => array(), // Ok
403 => array(), // Forbidden
424 => array(), // Failed Dependency
);
$hasError = false;
foreach($mutations as $propertyName=>$propertyValue) {
switch($propertyName) {
case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
$fieldName = 'transparent';
$newValues[$fieldName] = $propertyValue->getValue()==='transparent';
break;
default :
// Checking the property map
if (!isset($this->propertyMap[$propertyName])) {
// We don't know about this property.
$hasError = true;
$result[403][$propertyName] = null;
unset($mutations[$propertyName]);
continue;
}
$fieldName = $this->propertyMap[$propertyName];
$newValues[$fieldName] = $propertyValue;
}
}
// If there were any errors we need to fail the request
if ($hasError) {
// Properties has the remaining properties
foreach($mutations as $propertyName=>$propertyValue) {
$result[424][$propertyName] = null;
}
// Removing unused statuscodes for cleanliness
foreach($result as $status=>$properties) {
if (is_array($properties) && count($properties)===0) unset($result[$status]);
}
return $result;
}
// Success
// Now we're generating the sql query.
$valuesSql = array();
foreach($newValues as $fieldName=>$value) {
$valuesSql[] = $fieldName . ' = ?';
}
$valuesSql[] = 'ctag = ctag + 1';
$stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?");
$newValues['id'] = $calendarId;
$stmt->execute(array_values($newValues));
return true;
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
public function deleteCalendar($calendarId) {
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt->execute(array($calendarId));
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?');
$stmt->execute(array($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 string $calendarId
* @return array
*/
public function getCalendarObjects($calendarId) {
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt->execute(array($calendarId));
$result = array();
foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$result[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
);
}
return $result;
}
/**
* 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
* @return array
*/
public function getCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId, $objectUri));
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
if(!$row) return null;
return array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
);
}
/**
* 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) {
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence) VALUES (?,?,?,?,?,?,?,?,?)');
$stmt->execute(array(
$calendarId,
$objectUri,
$calendarData,
time(),
$extraData['etag'],
$extraData['size'],
$extraData['componentType'],
$extraData['firstOccurence'],
$extraData['lastOccurence'],
));
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
return '"' . $extraData['etag'] . '"';
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData) {
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarData,time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'] ,$calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
return '"' . $extraData['etag'] . '"';
}
/**
* Parses some information from calendar objects, used for optimized
* calendar-queries.
*
* Returns an array with the following keys:
* * etag
* * size
* * componentType
* * firstOccurence
* * lastOccurence
*
* @param string $calendarData
* @return array
*/
protected function getDenormalizedData($calendarData) {
$vObject = VObject\Reader::read($calendarData);
$componentType = null;
$component = null;
$firstOccurence = null;
$lastOccurence = null;
foreach($vObject->getComponents() as $component) {
if ($component->name!=='VTIMEZONE') {
$componentType = $component->name;
break;
}
}
if (!$componentType) {
throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
}
if ($componentType === 'VEVENT') {
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
// Finding the last occurence is a bit harder
if (!isset($component->RRULE)) {
if (isset($component->DTEND)) {
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
} elseif (isset($component->DURATION)) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->add(VObject\DateTimeParser::parse($component->DURATION->value));
$lastOccurence = $endDate->getTimeStamp();
} elseif ($component->DTSTART->getDateType()===VObject\Property\DateTime::DATE) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->modify('+1 day');
$lastOccurence = $endDate->getTimeStamp();
} else {
$lastOccurence = $firstOccurence;
}
} else {
$it = new VObject\RecurrenceIterator($vObject, (string)$component->UID);
$maxDate = new \DateTime(self::MAX_DATE);
if ($it->isInfinite()) {
$lastOccurence = $maxDate->getTimeStamp();
} else {
$end = $it->getDtEnd();
while($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
}
$lastOccurence = $end->getTimeStamp();
}
}
}
return array(
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
);
}
/**
* Deletes an existing calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* 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 a VEVENT.
*
* ..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.
*
* This specific implementation (for the PDO) backend optimizes filters on
* specific components, and VEVENT time-ranges.
*
* @param string $calendarId
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters) {
$result = array();
$validator = new \Sabre\CalDAV\CalendarQueryValidator();
$componentType = null;
$requirePostFilter = true;
$timeRange = null;
// if no filters were specified, we don't need to filter after a query
if (!$filters['prop-filters'] && !$filters['comp-filters']) {
$requirePostFilter = false;
}
// Figuring out if there's a component filter
if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
$componentType = $filters['comp-filters'][0]['name'];
// Checking if we need post-filters
if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
$requirePostFilter = false;
}
// There was a time-range filter
if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
$timeRange = $filters['comp-filters'][0]['time-range'];
// If start time OR the end time is not specified, we can do a
// 100% accurate mysql query.
if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
$requirePostFilter = false;
}
}
}
if ($requirePostFilter) {
$query = "SELECT uri, calendardata FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid";
} else {
$query = "SELECT uri FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid";
}
$values = array(
'calendarid' => $calendarId,
);
if ($componentType) {
$query.=" AND componenttype = :componenttype";
$values['componenttype'] = $componentType;
}
if ($timeRange && $timeRange['start']) {
$query.=" AND lastoccurence > :startdate";
$values['startdate'] = $timeRange['start']->getTimeStamp();
}
if ($timeRange && $timeRange['end']) {
$query.=" AND firstoccurence < :enddate";
$values['enddate'] = $timeRange['end']->getTimeStamp();
}
$stmt = $this->pdo->prepare($query);
$stmt->execute($values);
$result = array();
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
if ($requirePostFilter) {
if (!$this->validateFilterForObject($row, $filters)) {
continue;
}
}
$result[] = $row['uri'];
}
return $result;
}
}

View file

@ -1,243 +0,0 @@
<?php
namespace Sabre\CalDAV\Backend;
/**
* Adds support for sharing features to a CalDAV server.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* Early warning: Currently SabreDAV provides no implementation for this. This
* is, because in it's current state there is no elegant way to do this.
* The problem lies in the fact that a real CalDAV server with sharing support
* would first need email support (with invite notifications), and really also
* a browser-frontend that allows people to accept or reject these shares.
*
* In addition, the CalDAV backends are currently kept as independent as
* possible, and should not be aware of principals, email addresses or
* accounts.
*
* Adding an implementation for Sharing to standard-sabredav would contradict
* these goals, so for this reason this is currently not implemented, although
* it may very well in the future; but probably not before SabreDAV 2.0.
*
* The interface works however, so if you implement all this, and do it
* correctly sharing _will_ work. It's not particularly easy, and I _urge you_
* to make yourself acquainted with the following document first:
*
* https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* An overview
* ===========
*
* Implementing this interface will allow a user to share his or her calendars
* to other users. Effectively, when a calendar is shared the calendar will
* show up in both the Sharer's and Sharee's calendar-home root.
* This interface adds a few methods that ensure that this happens, and there
* are also a number of new requirements in the base-class you must now follow.
*
*
* How it works
* ============
*
* When a user shares a calendar, the addShare() method will be called with a
* list of sharees that are now added, and a list of sharees that have been
* removed.
* Removal is instant, but when a sharee is added the sharee first gets a
* chance to accept or reject the invitation for a share.
*
* After a share is accepted, the calendar will be returned from
* getUserCalendars for both the sharer, and the sharee.
*
* If the sharee deletes the calendar, only their share gets deleted. When the
* owner deletes a calendar, it will be removed for everybody.
*
*
* Notifications
* =============
*
* During all these sharing operations, a lot of notifications are sent back
* and forward.
*
* Whenever the list of sharees for a calendar has been changed (they have been
* added, removed or modified) all sharees should get a notification for this
* change.
* This notification is always represented by:
*
* Sabre\CalDAV\Notifications\Notification\Invite
*
* In the case of an invite, the sharee may reply with an 'accept' or
* 'decline'. These are always represented by:
*
* Sabre\CalDAV\Notifications\Notification\Invite
*
*
* Calendar access by sharees
* ==========================
*
* As mentioned earlier, shared calendars must now also be returned for
* getCalendarsForUser for sharees. A few things change though.
*
* The following properties must be specified:
*
* 1. {http://calendarserver.org/ns/}shared-url
*
* This property MUST contain the url to the original calendar, that is.. the
* path to the calendar from the owner.
*
* 2. {http://sabredav.org/ns}owner-principal
*
* This is a url to to the principal who is sharing the calendar.
*
* 3. {http://sabredav.org/ns}read-only
*
* This should be either 0 or 1, depending on if the user has read-only or
* read-write access to the calendar.
*
* Only when this is done, the calendar will correctly be marked as a calendar
* that's shared to him, thus allowing clients to display the correct interface
* and ACL enforcement.
*
* If a sharee deletes their calendar, only their instance of the calendar
* should be deleted, the original should still exists.
* Pretty much any 'dead' WebDAV properties on these shared calendars should be
* specific to a user. This means that if the displayname is changed by a
* sharee, the original is not affected. This is also true for:
* * The description
* * The color
* * The order
* * And any other dead properties.
*
* Properties like a ctag should not be different for multiple instances of the
* calendar.
*
* Lastly, objects *within* calendars should also have user-specific data. The
* two things that are user-specific are:
* * VALARM objects
* * The TRANSP property
*
* This _also_ implies that if a VALARM is deleted by a sharee for some event,
* this has no effect on the original VALARM.
*
* Understandably, the this last requirement is one of the hardest.
* Realisticly, I can see people ignoring this part of the spec, but that could
* cause a different set of issues.
*
*
* Publishing
* ==========
*
* When a user publishes a url, the server should generate a 'publish url'.
* This is a read-only url, anybody can use to consume the calendar feed.
*
* Calendars are in one of two states:
* * published
* * unpublished
*
* If a calendar is published, the following property should be returned
* for each calendar in getCalendarsForPrincipal.
*
* {http://calendarserver.org/ns/}publish-url
*
* This element should contain a {DAV:}href element, which points to the
* public url that does not require authentication. Unlike every other href,
* this url must be absolute.
*
* Ideally, the following property is always returned
*
* {http://calendarserver.org/ns/}pre-publish-url
*
* This property should contain the url that the calendar _would_ have, if it
* were to be published. iCal uses this to display the url, before the user
* will actually publish it.
*
*
* Selectively disabling publish or share feature
* ==============================================
*
* If Sabre\CalDAV\Property\AllowedSharingModes is returned from
* getCalendarsByUser, this allows the server to specify wether either sharing,
* or publishing is supported.
*
* This allows a client to determine in advance which features are available,
* and update the interface appropriately. If this property is not returned by
* the backend, the SharingPlugin automatically injects it and assumes both
* features are available.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface SharingSupport extends NotificationSupport {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* Note that if the calendar is currently marked as 'not shared' by and
* this method is called, the calendar should be 'upgraded' to a shared
* calendar.
*
* @param mixed $calendarId
* @param array $add
* @param array $remove
* @return void
*/
function updateShares($calendarId, array $add, array $remove);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* This method may be called by either the original instance of the
* calendar, as well as the shared instances. In the case of the shared
* instances, it is perfectly acceptable to return an empty array in case
* there are privacy concerns.
*
* @param mixed $calendarId
* @return array
*/
function getShares($calendarId);
/**
* This method is called when a user replied to a request to share.
*
* If the user chose to accept the share, this method should return the
* newly created calendar url.
*
* @param string href The sharee who is replying (often a mailto: address)
* @param int status One of the SharingPlugin::STATUS_* constants
* @param string $calendarUri The url to the calendar thats being shared
* @param string $inReplyTo The unique id this message is a response to
* @param string $summary A description of the reply
* @return null|string
*/
function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
/**
* Publishes a calendar
*
* @param mixed $calendarId
* @param bool $value
* @return void
*/
function setPublishStatus($calendarId, $value);
}

View file

@ -1,376 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* This object represents a CalDAV calendar.
*
* A calendar can contain multiple TODO and or Events. These are represented
* as \Sabre\CalDAV\CalendarObject objects.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Calendar implements ICalendar, DAV\IProperties, DAVACL\IACL {
/**
* This is an array with calendar information
*
* @var array
*/
protected $calendarInfo;
/**
* CalDAV backend
*
* @var Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Constructor
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->calendarInfo = $calendarInfo;
}
/**
* Returns the name of the calendar
*
* @return string
*/
public function getName() {
return $this->calendarInfo['uri'];
}
/**
* Updates properties such as the display name and description
*
* @param array $mutations
* @return array
*/
public function updateProperties($mutations) {
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations);
}
/**
* Returns the list of properties
*
* @param array $requestedProperties
* @return array
*/
public function getProperties($requestedProperties) {
$response = array();
foreach($requestedProperties as $prop) switch($prop) {
case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' :
$response[$prop] = new Property\SupportedCalendarData();
break;
case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' :
$response[$prop] = new Property\SupportedCollationSet();
break;
case '{DAV:}owner' :
$response[$prop] = new DAVACL\Property\Principal(DAVACL\Property\Principal::HREF,$this->calendarInfo['principaluri']);
break;
default :
if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop];
break;
}
return $response;
}
/**
* Returns a calendar object
*
* The contained calendar objects are for example Events or Todo's.
*
* @param string $name
* @return \Sabre\CalDAV\ICalendarObject
*/
public function getChild($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found');
$obj['acl'] = $this->getACL();
// Removing the irrelivant
foreach($obj['acl'] as $key=>$acl) {
if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') {
unset($obj['acl'][$key]);
}
}
return new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
/**
* Returns the full list of calendar objects
*
* @return array
*/
public function getChildren() {
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
$children = array();
foreach($objs as $obj) {
$obj['acl'] = $this->getACL();
// Removing the irrelivant
foreach($obj['acl'] as $key=>$acl) {
if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') {
unset($obj['acl'][$key]);
}
}
$children[] = new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
return $children;
}
/**
* Checks if a child-node exists.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj)
return false;
else
return true;
}
/**
* Creates a new directory
*
* We actually block this, as subdirectories are not allowed in calendars.
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');
}
/**
* Creates a new file
*
* The contents of the new file must be a valid ICalendar string.
*
* @param string $name
* @param resource $calendarData
* @return string|null
*/
public function createFile($name,$calendarData = null) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData);
}
/**
* Deletes the calendar.
*
* @return void
*/
public function delete() {
$this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
}
/**
* Renames the calendar. Note that most calendars use the
* {DAV:}displayname to display a name to display a name.
*
* @param string $newName
* @return void
*/
public function setName($newName) {
throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
*/
public function getLastModified() {
return null;
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->calendarInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
),
array(
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
'principal' => '{DAV:}authenticated',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
// We need to inject 'read-free-busy' in the tree, aggregated under
// {DAV:}read.
foreach($default['aggregates'] as &$agg) {
if ($agg['privilege'] !== '{DAV:}read') continue;
$agg['aggregates'][] = array(
'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
);
}
return $default;
}
/**
* 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.
*
* @param array $filters
* @return array
*/
public function calendarQuery(array $filters) {
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
}
}

View file

@ -1,279 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {
/**
* Sabre\CalDAV\Backend\BackendInterface
*
* @var Sabre\CalDAV\Backend\AbstractBackend
*/
protected $caldavBackend;
/**
* Array with information about this CalendarObject
*
* @var array
*/
protected $objectData;
/**
* Array with information about the containing calendar
*
* @var array
*/
protected $calendarInfo;
/**
* Constructor
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
* @param array $objectData
*/
public function __construct(Backend\BackendInterface $caldavBackend,array $calendarInfo,array $objectData) {
$this->caldavBackend = $caldavBackend;
if (!isset($objectData['calendarid'])) {
throw new \InvalidArgumentException('The objectData argument must contain a \'calendarid\' property');
}
if (!isset($objectData['uri'])) {
throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
}
$this->calendarInfo = $calendarInfo;
$this->objectData = $objectData;
}
/**
* Returns the uri for this object
*
* @return string
*/
public function getName() {
return $this->objectData['uri'];
}
/**
* Returns the ICalendar-formatted object
*
* @return string
*/
public function get() {
// Pre-populating the 'calendardata' is optional, if we don't have it
// already we fetch it from the backend.
if (!isset($this->objectData['calendardata'])) {
$this->objectData = $this->caldavBackend->getCalendarObject($this->objectData['calendarid'], $this->objectData['uri']);
}
return $this->objectData['calendardata'];
}
/**
* Updates the ICalendar-formatted object
*
* @param string|resource $calendarData
* @return string
*/
public function put($calendarData) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
$etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData);
$this->objectData['calendardata'] = $calendarData;
$this->objectData['etag'] = $etag;
return $etag;
}
/**
* Deletes the calendar object
*
* @return void
*/
public function delete() {
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'],$this->objectData['uri']);
}
/**
* Returns the mime content-type
*
* @return string
*/
public function getContentType() {
return 'text/calendar; charset=utf-8';
}
/**
* Returns an ETag for this object.
*
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* @return string
*/
public function getETag() {
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
} else {
return '"' . md5($this->get()). '"';
}
}
/**
* Returns the last modification date as a unix timestamp
*
* @return int
*/
public function getLastModified() {
return $this->objectData['lastmodified'];
}
/**
* Returns the size of this object in bytes
*
* @return int
*/
public function getSize() {
if (array_key_exists('size',$this->objectData)) {
return $this->objectData['size'];
} else {
return strlen($this->get());
}
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->calendarInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
// An alternative acl may be specified in the object data.
if (isset($this->objectData['acl'])) {
return $this->objectData['acl'];
}
// The default ACL
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,298 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\VObject;
/**
* Parses the calendar-query report request body.
*
* Whoever designed this format, and the CalDAV equivalent even more so,
* has no feel for design.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class CalendarQueryParser {
/**
* List of requested properties the client wanted
*
* @var array
*/
public $requestedProperties;
/**
* List of property/component filters.
*
* @var array
*/
public $filters;
/**
* This property will contain null if CALDAV:expand was not specified,
* otherwise it will contain an array with 2 elements (start, end). Each
* contain a DateTime object.
*
* If expand is specified, recurring calendar objects are to be expanded
* into their individual components, and only the components that fall
* within the specified time-range are to be returned.
*
* For more details, see rfc4791, section 9.6.5.
*
* @var null|array
*/
public $expand;
/**
* DOM Document
*
* @var DOMDocument
*/
protected $dom;
/**
* DOM XPath object
*
* @var DOMXPath
*/
protected $xpath;
/**
* Creates the parser
*
* @param \DOMDocument $dom
*/
public function __construct(\DOMDocument $dom) {
$this->dom = $dom;
$this->xpath = new \DOMXPath($dom);
$this->xpath->registerNameSpace('cal',Plugin::NS_CALDAV);
$this->xpath->registerNameSpace('dav','DAV:');
}
/**
* Parses the request.
*
* @return void
*/
public function parse() {
$filterNode = null;
$filter = $this->xpath->query('/cal:calendar-query/cal:filter');
if ($filter->length !== 1) {
throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed');
}
$compFilters = $this->parseCompFilters($filter->item(0));
if (count($compFilters)!==1) {
throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.');
}
$this->filters = $compFilters[0];
$this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild));
$expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
if ($expand->length>0) {
$this->expand = $this->parseExpand($expand->item(0));
}
}
/**
* Parses all the 'comp-filter' elements from a node
*
* @param \DOMElement $parentNode
* @return array
*/
protected function parseCompFilters(\DOMElement $parentNode) {
$compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
$result = array();
for($ii=0; $ii < $compFilterNodes->length; $ii++) {
$compFilterNode = $compFilterNodes->item($ii);
$compFilter = array();
$compFilter['name'] = $compFilterNode->getAttribute('name');
$compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
$compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
$compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
$compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
'VEVENT',
'VTODO',
'VJOURNAL',
'VFREEBUSY',
'VALARM',
))) {
throw new \Sabre\DAV\Exception\BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
};
$result[] = $compFilter;
}
return $result;
}
/**
* Parses all the prop-filter elements from a node
*
* @param \DOMElement $parentNode
* @return array
*/
protected function parsePropFilters(\DOMElement $parentNode) {
$propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
$result = array();
for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
$propFilterNode = $propFilterNodes->item($ii);
$propFilter = array();
$propFilter['name'] = $propFilterNode->getAttribute('name');
$propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
$propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
$propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
$propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
$result[] = $propFilter;
}
return $result;
}
/**
* Parses the param-filter element
*
* @param \DOMElement $parentNode
* @return array
*/
protected function parseParamFilters(\DOMElement $parentNode) {
$paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
$result = array();
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
$paramFilterNode = $paramFilterNodes->item($ii);
$paramFilter = array();
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
$paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
$paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
$result[] = $paramFilter;
}
return $result;
}
/**
* Parses the text-match element
*
* @param \DOMElement $parentNode
* @return array|null
*/
protected function parseTextMatch(\DOMElement $parentNode) {
$textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
if ($textMatchNodes->length === 0)
return null;
$textMatchNode = $textMatchNodes->item(0);
$negateCondition = $textMatchNode->getAttribute('negate-condition');
$negateCondition = $negateCondition==='yes';
$collation = $textMatchNode->getAttribute('collation');
if (!$collation) $collation = 'i;ascii-casemap';
return array(
'negate-condition' => $negateCondition,
'collation' => $collation,
'value' => $textMatchNode->nodeValue
);
}
/**
* Parses the time-range element
*
* @param \DOMElement $parentNode
* @return array|null
*/
protected function parseTimeRange(\DOMElement $parentNode) {
$timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
if ($timeRangeNodes->length === 0) {
return null;
}
$timeRangeNode = $timeRangeNodes->item(0);
if ($start = $timeRangeNode->getAttribute('start')) {
$start = VObject\DateTimeParser::parseDateTime($start);
} else {
$start = null;
}
if ($end = $timeRangeNode->getAttribute('end')) {
$end = VObject\DateTimeParser::parseDateTime($end);
} else {
$end = null;
}
if (!is_null($start) && !is_null($end) && $end <= $start) {
throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the time-range filter');
}
return array(
'start' => $start,
'end' => $end,
);
}
/**
* Parses the CALDAV:expand element
*
* @param \DOMElement $parentNode
* @return void
*/
protected function parseExpand(\DOMElement $parentNode) {
$start = $parentNode->getAttribute('start');
if(!$start) {
throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element');
}
$start = VObject\DateTimeParser::parseDateTime($start);
$end = $parentNode->getAttribute('end');
if(!$end) {
throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element');
}
$end = VObject\DateTimeParser::parseDateTime($end);
if ($end <= $start) {
throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.');
}
return array(
'start' => $start,
'end' => $end,
);
}
}

View file

@ -1,373 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\VObject;
use DateTime;
/**
* CalendarQuery Validator
*
* This class is responsible for checking if an iCalendar object matches a set
* of filters. The main function to do this is 'validate'.
*
* This is used to determine which icalendar objects should be returned for a
* calendar-query REPORT request.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class CalendarQueryValidator {
/**
* Verify if a list of filters applies to the calendar data object
*
* The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
*
* @param VObject\Component $vObject
* @param array $filters
* @return bool
*/
public function validate(VObject\Component $vObject,array $filters) {
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
if ($vObject->name !== $filters['name']) {
return false;
}
return
$this->validateCompFilters($vObject, $filters['comp-filters']) &&
$this->validatePropFilters($vObject, $filters['prop-filters']);
}
/**
* This method checks the validity of comp-filters.
*
* A list of comp-filters needs to be specified. Also the parent of the
* component we're checking should be specified, not the component to check
* itself.
*
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validateCompFilters(VObject\Component $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['comp-filters'] && !$filter['prop-filters']) {
continue;
}
// If there are sub-filters, we need to find at least one component
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
if (
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
// We had a match, so this comp-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-comp-filters or
// sub-prop-filters and there was no match. This means this filter
// needs to return false.
return false;
}
// If we got here it means we got through all comp-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of prop-filters.
*
* A list of prop-filters needs to be specified. Also the parent of the
* property we're checking should be specified, not the property to check
* itself.
*
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validatePropFilters(VObject\Component $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['param-filters'] && !$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one property
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
if(
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
) {
// We had a match, so this prop-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-param-filters or
// text-match filters and there was no match. This means the
// filter needs to return false.
return false;
}
// If we got here it means we got through all prop-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of param-filters.
*
* A list of param-filters needs to be specified. Also the parent of the
* parameter we're checking should be specified, not the parameter to check
* itself.
*
* @param VObject\Property $parent
* @param array $filters
* @return bool
*/
protected function validateParamFilters(VObject\Property $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent[$filter['name']]);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (!$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach($parent[$filter['name']] as $subParam) {
if($this->validateTextMatch($subParam,$filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
// If we got here it means there was a text-match filter and there
// were no matches. This means the filter needs to return false.
return false;
}
// If we got here it means we got through all param-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of a text-match.
*
* A single text-match should be specified as well as the specific property
* or parameter we need to validate.
*
* @param VObject\Node $parent
* @param array $textMatch
* @return bool
*/
protected function validateTextMatch(VObject\Node $parent, array $textMatch) {
$value = (string)$parent;
$isMatching = \Sabre\DAV\StringUtil::textMatch($value, $textMatch['value'], $textMatch['collation']);
return ($textMatch['negate-condition'] xor $isMatching);
}
/**
* Validates if a component matches the given time range.
*
* This is all based on the rules specified in rfc4791, which are quite
* complex.
*
* @param VObject\Node $component
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
protected function validateTimeRange(VObject\Node $component, $start, $end) {
if (is_null($start)) {
$start = new DateTime('1900-01-01');
}
if (is_null($end)) {
$end = new DateTime('3000-01-01');
}
switch($component->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
return $component->isInTimeRange($start, $end);
case 'VALARM' :
// If the valarm is wrapped in a recurring event, we need to
// expand the recursions, and validate each.
//
// Our datamodel doesn't easily allow us to do this straight
// in the VALARM component code, so this is a hack, and an
// expensive one too.
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
// Fire up the iterator!
$it = new VObject\RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
while($it->valid()) {
$expandedEvent = $it->getEventObject();
// We need to check from these expanded alarms, which
// one is the first to trigger. Based on this, we can
// determine if we can 'give up' expanding events.
$firstAlarm = null;
if ($expandedEvent->VALARM !== null) {
foreach($expandedEvent->VALARM as $expandedAlarm) {
$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 (is_null($firstAlarm)) {
// No alarm was found.
//
// Or technically: No alarm that will change for
// every instance of the recurrence was found,
// which means we can assume there was no match.
return false;
}
if ($firstAlarm > $end) {
return false;
}
$it->next();
}
return false;
} else {
return $component->isInTimeRange($start, $end);
}
case 'VFREEBUSY' :
throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
case 'COMPLETED' :
case 'CREATED' :
case 'DTEND' :
case 'DTSTAMP' :
case 'DTSTART' :
case 'DUE' :
case 'LAST-MODIFIED' :
return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
default :
throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
}
}
}

View file

@ -1,77 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAVACL\PrincipalBackend;
/**
* Calendars collection
*
* This object is responsible for generating a list of calendar-homes for each
* user.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class CalendarRootNode extends \Sabre\DAVACL\AbstractPrincipalCollection {
/**
* CalDAV backend
*
* @var Sabre\CalDAV\Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Constructor
*
* This constructor needs both an authentication and a caldav backend.
*
* By default this class will show a list of calendar collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
* @param PrincipalBackend\BackendInterface $principalBackend
* @param Backend\BackendInterface $caldavBackend
* @param string $principalPrefix
*/
public function __construct(PrincipalBackend\BackendInterface $principalBackend,Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;
}
/**
* Returns the nodename
*
* We're overriding this, because the default will be the 'principalPrefix',
* and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT
*
* @return string
*/
public function getName() {
return Plugin::CALENDAR_ROOT;
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principal
* @return \Sabre\DAV\INode
*/
public function getChildForPrincipal(array $principal) {
return new UserCalendars($this->caldavBackend, $principal);
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace Sabre\CalDAV\Exception;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* InvalidComponentType
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class InvalidComponentType extends DAV\Exception\Forbidden {
/**
* Adds in extra information in the xml response.
*
* This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode) {
$doc = $errorNode->ownerDocument;
$np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV,'cal:supported-calendar-component');
$errorNode->appendChild($np);
}
}

View file

@ -1,142 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\VObject;
/**
* ICS Exporter
*
* This plugin adds the ability to export entire calendars as .ics files.
* This is useful for clients that don't support CalDAV yet. They often do
* support ics files.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ICSExportPlugin extends DAV\ServerPlugin {
/**
* Reference to Server class
*
* @var \Sabre\DAV\Server
*/
protected $server;
/**
* Initializes the plugin and registers event handlers
*
* @param \Sabre\DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90);
}
/**
* 'beforeMethod' event handles. This event handles intercepts GET requests ending
* with ?export
*
* @param string $method
* @param string $uri
* @return bool
*/
public function beforeMethod($method, $uri) {
if ($method!='GET') return;
if ($this->server->httpRequest->getQueryString()!='export') return;
// splitting uri
list($uri) = explode('?',$uri,2);
$node = $this->server->tree->getNodeForPath($uri);
if (!($node instanceof Calendar)) return;
// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
}
$this->server->httpResponse->setHeader('Content-Type','text/calendar');
$this->server->httpResponse->sendStatus(200);
$nodes = $this->server->getPropertiesForPath($uri, array(
'{' . Plugin::NS_CALDAV . '}calendar-data',
),1);
$this->server->httpResponse->sendBody($this->generateICS($nodes));
// Returning false to break the event chain
return false;
}
/**
* Merges all calendar objects, and builds one big ics export
*
* @param array $nodes
* @return string
*/
public function generateICS(array $nodes) {
$calendar = new VObject\Component('vcalendar');
$calendar->version = '2.0';
if (DAV\Server::$exposeVersion) {
$calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
} else {
$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
}
$calendar->calscale = 'GREGORIAN';
$collectedTimezones = array();
$timezones = array();
$objects = array();
foreach($nodes as $node) {
if (!isset($node[200]['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
continue;
}
$nodeData = $node[200]['{' . Plugin::NS_CALDAV . '}calendar-data'];
$nodeComp = VObject\Reader::read($nodeData);
foreach($nodeComp->children() as $child) {
switch($child->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
$objects[] = $child;
break;
// VTIMEZONE is special, because we need to filter out the duplicates
case 'VTIMEZONE' :
// Naively just checking tzid.
if (in_array((string)$child->TZID, $collectedTimezones)) continue;
$timezones[] = $child;
$collectedTimezones[] = $child->TZID;
break;
}
}
}
foreach($timezones as $tz) $calendar->add($tz);
foreach($objects as $obj) $calendar->add($obj);
return $calendar->serialize();
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
* Calendar interface
*
* Implement this interface to allow a node to be recognized as an calendar.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface ICalendar extends DAV\ICollection {
/**
* 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.
*
* @param array $filters
* @return array
*/
public function calendarQuery(array $filters);
}

View file

@ -1,21 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
* CalendarObject interface
*
* Extend the ICalendarObject interface to allow your custom nodes to be picked up as
* CalendarObjects.
*
* Calendar objects are resources such as Events, Todo's or Journals.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface ICalendarObject extends DAV\IFile {
}

View file

@ -1,48 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that can be shared with other users.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IShareableCalendar extends ICalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
}

View file

@ -1,36 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that is shared by a different user.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface ISharedCalendar extends ICalendar {
/**
* This method should return the url of the owners' copy of the shared
* calendar.
*
* @return string
*/
function getSharedUrl();
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
}

View file

@ -1,173 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\DAVACL;
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
/**
* The notification backend
*
* @var Sabre\CalDAV\Backend\NotificationSupport
*/
protected $caldavBackend;
/**
* Principal uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param CalDAV\Backend\NotificationSupport $caldavBackend
* @param string $principalUri
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns all notifications for a principal
*
* @return array
*/
public function getChildren() {
$children = array();
$notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
foreach($notifications as $notification) {
$children[] = new Node(
$this->caldavBackend,
$this->principalUri,
$notification
);
}
return $children;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
return 'notifications';
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
),
array(
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
)
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's as an array argument.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre\CalDAV\Notifications\INode nodes as
* its children.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface ICollection extends DAV\ICollection {
}

View file

@ -1,38 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications;
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
*
* For a complete example, check out the Notification class, which contains
* some helper functions.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface INode {
/**
* This method must return an xml element, using the
* Sabre\CalDAV\Notifications\INotificationType classes.
*
* @return INotificationType
*/
function getNotificationType();
/**
* Returns the etag for the notification.
*
* The etag must be surrounded by litteral double-quotes.
*
* @return string
*/
function getETag();
}

View file

@ -1,44 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
/**
* This interface reflects a single notification type.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface INotificationType extends DAV\PropertyInterface {
/**
* This method serializes the entire notification, as it is used in the
* response body.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
function serializeBody(DAV\Server $server, \DOMElement $node);
/**
* Returns a unique id for this notification
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
function getId();
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
function getETag();
}

View file

@ -1,192 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\DAVACL;
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Node extends DAV\File implements INode, DAVACL\IACL {
/**
* The notification backend
*
* @var Sabre\CalDAV\Backend\NotificationSupport
*/
protected $caldavBackend;
/**
* The actual notification
*
* @var Sabre\CalDAV\Notifications\INotificationType
*/
protected $notification;
/**
* Owner principal of the notification
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param CalDAV\Backend\NotificationSupport $caldavBackend
* @param string $principalUri
* @param CalDAV\Notifications\INotificationType $notification
*/
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, INotificationType $notification) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
$this->notification = $notification;
}
/**
* Returns the path name for this notification
*
* @return id
*/
public function getName() {
return $this->notification->getId() . '.xml';
}
/**
* Returns the etag for the notification.
*
* The etag must be surrounded by litteral double-quotes.
*
* @return string
*/
public function getETag() {
return $this->notification->getETag();
}
/**
* This method must return an xml element, using the
* Sabre\CalDAV\Notifications\INotificationType classes.
*
* @return INotificationType
*/
public function getNotificationType() {
return $this->notification;
}
/**
* Deletes this notification
*
* @return void
*/
public function delete() {
$this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'principal' => $this->getOwner(),
'privilege' => '{DAV:}read',
'protected' => true,
),
array(
'principal' => $this->getOwner(),
'privilege' => '{DAV:}write',
'protected' => true,
)
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's as an array argument.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,324 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications\Notification;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* This class represents the cs:invite-notification notification element.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Invite extends DAV\Property implements CalDAV\Notifications\INotificationType {
/**
* A unique id for the message
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification
*
* @var DateTime
*/
protected $dtStamp;
/**
* A url to the recipient of the notification. This can be an email
* address (mailto:), or a principal url.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_* constants.
*
* @var int
*/
protected $type;
/**
* True if access to a calendar is read-only.
*
* @var bool
*/
protected $readOnly;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* Url to the sharer of the calendar
*
* @var string
*/
protected $organizer;
/**
* The name of the sharer.
*
* @var string
*/
protected $commonName;
/**
* The name of the sharer.
*
* @var string
*/
protected $firstName;
/**
* The name of the sharer.
*
* @var string
*/
protected $lastName;
/**
* A description of the share request
*
* @var string
*/
protected $summary;
/**
* The Etag for the notification
*
* @var string
*/
protected $etag;
/**
* The list of supported components
*
* @var Sabre\CalDAV\Property\SupportedCalendarComponentSet
*/
protected $supportedComponents;
/**
* Creates the Invite notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * readOnly - This must be set to true, if this is an invite for
* read-only access to a calendar.
* * hostUrl - A url to the shared calendar.
* * organizer - Url to the sharer principal.
* * commonName - The real name of the sharer (optional).
* * firstName - The first name of the sharer (optional).
* * lastName - The last name of the sharer (optional).
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
* * supportedComponents - An instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
* This allows the client to determine which components
* will be supported in the shared calendar. This is
* also optional.
*
* @param array $values All the options
*/
public function __construct(array $values) {
$required = array(
'id',
'etag',
'href',
'dtStamp',
'type',
'readOnly',
'hostUrl',
'organizer',
);
foreach($required as $item) {
if (!isset($values[$item])) {
throw new \InvalidArgumentException($item . ' is a required constructor option');
}
}
foreach($values as $key=>$value) {
if (!property_exists($this, $key)) {
throw new \InvalidArgumentException('Unknown option: ' . $key);
}
$this->$key = $value;
}
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $node) {
$prop = $node->ownerDocument->createElement('cs:invite-notification');
$node->appendChild($prop);
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serializeBody(DAV\Server $server, \DOMElement $node) {
$doc = $node->ownerDocument;
$dt = $doc->createElement('cs:dtstamp');
$this->dtStamp->setTimezone(new \DateTimezone('GMT'));
$dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z')));
$node->appendChild($dt);
$prop = $doc->createElement('cs:invite-notification');
$node->appendChild($prop);
$uid = $doc->createElement('cs:uid');
$uid->appendChild( $doc->createTextNode($this->id) );
$prop->appendChild($uid);
$href = $doc->createElement('d:href');
$href->appendChild( $doc->createTextNode( $this->href ) );
$prop->appendChild($href);
$nodeName = null;
switch($this->type) {
case SharingPlugin::STATUS_ACCEPTED :
$nodeName = 'cs:invite-accepted';
break;
case SharingPlugin::STATUS_DECLINED :
$nodeName = 'cs:invite-declined';
break;
case SharingPlugin::STATUS_DELETED :
$nodeName = 'cs:invite-deleted';
break;
case SharingPlugin::STATUS_NORESPONSE :
$nodeName = 'cs:invite-noresponse';
break;
}
$prop->appendChild(
$doc->createElement($nodeName)
);
$hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl);
$hostUrl = $doc->createElement('cs:hosturl');
$hostUrl->appendChild($hostHref);
$prop->appendChild($hostUrl);
$access = $doc->createElement('cs:access');
if ($this->readOnly) {
$access->appendChild($doc->createElement('cs:read'));
} else {
$access->appendChild($doc->createElement('cs:read-write'));
}
$prop->appendChild($access);
$organizerUrl = $doc->createElement('cs:organizer');
// If the organizer contains a 'mailto:' part, it means it should be
// treated as absolute.
if (strtolower(substr($this->organizer,0,7))==='mailto:') {
$organizerHref = new DAV\Property\Href($this->organizer, false);
} else {
$organizerHref = new DAV\Property\Href($this->organizer, true);
}
$organizerHref->serialize($server, $organizerUrl);
if ($this->commonName) {
$commonName = $doc->createElement('cs:common-name');
$commonName->appendChild($doc->createTextNode($this->commonName));
$organizerUrl->appendChild($commonName);
$commonNameOld = $doc->createElement('cs:organizer-cn');
$commonNameOld->appendChild($doc->createTextNode($this->commonName));
$prop->appendChild($commonNameOld);
}
if ($this->firstName) {
$firstName = $doc->createElement('cs:first-name');
$firstName->appendChild($doc->createTextNode($this->firstName));
$organizerUrl->appendChild($firstName);
$firstNameOld = $doc->createElement('cs:organizer-first');
$firstNameOld->appendChild($doc->createTextNode($this->firstName));
$prop->appendChild($firstNameOld);
}
if ($this->lastName) {
$lastName = $doc->createElement('cs:last-name');
$lastName->appendChild($doc->createTextNode($this->lastName));
$organizerUrl->appendChild($lastName);
$lastNameOld = $doc->createElement('cs:organizer-last');
$lastNameOld->appendChild($doc->createTextNode($this->lastName));
$prop->appendChild($lastNameOld);
}
$prop->appendChild($organizerUrl);
if ($this->summary) {
$summary = $doc->createElement('cs:summary');
$summary->appendChild($doc->createTextNode($this->summary));
$prop->appendChild($summary);
}
if ($this->supportedComponents) {
$xcomp = $doc->createElement('cal:supported-calendar-component-set');
$this->supportedComponents->serialize($server, $xcomp);
$prop->appendChild($xcomp);
}
}
/**
* 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;
}
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag() {
return $this->etag;
}
}

View file

@ -1,218 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications\Notification;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* This class represents the cs:invite-reply notification element.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class InviteReply extends DAV\Property implements CalDAV\Notifications\INotificationType {
/**
* A unique id for the message
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification
*
* @var DateTime
*/
protected $dtStamp;
/**
* The unique id of the notification this was a reply to.
*
* @var string
*/
protected $inReplyTo;
/**
* A url to the recipient of the original (!) notification.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_ constants.
*
* @var int
*/
protected $type;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* A description of the share request
*
* @var string
*/
protected $summary;
/**
* Notification Etag
*
* @var string
*/
protected $etag;
/**
* Creates the Invite Reply Notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * inReplyTo - This should refer to the 'id' of the notification
* this is a reply to.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * hostUrl - A url to the shared calendar.
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
*/
public function __construct(array $values) {
$required = array(
'id',
'etag',
'href',
'dtStamp',
'inReplyTo',
'type',
'hostUrl',
);
foreach($required as $item) {
if (!isset($values[$item])) {
throw new \InvalidArgumentException($item . ' is a required constructor option');
}
}
foreach($values as $key=>$value) {
if (!property_exists($this, $key)) {
throw new \InvalidArgumentException('Unknown option: ' . $key);
}
$this->$key = $value;
}
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $node) {
$prop = $node->ownerDocument->createElement('cs:invite-reply');
$node->appendChild($prop);
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serializeBody(DAV\Server $server, \DOMElement $node) {
$doc = $node->ownerDocument;
$dt = $doc->createElement('cs:dtstamp');
$this->dtStamp->setTimezone(new \DateTimezone('GMT'));
$dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z')));
$node->appendChild($dt);
$prop = $doc->createElement('cs:invite-reply');
$node->appendChild($prop);
$uid = $doc->createElement('cs:uid');
$uid->appendChild($doc->createTextNode($this->id));
$prop->appendChild($uid);
$inReplyTo = $doc->createElement('cs:in-reply-to');
$inReplyTo->appendChild( $doc->createTextNode($this->inReplyTo) );
$prop->appendChild($inReplyTo);
$href = $doc->createElement('d:href');
$href->appendChild( $doc->createTextNode($this->href) );
$prop->appendChild($href);
$nodeName = null;
switch($this->type) {
case SharingPlugin::STATUS_ACCEPTED :
$nodeName = 'cs:invite-accepted';
break;
case SharingPlugin::STATUS_DECLINED :
$nodeName = 'cs:invite-declined';
break;
}
$prop->appendChild(
$doc->createElement($nodeName)
);
$hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl);
$hostUrl = $doc->createElement('cs:hosturl');
$hostUrl->appendChild($hostHref);
$prop->appendChild($hostUrl);
if ($this->summary) {
$summary = $doc->createElement('cs:summary');
$summary->appendChild($doc->createTextNode($this->summary));
$prop->appendChild($summary);
}
}
/**
* 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;
}
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag() {
return $this->etag;
}
}

View file

@ -1,182 +0,0 @@
<?php
namespace Sabre\CalDAV\Notifications\Notification;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* SystemStatus notification
*
* This notification can be used to indicate to the user that the system is
* down.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SystemStatus extends DAV\Property implements CalDAV\Notifications\INotificationType {
const TYPE_LOW = 1;
const TYPE_MEDIUM = 2;
const TYPE_HIGH = 3;
/**
* A unique id
*
* @var string
*/
protected $id;
/**
* The type of alert. This should be one of the TYPE_ constants.
*
* @var int
*/
protected $type;
/**
* A human-readable description of the problem.
*
* @var string
*/
protected $description;
/**
* A url to a website with more information for the user.
*
* @var string
*/
protected $href;
/**
* Notification Etag
*
* @var string
*/
protected $etag;
/**
* Creates the notification.
*
* Some kind of unique id should be provided. This is used to generate a
* url.
*
* @param string $id
* @param string $etag
* @param int $type
* @param string $description
* @param string $href
*/
public function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) {
$this->id = $id;
$this->type = $type;
$this->description = $description;
$this->href = $href;
$this->etag = $etag;
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(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 DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serializeBody(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;
}
/*
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
public function getETag() {
return $this->etag;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,32 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* Principal collection
*
* This is an alternative collection to the standard ACL principal collection.
* This collection adds support for the calendar-proxy-read and
* calendar-proxy-write sub-principals, as defined by the caldav-proxy
* specification.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Collection extends DAVACL\AbstractPrincipalCollection {
/**
* Returns a child object based on principal information
*
* @param array $principalInfo
* @return User
*/
public function getChildForPrincipal(array $principalInfo) {
return new User($this->principalBackend, $principalInfo);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* ProxyRead principal interface
*
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IProxyRead extends DAVACL\IPrincipal {
}

View file

@ -1,19 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
/**
* ProxyWrite principal interface
*
* Any principal node implementing this interface will be picked up as a 'proxy
* principal group'.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IProxyWrite extends DAVACL\IPrincipal {
}

View file

@ -1,180 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
use Sabre\DAV;
/**
* ProxyRead principal
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ProxyRead implements IProxyRead {
/**
* Principal information from the parent principal.
*
* @var array
*/
protected $principalInfo;
/**
* Principal backend
*
* @var DAVACL\PrincipalBackend\BackendInterface
*/
protected $principalBackend;
/**
* Creates the object.
*
* Note that you MUST supply the parent principal information.
*
* @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
* @param array $principalInfo
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName() {
return 'calendar-proxy-read';
}
/**
* Returns the last modification time
*
* @return null
*/
public function getLastModified() {
return null;
}
/**
* Deletes the current node
*
* @throws DAV\Exception\Forbidden
* @return void
*/
public function delete() {
throw new DAV\Exception\Forbidden('Permission denied to delete node');
}
/**
* Renames the node
*
* @throws DAV\Exception\Forbidden
* @param string $name The new name
* @return void
*/
public function setName($name) {
throw new DAV\Exception\Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet() {
return array();
}
/**
* Returns the full principal url
*
* @return string
*/
public function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
}
/**
* Returns the list of group members
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName() {
return $this->getName();
}
}

View file

@ -1,180 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAVACL;
use Sabre\DAV;
/**
* ProxyWrite principal
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by User.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ProxyWrite implements IProxyWrite {
/**
* Parent principal information
*
* @var array
*/
protected $principalInfo;
/**
* Principal Backend
*
* @var DAVACL\PrincipalBackend\BackendInterface
*/
protected $principalBackend;
/**
* Creates the object
*
* Note that you MUST supply the parent principal information.
*
* @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
* @param array $principalInfo
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName() {
return 'calendar-proxy-write';
}
/**
* Returns the last modification time
*
* @return null
*/
public function getLastModified() {
return null;
}
/**
* Deletes the current node
*
* @throws DAV\Exception\Forbidden
* @return void
*/
public function delete() {
throw new DAV\Exception\Forbidden('Permission denied to delete node');
}
/**
* Renames the node
*
* @throws DAV\Exception\Forbidden
* @param string $name The new name
* @return void
*/
public function setName($name) {
throw new DAV\Exception\Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet() {
return array();
}
/**
* Returns the full principal url
*
* @return string
*/
public function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
}
/**
* Returns the list of group members
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName() {
return $this->getName();
}
}

View file

@ -1,134 +0,0 @@
<?php
namespace Sabre\CalDAV\Principal;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* CalDAV principal
*
* This is a standard user-principal for CalDAV. This principal is also a
* collection and returns the caldav-proxy-read and caldav-proxy-write child
* principals.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class User extends DAVACL\Principal implements DAV\ICollection {
/**
* Creates a new file in the directory
*
* @param string $name Name of the file
* @param resource $data Initial payload, passed as a readable stream resource.
* @throws DAV\Exception\Forbidden
* @return void
*/
public function createFile($name, $data = null) {
throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
}
/**
* Creates a new subdirectory
*
* @param string $name
* @throws DAV\Exception\Forbidden
* @return void
*/
public function createDirectory($name) {
throw new DAV\Exception\Forbidden('Permission denied to create directory');
}
/**
* Returns a specific child node, referenced by its name
*
* @param string $name
* @return DAV\INode
*/
public function getChild($name) {
$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
if (!$principal) {
throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
}
if ($name === 'calendar-proxy-read')
return new ProxyRead($this->principalBackend, $this->principalProperties);
if ($name === 'calendar-proxy-write')
return new ProxyWrite($this->principalBackend, $this->principalProperties);
throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
}
/**
* Returns an array with all the child nodes
*
* @return DAV\INode[]
*/
public function getChildren() {
$r = array();
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
$r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
}
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
$r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
}
return $r;
}
/**
* Returns whether or not the child node exists
*
* @param string $name
* @return bool
*/
public function childExists($name) {
try {
$this->getChild($name);
return true;
} catch (DAV\Exception\NotFound $e) {
return false;
}
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
$acl = parent::getACL();
$acl[] = array(
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
'protected' => true,
);
$acl[] = array(
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
'protected' => true,
);
return $acl;
}
}

View file

@ -1,74 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\DAV;
/**
* AllowedSharingModes
*
* This property encodes the 'allowed-sharing-modes' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* This property is a representation of the supported-calendar_component-set
* property in the CalDAV namespace. It simply requires an array of components,
* such as VEVENT, VTODO
*
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class AllowedSharingModes extends DAV\Property {
/**
* Whether or not a calendar can be shared with another user
*
* @var bool
*/
protected $canBeShared;
/**
* Whether or not the calendar can be placed on a public url.
*
* @var bool
*/
protected $canBePublished;
/**
* Constructor
*
* @param bool $canBeShared
* @param bool $canBePublished
* @return void
*/
public function __construct($canBeShared, $canBePublished) {
$this->canBeShared = $canBeShared;
$this->canBePublished = $canBePublished;
}
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $node) {
$doc = $node->ownerDocument;
if ($this->canBeShared) {
$xcomp = $doc->createElement('cs:can-be-shared');
$node->appendChild($xcomp);
}
if ($this->canBePublished) {
$xcomp = $doc->createElement('cs:can-be-published');
$node->appendChild($xcomp);
}
}
}

View file

@ -1,227 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* Invite property
*
* This property encodes the 'invite' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Invite extends DAV\Property {
/**
* The list of users a calendar has been shared to.
*
* @var array
*/
protected $users;
/**
* The organizer contains information about the person who shared the
* object.
*
* @var array
*/
protected $organizer;
/**
* Creates the property.
*
* Users is an array. Each element of the array has the following
* properties:
*
* * href - Often a mailto: address
* * commonName - Optional, for example a first and lastname for a user.
* * status - One of the SharingPlugin::STATUS_* constants.
* * readOnly - true or false
* * summary - Optional, description of the share
*
* The organizer key is optional to specify. It's only useful when a
* 'sharee' requests the sharing information.
*
* The organizer may have the following properties:
* * href - Often a mailto: address.
* * commonName - Optional human-readable name.
* * firstName - Optional first name.
* * lastName - Optional last name.
*
* If you wonder why these two structures are so different, I guess a
* valid answer is that the current spec is still a draft.
*
* @param array $users
*/
public function __construct(array $users, array $organizer = null) {
$this->users = $users;
$this->organizer = $organizer;
}
/**
* Returns the list of users, as it was passed to the constructor.
*
* @return array
*/
public function getValue() {
return $this->users;
}
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
if (!is_null($this->organizer)) {
$xorganizer = $doc->createElement('cs:organizer');
$href = $doc->createElement('d:href');
$href->appendChild($doc->createTextNode($this->organizer['href']));
$xorganizer->appendChild($href);
if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
$commonName = $doc->createElement('cs:common-name');
$commonName->appendChild($doc->createTextNode($this->organizer['commonName']));
$xorganizer->appendChild($commonName);
}
if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
$firstName = $doc->createElement('cs:first-name');
$firstName->appendChild($doc->createTextNode($this->organizer['firstName']));
$xorganizer->appendChild($firstName);
}
if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
$lastName = $doc->createElement('cs:last-name');
$lastName->appendChild($doc->createTextNode($this->organizer['lastName']));
$xorganizer->appendChild($lastName);
}
$node->appendChild($xorganizer);
}
foreach($this->users as $user) {
$xuser = $doc->createElement('cs:user');
$href = $doc->createElement('d:href');
$href->appendChild($doc->createTextNode($user['href']));
$xuser->appendChild($href);
if (isset($user['commonName']) && $user['commonName']) {
$commonName = $doc->createElement('cs:common-name');
$commonName->appendChild($doc->createTextNode($user['commonName']));
$xuser->appendChild($commonName);
}
switch($user['status']) {
case SharingPlugin::STATUS_ACCEPTED :
$status = $doc->createElement('cs:invite-accepted');
$xuser->appendChild($status);
break;
case SharingPlugin::STATUS_DECLINED :
$status = $doc->createElement('cs:invite-declined');
$xuser->appendChild($status);
break;
case SharingPlugin::STATUS_NORESPONSE :
$status = $doc->createElement('cs:invite-noresponse');
$xuser->appendChild($status);
break;
case SharingPlugin::STATUS_INVALID :
$status = $doc->createElement('cs:invite-invalid');
$xuser->appendChild($status);
break;
}
$xaccess = $doc->createElement('cs:access');
if ($user['readOnly']) {
$xaccess->appendChild(
$doc->createElement('cs:read')
);
} else {
$xaccess->appendChild(
$doc->createElement('cs:read-write')
);
}
$xuser->appendChild($xaccess);
if (isset($user['summary']) && $user['summary']) {
$summary = $doc->createElement('cs:summary');
$summary->appendChild($doc->createTextNode($user['summary']));
$xuser->appendChild($summary);
}
$node->appendChild($xuser);
}
}
/**
* Unserializes the property.
*
* This static method should return a an instance of this object.
*
* @param \DOMElement $prop
* @return DAV\IProperty
*/
static function unserialize(\DOMElement $prop) {
$xpath = new \DOMXPath($prop->ownerDocument);
$xpath->registerNamespace('cs', CalDAV\Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', 'DAV:');
$users = array();
foreach($xpath->query('cs:user', $prop) as $user) {
$status = null;
if ($xpath->evaluate('boolean(cs:invite-accepted)', $user)) {
$status = SharingPlugin::STATUS_ACCEPTED;
} elseif ($xpath->evaluate('boolean(cs:invite-declined)', $user)) {
$status = SharingPlugin::STATUS_DECLINED;
} elseif ($xpath->evaluate('boolean(cs:invite-noresponse)', $user)) {
$status = SharingPlugin::STATUS_NORESPONSE;
} elseif ($xpath->evaluate('boolean(cs:invite-invalid)', $user)) {
$status = SharingPlugin::STATUS_INVALID;
} else {
throw new DAV\Exception('Every cs:user property must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid');
}
$users[] = array(
'href' => $xpath->evaluate('string(d:href)', $user),
'commonName' => $xpath->evaluate('string(cs:common-name)', $user),
'readOnly' => $xpath->evaluate('boolean(cs:access/cs:read)', $user),
'summary' => $xpath->evaluate('string(cs:summary)', $user),
'status' => $status,
);
}
return new self($users);
}
}

View file

@ -1,102 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* schedule-calendar-transp property.
*
* This property is a representation of the schedule-calendar-transp property.
* This property is defined in RFC6638 (caldav scheduling).
*
* Its values are either 'transparent' or 'opaque'. If it's transparent, it
* means that this calendar will not be taken into consideration when a
* different user queries for free-busy information. If it's 'opaque', it will.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ScheduleCalendarTransp extends DAV\Property {
const TRANSPARENT = 'transparent';
const OPAQUE = 'opaque';
protected $value;
/**
* Creates the property
*
* @param string $value
*/
public function __construct($value) {
if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) {
throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"');
}
$this->value = $value;
}
/**
* Returns the current value
*
* @return string
*/
public function getValue() {
return $this->value;
}
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
switch($this->value) {
case self::TRANSPARENT :
$xval = $doc->createElement('cal:transparent');
break;
case self::OPAQUE :
$xval = $doc->createElement('cal:opaque');
break;
}
$node->appendChild($xval);
}
/**
* Unserializes the DOMElement back into a Property class.
*
* @param \DOMElement $node
* @return ScheduleCalendarTransp
*/
static function unserialize(\DOMElement $node) {
$value = null;
foreach($node->childNodes as $childNode) {
switch(DAV\XMLUtil::toClarkNotation($childNode)) {
case '{' . CalDAV\Plugin::NS_CALDAV . '}opaque' :
$value = self::OPAQUE;
break;
case '{' . CalDAV\Plugin::NS_CALDAV . '}transparent' :
$value = self::TRANSPARENT;
break;
}
}
if (is_null($value))
return null;
return new self($value);
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\DAV;
use Sabre\CalDAV;
/**
* Supported component set property
*
* This property is a representation of the supported-calendar_component-set
* property in the CalDAV namespace. It simply requires an array of components,
* such as VEVENT, VTODO
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SupportedCalendarComponentSet extends DAV\Property {
/**
* List of supported components, such as "VEVENT, VTODO"
*
* @var array
*/
private $components;
/**
* Creates the property
*
* @param array $components
*/
public function __construct(array $components) {
$this->components = $components;
}
/**
* Returns the list of supported components
*
* @return array
*/
public function getValue() {
return $this->components;
}
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
foreach($this->components as $component) {
$xcomp = $doc->createElement('cal:comp');
$xcomp->setAttribute('name',$component);
$node->appendChild($xcomp);
}
}
/**
* Unserializes the DOMElement back into a Property class.
*
* @param \DOMElement $node
* @return Property_SupportedCalendarComponentSet
*/
static function unserialize(\DOMElement $node) {
$components = array();
foreach($node->childNodes as $childNode) {
if (DAV\XMLUtil::toClarkNotation($childNode)==='{' . CalDAV\Plugin::NS_CALDAV . '}comp') {
$components[] = $childNode->getAttribute('name');
}
}
return new self($components);
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\DAV;
use Sabre\CalDAV\Plugin;
/**
* Supported-calendar-data property
*
* This property is a representation of the supported-calendar-data property
* in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
* so the value is currently hardcoded.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SupportedCalendarData extends DAV\Property {
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
$prefix = isset($server->xmlNamespaces[Plugin::NS_CALDAV])?$server->xmlNamespaces[Plugin::NS_CALDAV]:'cal';
$caldata = $doc->createElement($prefix . ':calendar-data');
$caldata->setAttribute('content-type','text/calendar');
$caldata->setAttribute('version','2.0');
$node->appendChild($caldata);
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace Sabre\CalDAV\Property;
use Sabre\DAV;
/**
* supported-collation-set property
*
* This property is a representation of the supported-collation-set property
* in the CalDAV namespace.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SupportedCollationSet extends DAV\Property {
/**
* Serializes the property in a DOM document
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
$prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav');
if (!$prefix) $prefix = 'cal';
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;ascii-casemap')
);
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;octet')
);
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;unicode-casemap')
);
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\VObject;
use Sabre\DAV;
/**
* iMIP handler.
*
* This class is responsible for sending out iMIP messages. iMIP is the
* email-based transport for iTIP. iTIP deals with scheduling operations for
* iCalendar objects.
*
* If you want to customize the email that gets sent out, you can do so by
* extending this class and overriding the sendMessage method.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class IMip {
/**
* Email address used in From: header.
*
* @var string
*/
protected $senderEmail;
/**
* Creates the email handler.
*
* @param string $senderEmail. The 'senderEmail' is the email that shows up
* in the 'From:' address. This should
* generally be some kind of no-reply email
* address you own.
*/
public function __construct($senderEmail) {
$this->senderEmail = $senderEmail;
}
/**
* Sends one or more iTip messages through email.
*
* @param string $originator Originator Email
* @param array $recipients Array of email addresses
* @param VObject\Component $vObject
* @param string $principal Principal Url of the originator
* @return void
*/
public function sendMessage($originator, array $recipients, VObject\Component $vObject, $principal) {
foreach($recipients as $recipient) {
$to = $recipient;
$replyTo = $originator;
$subject = 'SabreDAV iTIP message';
switch(strtoupper($vObject->METHOD)) {
case 'REPLY' :
$subject = 'Response for: ' . $vObject->VEVENT->SUMMARY;
break;
case 'REQUEST' :
$subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY;
break;
case 'CANCEL' :
$subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY;
break;
}
$headers = array();
$headers[] = 'Reply-To: ' . $replyTo;
$headers[] = 'From: ' . $this->senderEmail;
$headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8';
if (DAV\Server::$exposeVersion) {
$headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION . '-' . DAV\Version::STABILITY;
}
$vcalBody = $vObject->serialize();
$this->mail($to, $subject, $vcalBody, $headers);
}
}
// @codeCoverageIgnoreStart
// This is deemed untestable in a reasonable manner
/**
* This function is reponsible for sending the actual email.
*
* @param string $to Recipient email address
* @param string $subject Subject of the email
* @param string $body iCalendar body
* @param array $headers List of headers
* @return void
*/
protected function mail($to, $subject, $body, array $headers) {
mail($to, $subject, $body, implode("\r\n", $headers));
}
// @codeCoverageIgnoreEnd
}

View file

@ -1,16 +0,0 @@
<?php
namespace Sabre\CalDAV\Schedule;
/**
* Implement this interface to have a node be recognized as a CalDAV scheduling
* outbox.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL {
}

View file

@ -1,163 +0,0 @@
<?php
namespace Sabre\CalDAV\Schedule;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\DAVACL;
/**
* The CalDAV scheduling outbox
*
* The outbox is mainly used as an endpoint in the tree for a client to do
* free-busy requests. This functionality is completely handled by the
* Scheduling plugin, so this object is actually mostly static.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Outbox extends DAV\Collection implements IOutbox {
/**
* The principal Uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param string $principalUri
*/
public function __construct($principalUri) {
$this->principalUri = $principalUri;
}
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
public function getName() {
return 'outbox';
}
/**
* Returns an array with all the child nodes
*
* @return \Sabre\DAV\INode[]
*/
public function getChildren() {
return array();
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
$default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
$default['aggregates'][] = array(
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
);
$default['aggregates'][] = array(
'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
);
return $default;
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This object represents a CalDAV calendar that can be shared with other
* users.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ShareableCalendar extends Calendar implements IShareableCalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
public function updateShares(array $add, array $remove) {
$this->caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove);
}
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
public function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);
}
/**
* Marks this calendar as published.
*
* Publishing a calendar should automatically create a read-only, public,
* subscribable calendar.
*
* @param bool $value
* @return void
*/
public function setPublishStatus($value) {
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
}
}

View file

@ -1,116 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAVACL;
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SharedCalendar extends Calendar implements ISharedCalendar {
/**
* Constructor
*
* @param Backend\BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
$required = array(
'{http://calendarserver.org/ns/}shared-url',
'{http://sabredav.org/ns}owner-principal',
'{http://sabredav.org/ns}read-only',
);
foreach($required as $r) {
if (!isset($calendarInfo[$r])) {
throw new \InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)');
}
}
parent::__construct($caldavBackend, $calendarInfo);
}
/**
* This method should return the url of the owners' copy of the shared
* calendar.
*
* @return string
*/
public function getSharedUrl() {
return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->calendarInfo['{http://sabredav.org/ns}owner-principal'];
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
// The top-level ACL only contains access information for the true
// owner of the calendar, so we need to add the information for the
// sharee.
$acl = parent::getACL();
$acl[] = array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
);
if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
$acl[] = array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
);
}
return $acl;
}
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
public function getShares() {
return $this->caldavBackend->getShares($this->calendarInfo['id']);
}
}

View file

@ -1,519 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
/**
* This plugin implements support for caldav sharing.
*
* This spec is defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* See:
* Sabre\CalDAV\Backend\SharingSupport for all the documentation.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SharingPlugin extends DAV\ServerPlugin {
/**
* These are the various status constants used by sharing-messages.
*/
const STATUS_ACCEPTED = 1;
const STATUS_DECLINED = 2;
const STATUS_DELETED = 3;
const STATUS_NORESPONSE = 4;
const STATUS_INVALID = 5;
/**
* Reference to SabreDAV server object.
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
public function getFeatures() {
return array('calendarserver-sharing');
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'caldav-sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
array_push(
$this->server->protectedProperties,
'{' . Plugin::NS_CALENDARSERVER . '}invite',
'{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
'{' . Plugin::NS_CALENDARSERVER . '}shared-url'
);
$this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
$this->server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
$this->server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
$this->server->subscribeEvent('unknownMethod', array($this,'unknownMethod'));
}
/**
* This event is triggered when properties are requested for a certain
* node.
*
* This allows us to inject any properties early.
*
* @param string $path
* @param DAV\INode $node
* @param array $requestedProperties
* @param array $returnedProperties
* @return void
*/
public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) {
if ($node instanceof IShareableCalendar) {
if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) {
unset($requestedProperties[$index]);
$returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] =
new Property\Invite(
$node->getShares()
);
}
}
if ($node instanceof ISharedCalendar) {
if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties))!==false) {
unset($requestedProperties[$index]);
$returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}shared-url'] =
new DAV\Property\Href(
$node->getSharedUrl()
);
}
// The 'invite' property is slightly different for the 'shared'
// instance of the calendar, as it also contains the owner
// information.
if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) {
unset($requestedProperties[$index]);
// Fetching owner information
$props = $this->server->getPropertiesForPath($node->getOwner(), array(
'{http://sabredav.org/ns}email-address',
'{DAV:}displayname',
), 1);
$ownerInfo = array(
'href' => $node->getOwner(),
);
if ($props && isset($props[0]) && isset($props[0][200])) {
// We're mapping the internal webdav properties to the
// elements caldav-sharing expects.
$mapping = array(
'{http://sabredav.org/ns}email-address' => 'href',
'{DAV:}displayname' => 'commonName',
);
foreach($mapping as $source=>$dest) {
if (isset($props[0][200][$source])) {
$ownerInfo[$dest] = $props[0][200][$source];
}
}
}
$returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] =
new Property\Invite(
$node->getShares(),
$ownerInfo
);
}
}
}
/**
* This method is triggered *after* all properties have been retrieved.
* This allows us to inject the correct resourcetype for calendars that
* have been shared.
*
* @param string $path
* @param array $properties
* @param DAV\INode $node
* @return void
*/
public function afterGetProperties($path, &$properties, DAV\INode $node) {
if ($node instanceof IShareableCalendar) {
if (isset($properties[200]['{DAV:}resourcetype'])) {
if (count($node->getShares())>0) {
$properties[200]['{DAV:}resourcetype']->add(
'{' . Plugin::NS_CALENDARSERVER . '}shared-owner'
);
}
}
$propName = '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes';
if (array_key_exists($propName, $properties[404])) {
unset($properties[404][$propName]);
$properties[200][$propName] = new Property\AllowedSharingModes(true,false);
}
}
}
/**
* This method is trigged when a user attempts to update a node's
* properties.
*
* A previous draft of the sharing spec stated that it was possible to use
* PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
* the calendar.
*
* Even though this is no longer in the current spec, we keep this around
* because OS X 10.7 may still make use of this feature.
*
* @param array $mutations
* @param array $result
* @param DAV\INode $node
* @return void
*/
public function updateProperties(array &$mutations, array &$result, DAV\INode $node) {
if (!$node instanceof IShareableCalendar)
return;
if (!isset($mutations['{DAV:}resourcetype'])) {
return;
}
// Only doing something if shared-owner is indeed not in the list.
if($mutations['{DAV:}resourcetype']->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return;
$shares = $node->getShares();
$remove = array();
foreach($shares as $share) {
$remove[] = $share['href'];
}
$node->updateShares(array(), $remove);
// We're marking this update as 200 OK
$result[200]['{DAV:}resourcetype'] = null;
// Removing it from the mutations list
unset($mutations['{DAV:}resourcetype']);
}
/**
* This event is triggered when the server didn't know how to handle a
* certain request.
*
* We intercept this to handle POST requests on calendars.
*
* @param string $method
* @param string $uri
* @return null|bool
*/
public function unknownMethod($method, $uri) {
if ($method!=='POST') {
return;
}
// Only handling xml
$contentType = $this->server->httpRequest->getHeader('Content-Type');
if (strpos($contentType,'application/xml')===false && strpos($contentType,'text/xml')===false)
return;
// Making sure the node exists
try {
$node = $this->server->tree->getNodeForPath($uri);
} catch (DAV\Exception\NotFound $e) {
return;
}
$dom = DAV\XMLUtil::loadDOMDocument($this->server->httpRequest->getBody(true));
$documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild);
switch($documentType) {
// Dealing with the 'share' document, which modified invitees on a
// calendar.
case '{' . Plugin::NS_CALENDARSERVER . '}share' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($uri, '{DAV:}write');
}
$mutations = $this->parseShareRequest($dom);
$node->updateShares($mutations[0], $mutations[1]);
$this->server->httpResponse->sendStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
// The invite-reply document is sent when the user replies to an
// invitation of a calendar share.
case '{'. Plugin::NS_CALENDARSERVER.'}invite-reply' :
// This only works on the calendar-home-root node.
if (!$node instanceof UserCalendars) {
return;
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($uri, '{DAV:}write');
}
$message = $this->parseInviteReplyRequest($dom);
$url = $node->shareReply(
$message['href'],
$message['status'],
$message['calendarUri'],
$message['inReplyTo'],
$message['summary']
);
$this->server->httpResponse->sendStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
if ($url) {
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElement('cs:shared-as');
foreach($this->server->xmlNamespaces as $namespace => $prefix) {
$root->setAttribute('xmlns:' . $prefix, $namespace);
}
$dom->appendChild($root);
$href = new DAV\Property\Href($url);
$href->serialize($this->server, $root);
$this->server->httpResponse->setHeader('Content-Type','application/xml');
$this->server->httpResponse->sendBody($dom->saveXML());
}
// Breaking the event chain
return false;
case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($uri, '{DAV:}write');
}
$node->setPublishStatus(true);
// iCloud sends back the 202, so we will too.
$this->server->httpResponse->sendStatus(202);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableCalendar) {
return;
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($uri, '{DAV:}write');
}
$node->setPublishStatus(false);
$this->server->httpResponse->sendStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
}
}
/**
* Parses the 'share' POST request.
*
* This method returns an array, containing two arrays.
* The first array is a list of new sharees. Every element is a struct
* containing a:
* * href element. (usually a mailto: address)
* * commonName element (often a first and lastname, but can also be
* false)
* * readOnly (true or false)
* * summary (A description of the share, can also be false)
*
* The second array is a list of sharees that are to be removed. This is
* just a simple array with 'hrefs'.
*
* @param \DOMDocument $dom
* @return array
*/
protected function parseShareRequest(\DOMDocument $dom) {
$xpath = new \DOMXPath($dom);
$xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', 'DAV:');
$set = array();
$elems = $xpath->query('cs:set');
for($i=0; $i < $elems->length; $i++) {
$xset = $elems->item($i);
$set[] = array(
'href' => $xpath->evaluate('string(d:href)', $xset),
'commonName' => $xpath->evaluate('string(cs:common-name)', $xset),
'summary' => $xpath->evaluate('string(cs:summary)', $xset),
'readOnly' => $xpath->evaluate('boolean(cs:read)', $xset)!==false
);
}
$remove = array();
$elems = $xpath->query('cs:remove');
for($i=0; $i < $elems->length; $i++) {
$xremove = $elems->item($i);
$remove[] = $xpath->evaluate('string(d:href)', $xremove);
}
return array($set, $remove);
}
/**
* Parses the 'invite-reply' POST request.
*
* This method returns an array, containing the following properties:
* * href - The sharee who is replying
* * status - One of the self::STATUS_* constants
* * calendarUri - The url of the shared calendar
* * inReplyTo - The unique id of the share invitation.
* * summary - Optional description of the reply.
*
* @param \DOMDocument $dom
* @return array
*/
protected function parseInviteReplyRequest(\DOMDocument $dom) {
$xpath = new \DOMXPath($dom);
$xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', 'DAV:');
$hostHref = $xpath->evaluate('string(cs:hosturl/d:href)');
if (!$hostHref) {
throw new DAV\Exception\BadRequest('The {' . Plugin::NS_CALENDARSERVER . '}hosturl/{DAV:}href element is required');
}
return array(
'href' => $xpath->evaluate('string(d:href)'),
'calendarUri' => $this->server->calculateUri($hostHref),
'inReplyTo' => $xpath->evaluate('string(cs:in-reply-to)'),
'summary' => $xpath->evaluate('string(cs:summary)'),
'status' => $xpath->evaluate('boolean(cs:invite-accepted)')?self::STATUS_ACCEPTED:self::STATUS_DECLINED
);
}
}

View file

@ -1,342 +0,0 @@
<?php
namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* The UserCalenders class contains all calendars associated to one user
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class UserCalendars implements DAV\IExtendedCollection, DAVACL\IACL {
/**
* CalDAV backend
*
* @var Sabre\CalDAV\Backend\BackendInterface
*/
protected $caldavBackend;
/**
* Principal information
*
* @var array
*/
protected $principalInfo;
/**
* Constructor
*
* @param Backend\BackendInterface $caldavBackend
* @param mixed $userUri
*/
public function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
$this->caldavBackend = $caldavBackend;
$this->principalInfo = $principalInfo;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
list(,$name) = DAV\URLUtil::splitPath($this->principalInfo['uri']);
return $name;
}
/**
* Updates the name of this object
*
* @param string $name
* @return void
*/
public function setName($name) {
throw new DAV\Exception\Forbidden();
}
/**
* Deletes this object
*
* @return void
*/
public function delete() {
throw new DAV\Exception\Forbidden();
}
/**
* Returns the last modification date
*
* @return int
*/
public function getLastModified() {
return null;
}
/**
* Creates a new file under this object.
*
* This is currently not allowed
*
* @param string $filename
* @param resource $data
* @return void
*/
public function createFile($filename, $data=null) {
throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
}
/**
* Creates a new directory under this object.
*
* This is currently not allowed.
*
* @param string $filename
* @return void
*/
public function createDirectory($filename) {
throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
}
/**
* Returns a single calendar, by name
*
* @param string $name
* @todo needs optimizing
* @return Calendar
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return $child;
}
throw new DAV\Exception\NotFound('Calendar with name \'' . $name . '\' could not be found');
}
/**
* Checks if a calendar exists.
*
* @param string $name
* @todo needs optimizing
* @return bool
*/
public function childExists($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return true;
}
return false;
}
/**
* Returns a list of calendars
*
* @return array
*/
public function getChildren() {
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objs = array();
foreach($calendars as $calendar) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
} else {
$objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
}
} else {
$objs[] = new Calendar($this->caldavBackend, $calendar);
}
}
$objs[] = new Schedule\Outbox($this->principalInfo['uri']);
// We're adding a notifications node, if it's supported by the backend.
if ($this->caldavBackend instanceof Backend\NotificationSupport) {
$objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
return $objs;
}
/**
* Creates a new calendar
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @return void
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
$isCalendar = false;
foreach($resourceType as $rt) {
switch ($rt) {
case '{DAV:}collection' :
case '{http://calendarserver.org/ns/}shared-owner' :
// ignore
break;
case '{urn:ietf:params:xml:ns:caldav}calendar' :
$isCalendar = true;
break;
default :
throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
}
}
if (!$isCalendar) {
throw new DAV\Exception\InvalidResourceType('You can only create calendars in this collection');
}
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalInfo['uri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
/**
* This method is called when a user replied to a request to share.
*
* This method should return the url of the newly created calendar if the
* share was accepted.
*
* @param string href The sharee who is replying (often a mailto: address)
* @param int status One of the SharingPlugin::STATUS_* constants
* @param string $calendarUri The url to the calendar thats being shared
* @param string $inReplyTo The unique id this message is a response to
* @param string $summary A description of the reply
* @return null|string
*/
public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
if (!$this->caldavBackend instanceof Backend\SharingSupport) {
throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
}
return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace Sabre\CalDAV;
/**
* This class contains the Sabre\CalDAV version constants.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Version {
/**
* Full version number
*/
const VERSION = '1.8.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'stable';
}

View file

@ -1,315 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* The AddressBook class represents a CardDAV addressbook, owned by a specific user
*
* The AddressBook can contain multiple vcards
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL {
/**
* This is an array with addressbook information
*
* @var array
*/
private $addressBookInfo;
/**
* CardDAV backend
*
* @var Backend\BackendInterface
*/
private $carddavBackend;
/**
* Constructor
*
* @param Backend\BackendInterface $carddavBackend
* @param array $addressBookInfo
*/
public function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) {
$this->carddavBackend = $carddavBackend;
$this->addressBookInfo = $addressBookInfo;
}
/**
* Returns the name of the addressbook
*
* @return string
*/
public function getName() {
return $this->addressBookInfo['uri'];
}
/**
* Returns a card
*
* @param string $name
* @return \ICard
*/
public function getChild($name) {
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name);
if (!$obj) throw new DAV\Exception\NotFound('Card not found');
return new Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
/**
* Returns the full list of cards
*
* @return array
*/
public function getChildren() {
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
$children = array();
foreach($objs as $obj) {
$children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
return $children;
}
/**
* Creates a new directory
*
* We actually block this, as subdirectories are not allowed in addressbooks.
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed');
}
/**
* Creates a new file
*
* The contents of the new file must be a valid VCARD.
*
* This method may return an ETag.
*
* @param string $name
* @param resource $vcardData
* @return string|null
*/
public function createFile($name,$vcardData = null) {
if (is_resource($vcardData)) {
$vcardData = stream_get_contents($vcardData);
}
// Converting to UTF-8, if needed
$vcardData = DAV\StringUtil::ensureUTF8($vcardData);
return $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData);
}
/**
* Deletes the entire addressbook.
*
* @return void
*/
public function delete() {
$this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
}
/**
* Renames the addressbook
*
* @param string $newName
* @return void
*/
public function setName($newName) {
throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
*/
public function getLastModified() {
return null;
}
/**
* Updates properties on this node,
*
* The properties array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param array $mutations
* @return bool|array
*/
public function updateProperties($mutations) {
return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations);
}
/**
* Returns a list of properties for this nodes.
*
* The properties list is a list of propertynames the client requested,
* encoded in clark-notation {xmlnamespace}tagname
*
* If the array is empty, it means 'all properties' were requested.
*
* @param array $properties
* @return array
*/
public function getProperties($properties) {
$response = array();
foreach($properties as $propertyName) {
if (isset($this->addressBookInfo[$propertyName])) {
$response[$propertyName] = $this->addressBookInfo[$propertyName];
}
}
return $response;
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->addressBookInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,221 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
/**
* Parses the addressbook-query report request body.
*
* Whoever designed this format, and the CalDAV equivalent even more so,
* has no feel for design.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class AddressBookQueryParser {
const TEST_ANYOF = 'anyof';
const TEST_ALLOF = 'allof';
/**
* List of requested properties the client wanted
*
* @var array
*/
public $requestedProperties;
/**
* The number of results the client wants
*
* null means it wasn't specified, which in most cases means 'all results'.
*
* @var int|null
*/
public $limit;
/**
* List of property filters.
*
* @var array
*/
public $filters;
/**
* Either TEST_ANYOF or TEST_ALLOF
*
* @var string
*/
public $test;
/**
* DOM Document
*
* @var DOMDocument
*/
protected $dom;
/**
* DOM XPath object
*
* @var DOMXPath
*/
protected $xpath;
/**
* Creates the parser
*
* @param \DOMDocument $dom
*/
public function __construct(\DOMDocument $dom) {
$this->dom = $dom;
$this->xpath = new \DOMXPath($dom);
$this->xpath->registerNameSpace('card',Plugin::NS_CARDDAV);
}
/**
* Parses the request.
*
* @return void
*/
public function parse() {
$filterNode = null;
$limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)');
if (is_nan($limit)) $limit = null;
$filter = $this->xpath->query('/card:addressbook-query/card:filter');
// According to the CardDAV spec there needs to be exactly 1 filter
// element. However, KDE 4.8.2 contains a bug that will encode 0 filter
// elements, so this is a workaround for that.
//
// See: https://bugs.kde.org/show_bug.cgi?id=300047
if ($filter->length === 0) {
$test = null;
$filter = null;
} elseif ($filter->length === 1) {
$filter = $filter->item(0);
$test = $this->xpath->evaluate('string(@test)', $filter);
} else {
throw new DAV\Exception\BadRequest('Only one filter element is allowed');
}
if (!$test) $test = self::TEST_ANYOF;
if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
throw new DAV\Exception\BadRequest('The test attribute must either hold "anyof" or "allof"');
}
$propFilters = array();
$propFilterNodes = $this->xpath->query('card:prop-filter', $filter);
for($ii=0; $ii < $propFilterNodes->length; $ii++) {
$propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii));
}
$this->filters = $propFilters;
$this->limit = $limit;
$this->requestedProperties = array_keys(DAV\XMLUtil::parseProperties($this->dom->firstChild));
$this->test = $test;
}
/**
* Parses the prop-filter xml element
*
* @param \DOMElement $propFilterNode
* @return array
*/
protected function parsePropFilterNode(\DOMElement $propFilterNode) {
$propFilter = array();
$propFilter['name'] = $propFilterNode->getAttribute('name');
$propFilter['test'] = $propFilterNode->getAttribute('test');
if (!$propFilter['test']) $propFilter['test'] = 'anyof';
$propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0;
$paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode);
$propFilter['param-filters'] = array();
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
$propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii));
}
$propFilter['text-matches'] = array();
$textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode);
for($ii=0;$ii<$textMatchNodes->length;$ii++) {
$propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii));
}
return $propFilter;
}
/**
* Parses the param-filter element
*
* @param \DOMElement $paramFilterNode
* @return array
*/
public function parseParamFilterNode(\DOMElement $paramFilterNode) {
$paramFilter = array();
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
$paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0;
$paramFilter['text-match'] = null;
$textMatch = $this->xpath->query('card:text-match', $paramFilterNode);
if ($textMatch->length>0) {
$paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0));
}
return $paramFilter;
}
/**
* Text match
*
* @param \DOMElement $textMatchNode
* @return array
*/
public function parseTextMatchNode(\DOMElement $textMatchNode) {
$matchType = $textMatchNode->getAttribute('match-type');
if (!$matchType) $matchType = 'contains';
if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) {
throw new DAV\Exception\BadRequest('Unknown match-type: ' . $matchType);
}
$negateCondition = $textMatchNode->getAttribute('negate-condition');
$negateCondition = $negateCondition==='yes';
$collation = $textMatchNode->getAttribute('collation');
if (!$collation) $collation = 'i;unicode-casemap';
return array(
'negate-condition' => $negateCondition,
'collation' => $collation,
'match-type' => $matchType,
'value' => $textMatchNode->nodeValue
);
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAVACL;
/**
* AddressBook rootnode
*
* This object lists a collection of users, which can contain addressbooks.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class AddressBookRoot extends DAVACL\AbstractPrincipalCollection {
/**
* Principal Backend
*
* @var Sabre\DAVACL\PrincipalBackend\BackendInteface
*/
protected $principalBackend;
/**
* CardDAV backend
*
* @var Backend\BackendInterface
*/
protected $carddavBackend;
/**
* Constructor
*
* This constructor needs both a principal and a carddav backend.
*
* By default this class will show a list of addressbook collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
* @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
* @param Backend\BackendInterface $carddavBackend
* @param string $principalPrefix
*/
public function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend,Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') {
$this->carddavBackend = $carddavBackend;
parent::__construct($principalBackend, $principalPrefix);
}
/**
* Returns the name of the node
*
* @return string
*/
public function getName() {
return Plugin::ADDRESSBOOK_ROOT;
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principal
* @return \Sabre\DAV\INode
*/
public function getChildForPrincipal(array $principal) {
return new UserAddressBooks($this->carddavBackend, $principal['uri']);
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace Sabre\CardDAV\Backend;
/**
* CardDAV abstract Backend
*
* This class serves as a base-class for addressbook backends
*
* This class doesn't do much, but it was added for consistency.
*
* @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 AbstractBackend implements BackendInterface {
}

View file

@ -1,166 +0,0 @@
<?php
namespace Sabre\CardDAV\Backend;
/**
* CardDAV Backend Interface
*
* Any CardDAV backend must implement this interface.
*
* Note that there are references to 'addressBookId' scattered throughout the
* class. The value of the addressBookId is completely up to you, it can be any
* arbitrary value you can use as an unique identifier.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface BackendInterface {
/**
* Returns the list of addressbooks for a specific user.
*
* Every addressbook should have the following properties:
* id - an arbitrary unique id
* uri - the 'basename' part of the url
* principaluri - Same as the passed parameter
*
* Any additional clark-notation property may be passed besides this. Some
* common ones are :
* {DAV:}displayname
* {urn:ietf:params:xml:ns:carddav}addressbook-description
* {http://calendarserver.org/ns/}getctag
*
* @param string $principalUri
* @return array
*/
public function getAddressBooksForUser($principalUri);
/**
* Updates an addressbook's properties
*
* See Sabre\DAV\IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressBookId
* @param array $mutations
* @see Sabre\DAV\IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($addressBookId, array $mutations);
/**
* Creates a new address book
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
*/
public function createAddressBook($principalUri, $url, array $properties);
/**
* Deletes an entire addressbook and all its contents
*
* @param mixed $addressBookId
* @return void
*/
public function deleteAddressBook($addressBookId);
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public function getCards($addressbookId);
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public function getCard($addressBookId, $cardUri);
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function createCard($addressBookId, $cardUri, $cardData);
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function updateCard($addressBookId, $cardUri, $cardData);
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
public function deleteCard($addressBookId, $cardUri);
}

View file

@ -1,333 +0,0 @@
<?php
namespace Sabre\CardDAV\Backend;
use Sabre\CardDAV;
use Sabre\DAV;
/**
* PDO CardDAV backend
*
* This CardDAV backend uses PDO to store addressbooks
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class PDO extends AbstractBackend {
/**
* PDO connection
*
* @var PDO
*/
protected $pdo;
/**
* The PDO table name used to store addressbooks
*/
protected $addressBooksTableName;
/**
* The PDO table name used to store cards
*/
protected $cardsTableName;
/**
* Sets up the object
*
* @param \PDO $pdo
* @param string $addressBooksTableName
* @param string $cardsTableName
*/
public function __construct(\PDO $pdo, $addressBooksTableName = 'addressbooks', $cardsTableName = 'cards') {
$this->pdo = $pdo;
$this->addressBooksTableName = $addressBooksTableName;
$this->cardsTableName = $cardsTableName;
}
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $principalUri
* @return array
*/
public function getAddressBooksForUser($principalUri) {
$stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM '.$this->addressBooksTableName.' WHERE principaluri = ?');
$stmt->execute(array($principalUri));
$addressBooks = array();
foreach($stmt->fetchAll() as $row) {
$addressBooks[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{DAV:}displayname' => $row['displayname'],
'{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
'{http://calendarserver.org/ns/}getctag' => $row['ctag'],
'{' . CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' =>
new CardDAV\Property\SupportedAddressData(),
);
}
return $addressBooks;
}
/**
* Updates an addressbook's properties
*
* See Sabre\DAV\IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressBookId
* @param array $mutations
* @see Sabre\DAV\IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($addressBookId, array $mutations) {
$updates = array();
foreach($mutations as $property=>$newValue) {
switch($property) {
case '{DAV:}displayname' :
$updates['displayname'] = $newValue;
break;
case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
$updates['description'] = $newValue;
break;
default :
// If any unsupported values were being updated, we must
// let the entire request fail.
return false;
}
}
// No values are being updated?
if (!$updates) {
return false;
}
$query = 'UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 ';
foreach($updates as $key=>$value) {
$query.=', `' . $key . '` = :' . $key . ' ';
}
$query.=' WHERE id = :addressbookid';
$stmt = $this->pdo->prepare($query);
$updates['addressbookid'] = $addressBookId;
$stmt->execute($updates);
return true;
}
/**
* Creates a new address book
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
*/
public function createAddressBook($principalUri, $url, array $properties) {
$values = array(
'displayname' => null,
'description' => null,
'principaluri' => $principalUri,
'uri' => $url,
);
foreach($properties as $property=>$newValue) {
switch($property) {
case '{DAV:}displayname' :
$values['displayname'] = $newValue;
break;
case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
$values['description'] = $newValue;
break;
default :
throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
}
}
$query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)';
$stmt = $this->pdo->prepare($query);
$stmt->execute($values);
}
/**
* Deletes an entire addressbook and all its contents
*
* @param int $addressBookId
* @return void
*/
public function deleteAddressBook($addressBookId) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressBookId));
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
$stmt->execute(array($addressBookId));
}
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public function getCards($addressbookId) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressbookId));
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public function getCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
$stmt->execute(array($addressBookId, $cardUri));
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return (count($result)>0?$result[0]:false);
}
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function createCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)');
$result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function updateCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?');
$stmt->execute(array($cardData, time(), $cardUri, $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
public function deleteCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
$stmt->execute(array($addressBookId, $cardUri));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return $stmt->rowCount()===1;
}
}

View file

@ -1,260 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAVACL;
use Sabre\DAV;
/**
* The Card object represents a single Card from an addressbook
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Card extends DAV\File implements ICard, DAVACL\IACL {
/**
* CardDAV backend
*
* @var Backend\BackendInterface
*/
private $carddavBackend;
/**
* Array with information about this Card
*
* @var array
*/
private $cardData;
/**
* Array with information about the containing addressbook
*
* @var array
*/
private $addressBookInfo;
/**
* Constructor
*
* @param Backend\BackendInterface $carddavBackend
* @param array $addressBookInfo
* @param array $cardData
*/
public function __construct(Backend\BackendInterface $carddavBackend,array $addressBookInfo,array $cardData) {
$this->carddavBackend = $carddavBackend;
$this->addressBookInfo = $addressBookInfo;
$this->cardData = $cardData;
}
/**
* Returns the uri for this object
*
* @return string
*/
public function getName() {
return $this->cardData['uri'];
}
/**
* Returns the VCard-formatted object
*
* @return string
*/
public function get() {
// Pre-populating 'carddata' is optional. If we don't yet have it
// already, we fetch it from the backend.
if (!isset($this->cardData['carddata'])) {
$this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
}
return $this->cardData['carddata'];
}
/**
* Updates the VCard-formatted object
*
* @param string $cardData
* @return string|null
*/
public function put($cardData) {
if (is_resource($cardData))
$cardData = stream_get_contents($cardData);
// Converting to UTF-8, if needed
$cardData = DAV\StringUtil::ensureUTF8($cardData);
$etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData);
$this->cardData['carddata'] = $cardData;
$this->cardData['etag'] = $etag;
return $etag;
}
/**
* Deletes the card
*
* @return void
*/
public function delete() {
$this->carddavBackend->deleteCard($this->addressBookInfo['id'],$this->cardData['uri']);
}
/**
* Returns the mime content-type
*
* @return string
*/
public function getContentType() {
return 'text/x-vcard; charset=utf-8';
}
/**
* Returns an ETag for this object
*
* @return string
*/
public function getETag() {
if (isset($this->cardData['etag'])) {
return $this->cardData['etag'];
} else {
$data = $this->get();
if (is_string($data)) {
return '"' . md5($data) . '"';
} else {
// We refuse to calculate the md5 if it's a stream.
return null;
}
}
}
/**
* Returns the last modification date as a unix timestamp
*
* @return int
*/
public function getLastModified() {
return isset($this->cardData['lastmodified'])?$this->cardData['lastmodified']:null;
}
/**
* Returns the size of this object in bytes
*
* @return int
*/
public function getSize() {
if (array_key_exists('size', $this->cardData)) {
return $this->cardData['size'];
} else {
return strlen($this->get());
}
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->addressBookInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
/**
* AddressBook interface
*
* Implement this interface to allow a node to be recognized as an addressbook.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IAddressBook extends DAV\ICollection {
}

View file

@ -1,20 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
/**
* Card interface
*
* Extend the ICard interface to allow your custom nodes to be picked up as
* 'Cards'.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface ICard extends DAV\IFile {
}

View file

@ -1,21 +0,0 @@
<?php
namespace Sabre\CardDAV;
/**
* IDirectory interface
*
* Implement this interface to have an addressbook marked as a 'directory'. A
* directory is an (often) global addressbook.
*
* A full description can be found in the IETF draft:
* - draft-daboo-carddav-directory-gateway
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface IDirectory extends IAddressBook {
}

View file

@ -1,704 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\DAVACL;
use Sabre\VObject;
/**
* CardDAV plugin
*
* The CardDAV plugin adds CardDAV functionality to the WebDAV server
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Plugin extends DAV\ServerPlugin {
/**
* Url to the addressbooks
*/
const ADDRESSBOOK_ROOT = 'addressbooks';
/**
* xml namespace for CardDAV elements
*/
const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
/**
* Add urls to this property to have them automatically exposed as
* 'directories' to the user.
*
* @var array
*/
public $directories = array();
/**
* Server class
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* Initializes the plugin
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
/* Events */
$server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
$server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
$server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
$server->subscribeEvent('report', array($this,'report'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
$server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
$server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
/* Namespaces */
$server->xmlNamespaces[self::NS_CARDDAV] = 'card';
/* Mapping Interfaces to {DAV:}resourcetype values */
$server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
$server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory';
/* Adding properties that may never be changed */
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';
$server->propertyMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Property\\Href';
$this->server = $server;
}
/**
* Returns a list of supported features.
*
* This is used in the DAV: header in the OPTIONS and PROPFIND requests.
*
* @return array
*/
public function getFeatures() {
return array('addressbook');
}
/**
* Returns a list of reports this plugin supports.
*
* This will be used in the {DAV:}supported-report-set property.
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @param string $uri
* @return array
*/
public function getSupportedReportSet($uri) {
$node = $this->server->tree->getNodeForPath($uri);
if ($node instanceof IAddressBook || $node instanceof ICard) {
return array(
'{' . self::NS_CARDDAV . '}addressbook-multiget',
'{' . self::NS_CARDDAV . '}addressbook-query',
);
}
return array();
}
/**
* Adds all CardDAV-specific properties
*
* @param string $path
* @param DAV\INode $node
* @param array $requestedProperties
* @param array $returnedProperties
* @return void
*/
public function beforeGetProperties($path, DAV\INode $node, array &$requestedProperties, array &$returnedProperties) {
if ($node instanceof DAVACL\IPrincipal) {
// calendar-home-set property
$addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set';
if (in_array($addHome,$requestedProperties)) {
$principalId = $node->getName();
$addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/';
unset($requestedProperties[array_search($addHome, $requestedProperties)]);
$returnedProperties[200][$addHome] = new DAV\Property\Href($addressbookHomePath);
}
$directories = '{' . self::NS_CARDDAV . '}directory-gateway';
if ($this->directories && in_array($directories, $requestedProperties)) {
unset($requestedProperties[array_search($directories, $requestedProperties)]);
$returnedProperties[200][$directories] = new DAV\Property\HrefList($this->directories);
}
}
if ($node instanceof ICard) {
// The address-data property is not supposed to be a 'real'
// property, but in large chunks of the spec it does act as such.
// Therefore we simply expose it as a property.
$addressDataProp = '{' . self::NS_CARDDAV . '}address-data';
if (in_array($addressDataProp, $requestedProperties)) {
unset($requestedProperties[$addressDataProp]);
$val = $node->get();
if (is_resource($val))
$val = stream_get_contents($val);
$returnedProperties[200][$addressDataProp] = $val;
}
}
if ($node instanceof UserAddressBooks) {
$meCardProp = '{http://calendarserver.org/ns/}me-card';
if (in_array($meCardProp, $requestedProperties)) {
$props = $this->server->getProperties($node->getOwner(), array('{http://sabredav.org/ns}vcard-url'));
if (isset($props['{http://sabredav.org/ns}vcard-url'])) {
$returnedProperties[200][$meCardProp] = new DAV\Property\Href(
$props['{http://sabredav.org/ns}vcard-url']
);
$pos = array_search($meCardProp, $requestedProperties);
unset($requestedProperties[$pos]);
}
}
}
}
/**
* This event is triggered when a PROPPATCH method is executed
*
* @param array $mutations
* @param array $result
* @param DAV\INode $node
* @return bool
*/
public function updateProperties(&$mutations, &$result, DAV\INode $node) {
if (!$node instanceof UserAddressBooks) {
return true;
}
$meCard = '{http://calendarserver.org/ns/}me-card';
// The only property we care about
if (!isset($mutations[$meCard]))
return true;
$value = $mutations[$meCard];
unset($mutations[$meCard]);
if ($value instanceof DAV\Property\IHref) {
$value = $value->getHref();
$value = $this->server->calculateUri($value);
} elseif (!is_null($value)) {
$result[400][$meCard] = null;
return false;
}
$innerResult = $this->server->updateProperties(
$node->getOwner(),
array(
'{http://sabredav.org/ns}vcard-url' => $value,
)
);
$closureResult = false;
foreach($innerResult as $status => $props) {
if (is_array($props) && array_key_exists('{http://sabredav.org/ns}vcard-url', $props)) {
$result[$status][$meCard] = null;
$closureResult = ($status>=200 && $status<300);
}
}
return $result;
}
/**
* This functions handles REPORT requests specific to CardDAV
*
* @param string $reportName
* @param \DOMNode $dom
* @return bool
*/
public function report($reportName,$dom) {
switch($reportName) {
case '{'.self::NS_CARDDAV.'}addressbook-multiget' :
$this->addressbookMultiGetReport($dom);
return false;
case '{'.self::NS_CARDDAV.'}addressbook-query' :
$this->addressBookQueryReport($dom);
return false;
default :
return;
}
}
/**
* This function handles the addressbook-multiget REPORT.
*
* This report is used by the client to fetch the content of a series
* of urls. Effectively avoiding a lot of redundant requests.
*
* @param \DOMNode $dom
* @return void
*/
public function addressbookMultiGetReport($dom) {
$properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
$propertyList = array();
foreach($hrefElems as $elem) {
$uri = $this->server->calculateUri($elem->nodeValue);
list($propertyList[]) = $this->server->getPropertiesForPath($uri,$properties);
}
$prefer = $this->server->getHTTPPRefer();
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal']));
}
/**
* This method is triggered before a file gets updated with new content.
*
* This plugin uses this method to ensure that Card nodes receive valid
* vcard data.
*
* @param string $path
* @param DAV\IFile $node
* @param resource $data
* @return void
*/
public function beforeWriteContent($path, DAV\IFile $node, &$data) {
if (!$node instanceof ICard)
return;
$this->validateVCard($data);
}
/**
* This method is triggered before a new file is created.
*
* This plugin uses this method to ensure that Card nodes receive valid
* vcard data.
*
* @param string $path
* @param resource $data
* @param DAV\ICollection $parentNode
* @return void
*/
public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode) {
if (!$parentNode instanceof IAddressBook)
return;
$this->validateVCard($data);
}
/**
* Checks if the submitted iCalendar data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @return void
*/
protected function validateVCard(&$data) {
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Converting the data to unicode, if needed.
$data = DAV\StringUtil::ensureUTF8($data);
try {
$vobj = VObject\Reader::read($data);
} catch (VObject\ParseException $e) {
throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCARD') {
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
}
if (!isset($vobj->UID)) {
throw new DAV\Exception\BadRequest('Every vcard must have a UID.');
}
}
/**
* This function handles the addressbook-query REPORT
*
* This report is used by the client to filter an addressbook based on a
* complex query.
*
* @param \DOMNode $dom
* @return void
*/
protected function addressbookQueryReport($dom) {
$query = new AddressBookQueryParser($dom);
$query->parse();
$depth = $this->server->getHTTPDepth(0);
if ($depth==0) {
$candidateNodes = array(
$this->server->tree->getNodeForPath($this->server->getRequestUri())
);
} else {
$candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
}
$validNodes = array();
foreach($candidateNodes as $node) {
if (!$node instanceof ICard)
continue;
$blob = $node->get();
if (is_resource($blob)) {
$blob = stream_get_contents($blob);
}
if (!$this->validateFilters($blob, $query->filters, $query->test)) {
continue;
}
$validNodes[] = $node;
if ($query->limit && $query->limit <= count($validNodes)) {
// We hit the maximum number of items, we can stop now.
break;
}
}
$result = array();
foreach($validNodes as $validNode) {
if ($depth==0) {
$href = $this->server->getRequestUri();
} else {
$href = $this->server->getRequestUri() . '/' . $validNode->getName();
}
list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0);
}
$prefer = $this->server->getHTTPPRefer();
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal']));
}
/**
* Validates if a vcard makes it throught a list of filters.
*
* @param string $vcardData
* @param array $filters
* @param string $test anyof or allof (which means OR or AND)
* @return bool
*/
public function validateFilters($vcardData, array $filters, $test) {
$vcard = VObject\Reader::read($vcardData);
if (!$filters) return true;
foreach($filters as $filter) {
$isDefined = isset($vcard->{$filter['name']});
if ($filter['is-not-defined']) {
if ($isDefined) {
$success = false;
} else {
$success = true;
}
} elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
// We only need to check for existence
$success = $isDefined;
} else {
$vProperties = $vcard->select($filter['name']);
$results = array();
if ($filter['param-filters']) {
$results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
}
if ($filter['text-matches']) {
$texts = array();
foreach($vProperties as $vProperty)
$texts[] = $vProperty->value;
$results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
}
if (count($results)===1) {
$success = $results[0];
} else {
if ($filter['test'] === 'anyof') {
$success = $results[0] || $results[1];
} else {
$success = $results[0] && $results[1];
}
}
} // else
// There are two conditions where we can already determine whether
// or not this filter succeeds.
if ($test==='anyof' && $success) {
return true;
}
if ($test==='allof' && !$success) {
return false;
}
} // foreach
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* Validates if a param-filter can be applied to a specific property.
*
* @todo currently we're only validating the first parameter of the passed
* property. Any subsequence parameters with the same name are
* ignored.
* @param array $vProperties
* @param array $filters
* @param string $test
* @return bool
*/
protected function validateParamFilters(array $vProperties, array $filters, $test) {
foreach($filters as $filter) {
$isDefined = false;
foreach($vProperties as $vProperty) {
$isDefined = isset($vProperty[$filter['name']]);
if ($isDefined) break;
}
if ($filter['is-not-defined']) {
if ($isDefined) {
$success = false;
} else {
$success = true;
}
// If there's no text-match, we can just check for existence
} elseif (!$filter['text-match'] || !$isDefined) {
$success = $isDefined;
} else {
$success = false;
foreach($vProperties as $vProperty) {
// If we got all the way here, we'll need to validate the
// text-match filter.
$success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->value, $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
if ($success) break;
}
if ($filter['text-match']['negate-condition']) {
$success = !$success;
}
} // else
// There are two conditions where we can already determine whether
// or not this filter succeeds.
if ($test==='anyof' && $success) {
return true;
}
if ($test==='allof' && !$success) {
return false;
}
}
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* Validates if a text-filter can be applied to a specific property.
*
* @param array $texts
* @param array $filters
* @param string $test
* @return bool
*/
protected function validateTextMatches(array $texts, array $filters, $test) {
foreach($filters as $filter) {
$success = false;
foreach($texts as $haystack) {
$success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
// Breaking on the first match
if ($success) break;
}
if ($filter['negate-condition']) {
$success = !$success;
}
if ($success && $test==='anyof')
return true;
if (!$success && $test=='allof')
return false;
}
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* This event is triggered after webdav-properties have been retrieved.
*
* @return bool
*/
public function afterGetProperties($uri, &$properties) {
// If the request was made using the SOGO connector, we must rewrite
// the content-type property. By default SabreDAV will send back
// text/x-vcard; charset=utf-8, but for SOGO we must strip that last
// part.
if (!isset($properties[200]['{DAV:}getcontenttype']))
return;
if (strpos($this->server->httpRequest->getHeader('User-Agent'),'Thunderbird')===false) {
return;
}
if (strpos($properties[200]['{DAV:}getcontenttype'],'text/x-vcard')===0) {
$properties[200]['{DAV:}getcontenttype'] = 'text/x-vcard';
}
}
/**
* This method is used to generate HTML output for the
* Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
* can use to create new calendars.
*
* @param DAV\INode $node
* @param string $output
* @return bool
*/
public function htmlActionsPanel(DAV\INode $node, &$output) {
if (!$node instanceof UserAddressBooks)
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new address book</h3>
<input type="hidden" name="sabreAction" value="mkaddressbook" />
<label>Name (uri):</label> <input type="text" name="name" /><br />
<label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
<input type="submit" value="create" />
</form>
</td></tr>';
return false;
}
/**
* This method allows us to intercept the 'mkcalendar' sabreAction. This
* action enables the user to create new calendars from the browser plugin.
*
* @param string $uri
* @param string $action
* @param array $postVars
* @return bool
*/
public function browserPostAction($uri, $action, array $postVars) {
if ($action!=='mkaddressbook')
return;
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook');
$properties = array();
if (isset($postVars['{DAV:}displayname'])) {
$properties['{DAV:}displayname'] = $postVars['{DAV:}displayname'];
}
$this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties);
return false;
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace Sabre\CardDAV\Property;
use Sabre\DAV;
use Sabre\CardDAV;
/**
* Supported-address-data property
*
* This property is a representation of the supported-address-data property
* in the CardDAV namespace.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class SupportedAddressData extends DAV\Property {
/**
* supported versions
*
* @var array
*/
protected $supportedData = array();
/**
* Creates the property
*
* @param array|null $supportedData
*/
public function __construct(array $supportedData = null) {
if (is_null($supportedData)) {
$supportedData = array(
array('contentType' => 'text/vcard', 'version' => '3.0'),
array('contentType' => 'text/vcard', 'version' => '4.0'),
);
}
$this->supportedData = $supportedData;
}
/**
* Serializes the property in a DOMDocument
*
* @param DAV\Server $server
* @param \DOMElement $node
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $node) {
$doc = $node->ownerDocument;
$prefix =
isset($server->xmlNamespaces[CardDAV\Plugin::NS_CARDDAV]) ?
$server->xmlNamespaces[CardDAV\Plugin::NS_CARDDAV] :
'card';
foreach($this->supportedData as $supported) {
$caldata = $doc->createElementNS(CardDAV\Plugin::NS_CARDDAV, $prefix . ':address-data-type');
$caldata->setAttribute('content-type',$supported['contentType']);
$caldata->setAttribute('version',$supported['version']);
$node->appendChild($caldata);
}
}
}

View file

@ -1,260 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\DAVACL;
/**
* UserAddressBooks class
*
* The UserAddressBooks collection contains a list of addressbooks associated with a user
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class UserAddressBooks extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {
/**
* Principal uri
*
* @var array
*/
protected $principalUri;
/**
* carddavBackend
*
* @var Backend\BackendInterface
*/
protected $carddavBackend;
/**
* Constructor
*
* @param Backend\BackendInterface $carddavBackend
* @param string $principalUri
*/
public function __construct(Backend\BackendInterface $carddavBackend, $principalUri) {
$this->carddavBackend = $carddavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
list(,$name) = DAV\URLUtil::splitPath($this->principalUri);
return $name;
}
/**
* Updates the name of this object
*
* @param string $name
* @return void
*/
public function setName($name) {
throw new DAV\Exception\MethodNotAllowed();
}
/**
* Deletes this object
*
* @return void
*/
public function delete() {
throw new DAV\Exception\MethodNotAllowed();
}
/**
* Returns the last modification date
*
* @return int
*/
public function getLastModified() {
return null;
}
/**
* Creates a new file under this object.
*
* This is currently not allowed
*
* @param string $filename
* @param resource $data
* @return void
*/
public function createFile($filename, $data=null) {
throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
}
/**
* Creates a new directory under this object.
*
* This is currently not allowed.
*
* @param string $filename
* @return void
*/
public function createDirectory($filename) {
throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
}
/**
* Returns a single calendar, by name
*
* @param string $name
* @todo needs optimizing
* @return \AddressBook
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return $child;
}
throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found');
}
/**
* Returns a list of addressbooks
*
* @return array
*/
public function getChildren() {
$addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri);
$objs = array();
foreach($addressbooks as $addressbook) {
$objs[] = new AddressBook($this->carddavBackend, $addressbook);
}
return $objs;
}
/**
* Creates a new addressbook
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @return void
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
if (!in_array('{'.Plugin::NS_CARDDAV.'}addressbook',$resourceType) || count($resourceType)!==2) {
throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
}
$this->carddavBackend->createAddressBook($this->principalUri, $name, $properties);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalUri,
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalUri,
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\VObject;
/**
* VCF Exporter
*
* This plugin adds the ability to export entire address books as .vcf files.
* This is useful for clients that don't support CardDAV yet. They often do
* support vcf files.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Thomas Tanghus (http://tanghus.net/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class VCFExportPlugin extends DAV\ServerPlugin {
/**
* Reference to Server class
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* Initializes the plugin and registers event handlers
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90);
}
/**
* 'beforeMethod' event handles. This event handles intercepts GET requests ending
* with ?export
*
* @param string $method
* @param string $uri
* @return bool
*/
public function beforeMethod($method, $uri) {
if ($method!='GET') return;
if ($this->server->httpRequest->getQueryString()!='export') return;
// splitting uri
list($uri) = explode('?',$uri,2);
$node = $this->server->tree->getNodeForPath($uri);
if (!($node instanceof IAddressBook)) return;
// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
}
$this->server->httpResponse->setHeader('Content-Type','text/directory');
$this->server->httpResponse->sendStatus(200);
$nodes = $this->server->getPropertiesForPath($uri, array(
'{' . Plugin::NS_CARDDAV . '}address-data',
),1);
$this->server->httpResponse->sendBody($this->generateVCF($nodes));
// Returning false to break the event chain
return false;
}
/**
* Merges all vcard objects, and builds one big vcf export
*
* @param array $nodes
* @return string
*/
public function generateVCF(array $nodes) {
$output = "";
foreach($nodes as $node) {
if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) {
continue;
}
$nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data'];
// Parsing this node so VObject can clean up the output.
$output .=
VObject\Reader::read($nodeData)->serialize();
}
return $output;
}
}

View file

@ -1,26 +0,0 @@
<?php
namespace Sabre\CardDAV;
/**
* Version Class
*
* This class contains the Sabre\CardDAV version information
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Version {
/**
* Full version number
*/
const VERSION = '1.8.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'stable';
}

View file

@ -1,87 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
use Sabre\DAV;
use Sabre\HTTP;
/**
* HTTP Basic authentication backend class
*
* This class can be used by authentication objects wishing to use HTTP Basic
* Most of the digest logic is handled, implementors just need to worry about
* the validateUserPass method.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author James David Low (http://jameslow.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class AbstractBasic implements BackendInterface {
/**
* This variable holds the currently logged in username.
*
* @var string|null
*/
protected $currentUser;
/**
* Validates a username and password
*
* This method should return true or false depending on if login
* succeeded.
*
* @param string $username
* @param string $password
* @return bool
*/
abstract protected function validateUserPass($username, $password);
/**
* Returns information about the currently logged in username.
*
* If nobody is currently logged in, this method should return null.
*
* @return string|null
*/
public function getCurrentUser() {
return $this->currentUser;
}
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param DAV\Server $server
* @param string $realm
* @throws DAV\Exception\NotAuthenticated
* @return bool
*/
public function authenticate(DAV\Server $server, $realm) {
$auth = new HTTP\BasicAuth();
$auth->setHTTPRequest($server->httpRequest);
$auth->setHTTPResponse($server->httpResponse);
$auth->setRealm($realm);
$userpass = $auth->getUserPass();
if (!$userpass) {
$auth->requireLogin();
throw new DAV\Exception\NotAuthenticated('No basic authentication headers were found');
}
// Authenticates the user
if (!$this->validateUserPass($userpass[0],$userpass[1])) {
$auth->requireLogin();
throw new DAV\Exception\NotAuthenticated('Username or password does not match');
}
$this->currentUser = $userpass[0];
return true;
}
}

View file

@ -1,101 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
use Sabre\HTTP;
use Sabre\DAV;
/**
* HTTP Digest authentication backend class
*
* This class can be used by authentication objects wishing to use HTTP Digest
* Most of the digest logic is handled, implementors just need to worry about
* the getDigestHash method
*
* @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 AbstractDigest implements BackendInterface {
/**
* This variable holds the currently logged in username.
*
* @var array|null
*/
protected $currentUser;
/**
* Returns a users digest hash based on the username and realm.
*
* If the user was not known, null must be returned.
*
* @param string $realm
* @param string $username
* @return string|null
*/
abstract public function getDigestHash($realm, $username);
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param DAV\Server $server
* @param string $realm
* @throws DAV\Exception\NotAuthenticated
* @return bool
*/
public function authenticate(DAV\Server $server, $realm) {
$digest = new HTTP\DigestAuth();
// Hooking up request and response objects
$digest->setHTTPRequest($server->httpRequest);
$digest->setHTTPResponse($server->httpResponse);
$digest->setRealm($realm);
$digest->init();
$username = $digest->getUsername();
// No username was given
if (!$username) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('No digest authentication headers were found');
}
$hash = $this->getDigestHash($realm, $username);
// If this was false, the user account didn't exist
if ($hash===false || is_null($hash)) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('The supplied username was not on file');
}
if (!is_string($hash)) {
throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
}
// If this was false, the password or part of the hash was incorrect.
if (!$digest->validateA1($hash)) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('Incorrect username');
}
$this->currentUser = $username;
return true;
}
/**
* Returns the currently logged in username.
*
* @return string|null
*/
public function getCurrentUser() {
return $this->currentUser;
}
}

View file

@ -1,63 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
use Sabre\DAV;
/**
* Apache authenticator
*
* This authentication backend assumes that authentication has been
* configured in apache, rather than within SabreDAV.
*
* Make sure apache is properly configured for this to work.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Apache implements BackendInterface {
/**
* Current apache user
*
* @var string
*/
protected $remoteUser;
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param DAV\Server $server
* @param string $realm
* @return bool
*/
public function authenticate(DAV\Server $server, $realm) {
$remoteUser = $server->httpRequest->getRawServerValue('REMOTE_USER');
if (is_null($remoteUser)) {
throw new DAV\Exception('We did not receive the $_SERVER[REMOTE_USER] property. This means that apache might have been misconfigured');
}
$this->remoteUser = $remoteUser;
return true;
}
/**
* Returns information about the currently logged in user.
*
* If nobody is currently logged in, this method should return null.
*
* @return array|null
*/
public function getCurrentUser() {
return $this->remoteUser;
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
/**
* This is the base class for any authentication object.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface BackendInterface {
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param \Sabre\DAV\Server $server
* @param string $realm
* @return bool
*/
function authenticate(\Sabre\DAV\Server $server,$realm);
/**
* Returns information about the currently logged in username.
*
* If nobody is currently logged in, this method should return null.
*
* @return string|null
*/
function getCurrentUser();
}

View file

@ -1,77 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
use Sabre\DAV;
/**
* This is an authentication backend that uses a file to manage passwords.
*
* The backend file must conform to Apache's htdigest format
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class File extends AbstractDigest {
/**
* List of users
*
* @var array
*/
protected $users = array();
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
*
* @param string|null $filename
*/
public function __construct($filename=null) {
if (!is_null($filename))
$this->loadFile($filename);
}
/**
* Loads an htdigest-formatted file. This method can be called multiple times if
* more than 1 file is used.
*
* @param string $filename
* @return void
*/
public function loadFile($filename) {
foreach(file($filename,FILE_IGNORE_NEW_LINES) as $line) {
if (substr_count($line, ":") !== 2)
throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons');
list($username,$realm,$A1) = explode(':',$line);
if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1))
throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash');
$this->users[$realm . ':' . $username] = $A1;
}
}
/**
* Returns a users' information
*
* @param string $realm
* @param string $username
* @return string
*/
public function getDigestHash($realm, $username) {
return isset($this->users[$realm . ':' . $username])?$this->users[$realm . ':' . $username]:false;
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace Sabre\DAV\Auth\Backend;
/**
* This is an authentication backend that uses a file to manage passwords.
*
* The backend file must conform to Apache's htdigest format
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class PDO extends AbstractDigest {
/**
* Reference to PDO connection
*
* @var PDO
*/
protected $pdo;
/**
* PDO table name we'll be using
*
* @var string
*/
protected $tableName;
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
*
* @param PDO $pdo
* @param string $tableName The PDO table name to use
*/
public function __construct(\PDO $pdo, $tableName = 'users') {
$this->pdo = $pdo;
$this->tableName = $tableName;
}
/**
* Returns the digest hash for a user.
*
* @param string $realm
* @param string $username
* @return string|null
*/
public function getDigestHash($realm,$username) {
$stmt = $this->pdo->prepare('SELECT username, digesta1 FROM '.$this->tableName.' WHERE username = ?');
$stmt->execute(array($username));
$result = $stmt->fetchAll();
if (!count($result)) return;
return $result[0]['digesta1'];
}
}

View file

@ -1,112 +0,0 @@
<?php
namespace Sabre\DAV\Auth;
use Sabre\DAV;
/**
* This plugin provides Authentication for a WebDAV server.
*
* It relies on a Backend object, which provides user information.
*
* Additionally, it provides support for:
* * {DAV:}current-user-principal property from RFC5397
* * {DAV:}principal-collection-set property from RFC3744
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Plugin extends DAV\ServerPlugin {
/**
* Reference to main server object
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* Authentication backend
*
* @var Backend\BackendInterface
*/
protected $authBackend;
/**
* The authentication realm.
*
* @var string
*/
private $realm;
/**
* __construct
*
* @param Backend\BackendInterface $authBackend
* @param string $realm
*/
public function __construct(Backend\BackendInterface $authBackend, $realm) {
$this->authBackend = $authBackend;
$this->realm = $realm;
}
/**
* Initializes the plugin. This function is automatically called by the server
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),10);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'auth';
}
/**
* Returns the current users' principal uri.
*
* If nobody is logged in, this will return null.
*
* @return string|null
*/
public function getCurrentUser() {
$userInfo = $this->authBackend->getCurrentUser();
if (!$userInfo) return null;
return $userInfo;
}
/**
* This method is called before any HTTP method and forces users to be authenticated
*
* @param string $method
* @param string $uri
* @throws Sabre\DAV\Exception\NotAuthenticated
* @return bool
*/
public function beforeMethod($method, $uri) {
$this->authBackend->authenticate($this->server,$this->realm);
}
}

View file

@ -1,99 +0,0 @@
<?php
namespace Sabre\DAV\Browser;
use Sabre\DAV;
/**
* GuessContentType plugin
*
* A lot of the built-in File objects just return application/octet-stream
* as a content-type by default. This is a problem for some clients, because
* they expect a correct contenttype.
*
* There's really no accurate, fast and portable way to determine the contenttype
* so this extension does what the rest of the world does, and guesses it based
* on the file extension.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class GuessContentType extends DAV\ServerPlugin {
/**
* List of recognized file extensions
*
* Feel free to add more
*
* @var array
*/
public $extensionMap = array(
// images
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
// groupware
'ics' => 'text/calendar',
'vcf' => 'text/x-vcard',
// text
'txt' => 'text/plain',
);
/**
* Initializes the plugin
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
// Using a relatively low priority (200) to allow other extensions
// to set the content-type first.
$server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'),200);
}
/**
* Handler for teh afterGetProperties event
*
* @param string $path
* @param array $properties
* @return void
*/
public function afterGetProperties($path, &$properties) {
if (array_key_exists('{DAV:}getcontenttype', $properties[404])) {
list(, $fileName) = DAV\URLUtil::splitPath($path);
$contentType = $this->getContentType($fileName);
if ($contentType) {
$properties[200]['{DAV:}getcontenttype'] = $contentType;
unset($properties[404]['{DAV:}getcontenttype']);
}
}
}
/**
* Simple method to return the contenttype
*
* @param string $fileName
* @return string
*/
protected function getContentType($fileName) {
// Just grabbing the extension
$extension = strtolower(substr($fileName,strrpos($fileName,'.')+1));
if (isset($this->extensionMap[$extension]))
return $this->extensionMap[$extension];
}
}

View file

@ -1,57 +0,0 @@
<?php
namespace Sabre\DAV\Browser;
use Sabre\DAV;
/**
* This is a simple plugin that will map any GET request for non-files to
* PROPFIND allprops-requests.
*
* This should allow easy debugging of PROPFIND
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class MapGetToPropFind extends DAV\ServerPlugin {
/**
* reference to server class
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* Initializes the plugin and subscribes to events
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor'));
}
/**
* This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpGetInterceptor($method, $uri) {
if ($method!='GET') return true;
$node = $this->server->tree->getNodeForPath($uri);
if ($node instanceof DAV\IFile) return;
$this->server->invokeMethod('PROPFIND',$uri);
return false;
}
}

View file

@ -1,491 +0,0 @@
<?php
namespace Sabre\DAV\Browser;
use Sabre\DAV;
/**
* Browser Plugin
*
* This plugin provides a html representation, so that a WebDAV server may be accessed
* using a browser.
*
* The class intercepts GET requests to collection resources and generates a simple
* html index.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Plugin extends DAV\ServerPlugin {
/**
* List of default icons for nodes.
*
* This is an array with class / interface names as keys, and asset names
* as values.
*
* The evaluation order is reversed. The last item in the list gets
* precendence.
*
* @var array
*/
public $iconMap = array(
'Sabre\\DAV\\IFile' => 'icons/file',
'Sabre\\DAV\\ICollection' => 'icons/collection',
'Sabre\\DAVACL\\IPrincipal' => 'icons/principal',
'Sabre\\CalDAV\\ICalendar' => 'icons/calendar',
'Sabre\\CardDAV\\IAddressBook' => 'icons/addressbook',
'Sabre\\CardDAV\\ICard' => 'icons/card',
);
/**
* The file extension used for all icons
*
* @var string
*/
public $iconExtension = '.png';
/**
* reference to server class
*
* @var Sabre\DAV\Server
*/
protected $server;
/**
* enablePost turns on the 'actions' panel, which allows people to create
* folders and upload files straight from a browser.
*
* @var bool
*/
protected $enablePost = true;
/**
* By default the browser plugin will generate a favicon and other images.
* To turn this off, set this property to false.
*
* @var bool
*/
protected $enableAssets = true;
/**
* Creates the object.
*
* By default it will allow file creation and uploads.
* Specify the first argument as false to disable this
*
* @param bool $enablePost
* @param bool $enableAssets
*/
public function __construct($enablePost=true, $enableAssets = true) {
$this->enablePost = $enablePost;
$this->enableAssets = $enableAssets;
}
/**
* Initializes the plugin and subscribes to events
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor'));
$this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200);
if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler'));
}
/**
* This method intercepts GET requests to collections and returns the html
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpGetInterceptor($method, $uri) {
if ($method !== 'GET') return true;
// We're not using straight-up $_GET, because we want everything to be
// unit testable.
$getVars = array();
parse_str($this->server->httpRequest->getQueryString(), $getVars);
if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) {
$this->serveAsset($getVars['assetName']);
return false;
}
try {
$node = $this->server->tree->getNodeForPath($uri);
} catch (DAV\Exception\NotFound $e) {
// We're simply stopping when the file isn't found to not interfere
// with other plugins.
return;
}
if ($node instanceof DAV\IFile)
return;
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8');
$this->server->httpResponse->sendBody(
$this->generateDirectoryIndex($uri)
);
return false;
}
/**
* Handles POST requests for tree operations.
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpPOSTHandler($method, $uri) {
if ($method!='POST') return;
$contentType = $this->server->httpRequest->getHeader('Content-Type');
list($contentType) = explode(';', $contentType);
if ($contentType !== 'application/x-www-form-urlencoded' &&
$contentType !== 'multipart/form-data') {
return;
}
$postVars = $this->server->httpRequest->getPostVars();
if (!isset($postVars['sabreAction']))
return;
if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) {
switch($postVars['sabreAction']) {
case 'mkcol' :
if (isset($postVars['name']) && trim($postVars['name'])) {
// Using basename() because we won't allow slashes
list(, $folderName) = DAV\URLUtil::splitPath(trim($postVars['name']));
$this->server->createDirectory($uri . '/' . $folderName);
}
break;
case 'put' :
if ($_FILES) $file = current($_FILES);
else break;
list(, $newName) = DAV\URLUtil::splitPath(trim($file['name']));
if (isset($postVars['name']) && trim($postVars['name']))
$newName = trim($postVars['name']);
// Making sure we only have a 'basename' component
list(, $newName) = DAV\URLUtil::splitPath($newName);
if (is_uploaded_file($file['tmp_name'])) {
$this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r'));
}
break;
}
}
$this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri());
$this->server->httpResponse->sendStatus(302);
return false;
}
/**
* Escapes a string for html.
*
* @param string $value
* @return string
*/
public function escapeHTML($value) {
return htmlspecialchars($value,ENT_QUOTES,'UTF-8');
}
/**
* Generates the html directory index for a given url
*
* @param string $path
* @return string
*/
public function generateDirectoryIndex($path) {
$version = '';
if (DAV\Server::$exposeVersion) {
$version = DAV\Version::VERSION ."-". DAV\Version::STABILITY;
}
$html = "<html>
<head>
<title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title>
<style type=\"text/css\">
body { Font-family: arial}
h1 { font-size: 150% }
</style>
";
if ($this->enableAssets) {
$html.='<link rel="shortcut icon" href="'.$this->getAssetUrl('favicon.ico').'" type="image/vnd.microsoft.icon" />';
}
$html .= "</head>
<body>
<h1>Index for " . $this->escapeHTML($path) . "/</h1>
<table>
<tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr>
<tr><td colspan=\"5\"><hr /></td></tr>";
$files = $this->server->getPropertiesForPath($path,array(
'{DAV:}displayname',
'{DAV:}resourcetype',
'{DAV:}getcontenttype',
'{DAV:}getcontentlength',
'{DAV:}getlastmodified',
),1);
$parent = $this->server->tree->getNodeForPath($path);
if ($path) {
list($parentUri) = DAV\URLUtil::splitPath($path);
$fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
$icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':'';
$html.= "<tr>
<td>$icon</td>
<td><a href=\"{$fullPath}\">..</a></td>
<td>[parent]</td>
<td></td>
<td></td>
</tr>";
}
foreach($files as $file) {
// This is the current directory, we can skip it
if (rtrim($file['href'],'/')==$path) continue;
list(, $name) = DAV\URLUtil::splitPath($file['href']);
$type = null;
if (isset($file[200]['{DAV:}resourcetype'])) {
$type = $file[200]['{DAV:}resourcetype']->getValue();
// resourcetype can have multiple values
if (!is_array($type)) $type = array($type);
foreach($type as $k=>$v) {
// Some name mapping is preferred
switch($v) {
case '{DAV:}collection' :
$type[$k] = 'Collection';
break;
case '{DAV:}principal' :
$type[$k] = 'Principal';
break;
case '{urn:ietf:params:xml:ns:carddav}addressbook' :
$type[$k] = 'Addressbook';
break;
case '{urn:ietf:params:xml:ns:caldav}calendar' :
$type[$k] = 'Calendar';
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' :
$type[$k] = 'Schedule Inbox';
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' :
$type[$k] = 'Schedule Outbox';
break;
case '{http://calendarserver.org/ns/}calendar-proxy-read' :
$type[$k] = 'Proxy-Read';
break;
case '{http://calendarserver.org/ns/}calendar-proxy-write' :
$type[$k] = 'Proxy-Write';
break;
}
}
$type = implode(', ', $type);
}
// If no resourcetype was found, we attempt to use
// the contenttype property
if (!$type && isset($file[200]['{DAV:}getcontenttype'])) {
$type = $file[200]['{DAV:}getcontenttype'];
}
if (!$type) $type = 'Unknown';
$size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:'';
$lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM):'';
$fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/'));
$displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name;
$displayName = $this->escapeHTML($displayName);
$type = $this->escapeHTML($type);
$icon = '';
if ($this->enableAssets) {
$node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name);
foreach(array_reverse($this->iconMap) as $class=>$iconName) {
if ($node instanceof $class) {
$icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>';
break;
}
}
}
$html.= "<tr>
<td>$icon</td>
<td><a href=\"{$fullPath}\">{$displayName}</a></td>
<td>{$type}</td>
<td>{$size}</td>
<td>{$lastmodified}</td>
</tr>";
}
$html.= "<tr><td colspan=\"5\"><hr /></td></tr>";
$output = '';
if ($this->enablePost) {
$this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output));
}
$html.=$output;
$html.= "</table>
<address>Generated by SabreDAV " . $version . " (c)2007-2012 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address>
</body>
</html>";
return $html;
}
/**
* This method is used to generate the 'actions panel' output for
* collections.
*
* This specifically generates the interfaces for creating new files, and
* creating new directories.
*
* @param DAV\INode $node
* @param mixed $output
* @return void
*/
public function htmlActionsPanel(DAV\INode $node, &$output) {
if (!$node instanceof DAV\ICollection)
return;
// We also know fairly certain that if an object is a non-extended
// SimpleCollection, we won't need to show the panel either.
if (get_class($node)==='Sabre\\DAV\\SimpleCollection')
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new folder</h3>
<input type="hidden" name="sabreAction" value="mkcol" />
Name: <input type="text" name="name" /><br />
<input type="submit" value="create" />
</form>
<form method="post" action="" enctype="multipart/form-data">
<h3>Upload file</h3>
<input type="hidden" name="sabreAction" value="put" />
Name (optional): <input type="text" name="name" /><br />
File: <input type="file" name="file" /><br />
<input type="submit" value="upload" />
</form>
</td></tr>';
}
/**
* This method takes a path/name of an asset and turns it into url
* suiteable for http access.
*
* @param string $assetName
* @return string
*/
protected function getAssetUrl($assetName) {
return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
}
/**
* This method returns a local pathname to an asset.
*
* @param string $assetName
* @return string
*/
protected function getLocalAssetPath($assetName) {
// Making sure people aren't trying to escape from the base path.
$assetSplit = explode('/', $assetName);
if (in_array('..',$assetSplit)) {
throw new DAV\Exception('Incorrect asset path');
}
$path = __DIR__ . '/assets/' . $assetName;
return $path;
}
/**
* This method reads an asset from disk and generates a full http response.
*
* @param string $assetName
* @return void
*/
protected function serveAsset($assetName) {
$assetPath = $this->getLocalAssetPath($assetName);
if (!file_exists($assetPath)) {
throw new DAV\Exception\NotFound('Could not find an asset with this name');
}
// Rudimentary mime type detection
switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) {
case 'ico' :
$mime = 'image/vnd.microsoft.icon';
break;
case 'png' :
$mime = 'image/png';
break;
default:
$mime = 'application/octet-stream';
break;
}
$this->server->httpResponse->setHeader('Content-Type', $mime);
$this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
$this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->sendBody(fopen($assetPath,'r'));
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -1,541 +0,0 @@
<?php
namespace Sabre\DAV;
/**
* SabreDAV DAV client
*
* This client wraps around Curl to provide a convenient API to a WebDAV
* server.
*
* NOTE: This class is experimental, it's api will likely change in the future.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Client {
/**
* The propertyMap is a key-value array.
*
* If you use the propertyMap, any {DAV:}multistatus responses with the
* proeprties listed in this array, will automatically be mapped to a
* respective class.
*
* The {DAV:}resourcetype property is automatically added. This maps to
* Sabre\DAV\Property\ResourceType
*
* @var array
*/
public $propertyMap = array();
protected $baseUri;
protected $userName;
protected $password;
protected $proxy;
protected $trustedCertificates;
/**
* Basic authentication
*/
const AUTH_BASIC = 1;
/**
* Digest authentication
*/
const AUTH_DIGEST = 2;
/**
* The authentication type we're using.
*
* This is a bitmask of AUTH_BASIC and AUTH_DIGEST.
*
* If DIGEST is used, the client makes 1 extra request per request, to get
* the authentication tokens.
*
* @var int
*/
protected $authType;
/**
* Constructor
*
* Settings are provided through the 'settings' argument. The following
* settings are supported:
*
* * baseUri
* * userName (optional)
* * password (optional)
* * proxy (optional)
*
* @param array $settings
*/
public function __construct(array $settings) {
if (!isset($settings['baseUri'])) {
throw new \InvalidArgumentException('A baseUri must be provided');
}
$validSettings = array(
'baseUri',
'userName',
'password',
'proxy',
);
foreach($validSettings as $validSetting) {
if (isset($settings[$validSetting])) {
$this->$validSetting = $settings[$validSetting];
}
}
if (isset($settings['authType'])) {
$this->authType = $settings['authType'];
} else {
$this->authType = self::AUTH_BASIC | self::AUTH_DIGEST;
}
$this->propertyMap['{DAV:}resourcetype'] = 'Sabre\\DAV\\Property\\ResourceType';
}
/**
* Add trusted root certificates to the webdav client.
*
* The parameter certificates should be a absulute path to a file
* which contains all trusted certificates
*
* @param string $certificates
*/
public function addTrustedCertificates($certificates) {
$this->trustedCertificates = $certificates;
}
/**
* Does a PROPFIND request
*
* The list of requested properties must be specified as an array, in clark
* notation.
*
* The returned array will contain a list of filenames as keys, and
* properties as values.
*
* The properties array will contain the list of properties. Only properties
* that are actually returned from the server (without error) will be
* returned, anything else is discarded.
*
* Depth should be either 0 or 1. A depth of 1 will cause a request to be
* made to the server to also return all child resources.
*
* @param string $url
* @param array $properties
* @param int $depth
* @return array
*/
public function propFind($url, array $properties, $depth = 0) {
$body = '<?xml version="1.0"?>' . "\n";
$body.= '<d:propfind xmlns:d="DAV:">' . "\n";
$body.= ' <d:prop>' . "\n";
foreach($properties as $property) {
list(
$namespace,
$elementName
) = XMLUtil::parseClarkNotation($property);
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . ' />' . "\n";
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n";
}
}
$body.= ' </d:prop>' . "\n";
$body.= '</d:propfind>';
$response = $this->request('PROPFIND', $url, $body, array(
'Depth' => $depth,
'Content-Type' => 'application/xml'
));
$result = $this->parseMultiStatus($response['body']);
// If depth was 0, we only return the top item
if ($depth===0) {
reset($result);
$result = current($result);
return isset($result[200])?$result[200]:array();
}
$newResult = array();
foreach($result as $href => $statusList) {
$newResult[$href] = isset($statusList[200])?$statusList[200]:array();
}
return $newResult;
}
/**
* Updates a list of properties on the server
*
* The list of properties must have clark-notation properties for the keys,
* and the actual (string) value for the value. If the value is null, an
* attempt is made to delete the property.
*
* @todo Must be building the request using the DOM, and does not yet
* support complex properties.
* @param string $url
* @param array $properties
* @return void
*/
public function propPatch($url, array $properties) {
$body = '<?xml version="1.0"?>' . "\n";
$body.= '<d:propertyupdate xmlns:d="DAV:">' . "\n";
foreach($properties as $propName => $propValue) {
list(
$namespace,
$elementName
) = XMLUtil::parseClarkNotation($propName);
if ($propValue === null) {
$body.="<d:remove><d:prop>\n";
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . ' />' . "\n";
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n";
}
$body.="</d:prop></d:remove>\n";
} else {
$body.="<d:set><d:prop>\n";
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . '>';
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\">";
}
// Shitty.. i know
$body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8');
if ($namespace === 'DAV:') {
$body.='</d:' . $elementName . '>' . "\n";
} else {
$body.="</x:" . $elementName . ">\n";
}
$body.="</d:prop></d:set>\n";
}
}
$body.= '</d:propertyupdate>';
$this->request('PROPPATCH', $url, $body, array(
'Content-Type' => 'application/xml'
));
}
/**
* Performs an HTTP options request
*
* This method returns all the features from the 'DAV:' header as an array.
* If there was no DAV header, or no contents this method will return an
* empty array.
*
* @return array
*/
public function options() {
$result = $this->request('OPTIONS');
if (!isset($result['headers']['dav'])) {
return array();
}
$features = explode(',', $result['headers']['dav']);
foreach($features as &$v) {
$v = trim($v);
}
return $features;
}
/**
* Performs an actual HTTP request, and returns the result.
*
* If the specified url is relative, it will be expanded based on the base
* url.
*
* The returned array contains 3 keys:
* * body - the response body
* * httpCode - a HTTP code (200, 404, etc)
* * headers - a list of response http headers. The header names have
* been lowercased.
*
* @param string $method
* @param string $url
* @param string $body
* @param array $headers
* @return array
*/
public function request($method, $url = '', $body = null, $headers = array()) {
$url = $this->getAbsoluteUrl($url);
$curlSettings = array(
CURLOPT_RETURNTRANSFER => true,
// Return headers as part of the response
CURLOPT_HEADER => true,
CURLOPT_POSTFIELDS => $body,
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
);
if($this->trustedCertificates) {
$curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates;
}
switch ($method) {
case 'HEAD' :
// do not read body with HEAD requests (this is neccessary because cURL does not ignore the body with HEAD
// requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP
// specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with
// ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the
// response body
$curlSettings[CURLOPT_NOBODY] = true;
$curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
break;
default:
$curlSettings[CURLOPT_CUSTOMREQUEST] = $method;
break;
}
// Adding HTTP headers
$nHeaders = array();
foreach($headers as $key=>$value) {
$nHeaders[] = $key . ': ' . $value;
}
$curlSettings[CURLOPT_HTTPHEADER] = $nHeaders;
if ($this->proxy) {
$curlSettings[CURLOPT_PROXY] = $this->proxy;
}
if ($this->userName && $this->authType) {
$curlType = 0;
if ($this->authType & self::AUTH_BASIC) {
$curlType |= CURLAUTH_BASIC;
}
if ($this->authType & self::AUTH_DIGEST) {
$curlType |= CURLAUTH_DIGEST;
}
$curlSettings[CURLOPT_HTTPAUTH] = $curlType;
$curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password;
}
list(
$response,
$curlInfo,
$curlErrNo,
$curlError
) = $this->curlRequest($url, $curlSettings);
$headerBlob = substr($response, 0, $curlInfo['header_size']);
$response = substr($response, $curlInfo['header_size']);
// In the case of 100 Continue, or redirects we'll have multiple lists
// of headers for each separate HTTP response. We can easily split this
// because they are separated by \r\n\r\n
$headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
// We only care about the last set of headers
$headerBlob = $headerBlob[count($headerBlob)-1];
// Splitting headers
$headerBlob = explode("\r\n", $headerBlob);
$headers = array();
foreach($headerBlob as $header) {
$parts = explode(':', $header, 2);
if (count($parts)==2) {
$headers[strtolower(trim($parts[0]))] = trim($parts[1]);
}
}
$response = array(
'body' => $response,
'statusCode' => $curlInfo['http_code'],
'headers' => $headers
);
if ($curlErrNo) {
throw new Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')');
}
if ($response['statusCode']>=400) {
switch ($response['statusCode']) {
case 400 :
throw new Exception\BadRequest('Bad request');
case 401 :
throw new Exception\NotAuthenticated('Not authenticated');
case 402 :
throw new Exception\PaymentRequired('Payment required');
case 403 :
throw new Exception\Forbidden('Forbidden');
case 404:
throw new Exception\NotFound('Resource not found.');
case 405 :
throw new Exception\MethodNotAllowed('Method not allowed');
case 409 :
throw new Exception\Conflict('Conflict');
case 412 :
throw new Exception\PreconditionFailed('Precondition failed');
case 416 :
throw new Exception\RequestedRangeNotSatisfiable('Requested Range Not Satisfiable');
case 500 :
throw new Exception('Internal server error');
case 501 :
throw new Exception\NotImplemented('Not Implemented');
case 507 :
throw new Exception\InsufficientStorage('Insufficient storage');
default:
throw new Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')');
}
}
return $response;
}
/**
* Wrapper for all curl functions.
*
* The only reason this was split out in a separate method, is so it
* becomes easier to unittest.
*
* @param string $url
* @param array $settings
* @return array
*/
// @codeCoverageIgnoreStart
protected function curlRequest($url, $settings) {
$curl = curl_init($url);
curl_setopt_array($curl, $settings);
return array(
curl_exec($curl),
curl_getinfo($curl),
curl_errno($curl),
curl_error($curl)
);
}
// @codeCoverageIgnoreEnd
/**
* Returns the full url based on the given url (which may be relative). All
* urls are expanded based on the base url as given by the server.
*
* @param string $url
* @return string
*/
protected function getAbsoluteUrl($url) {
// If the url starts with http:// or https://, the url is already absolute.
if (preg_match('/^http(s?):\/\//', $url)) {
return $url;
}
// If the url starts with a slash, we must calculate the url based off
// the root of the base url.
if (strpos($url,'/') === 0) {
$parts = parse_url($this->baseUri);
return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
}
// Otherwise...
return $this->baseUri . $url;
}
/**
* Parses a WebDAV multistatus response body
*
* This method returns an array with the following structure
*
* array(
* 'url/to/resource' => array(
* '200' => array(
* '{DAV:}property1' => 'value1',
* '{DAV:}property2' => 'value2',
* ),
* '404' => array(
* '{DAV:}property1' => null,
* '{DAV:}property2' => null,
* ),
* )
* 'url/to/resource2' => array(
* .. etc ..
* )
* )
*
*
* @param string $body xml body
* @return array
*/
public function parseMultiStatus($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', 'DAV:');
$propResult = array();
foreach($responseXML->xpath('d:response') as $response) {
$response->registerXPathNamespace('d', 'DAV:');
$href = $response->xpath('d:href');
$href = (string)$href[0];
$properties = array();
foreach($response->xpath('d:propstat') as $propStat) {
$propStat->registerXPathNamespace('d', 'DAV:');
$status = $propStat->xpath('d:status');
list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3);
$properties[$statusCode] = XMLUtil::parseProperties(dom_import_simplexml($propStat), $this->propertyMap);
}
$propResult[$href] = $properties;
}
return $propResult;
}
}

View file

@ -1,110 +0,0 @@
<?php
namespace Sabre\DAV;
/**
* Collection class
*
* This is a helper class, that should aid in getting collections classes setup.
* Most of its methods are implemented, and throw permission denied exceptions
*
* @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 Collection extends Node implements ICollection {
/**
* Returns a child object, by its name.
*
* This method makes use of the getChildren method to grab all the child
* nodes, and compares the name.
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
* @throws Exception\NotFound
* @return INode
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($child->getName()==$name) return $child;
}
throw new Exception\NotFound('File not found: ' . $name);
}
/**
* Checks is a child-node exists.
*
* It is generally a good idea to try and override this. Usually it can be optimized.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
try {
$this->getChild($name);
return true;
} catch(Exception\NotFound $e) {
return false;
}
}
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After succesful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
public function createFile($name, $data = null) {
throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
}
/**
* Creates a new subdirectory
*
* @param string $name
* @throws Exception\Forbidden
* @return void
*/
public function createDirectory($name) {
throw new Exception\Forbidden('Permission denied to create directory');
}
}

View file

@ -1,64 +0,0 @@
<?php
/**
* SabreDAV base exception
*
* This is SabreDAV's base exception file, use this to implement your own exception.
*
* @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
*/
namespace Sabre\DAV;
/**
* Main Exception class.
*
* This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred.
* The default for this is 500.
*
* This class also allows you to generate custom xml data for your exceptions. This will be displayed
* in the 'error' element in the failing response.
*/
class Exception extends \Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 500;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(Server $server,\DOMElement $errorNode) {
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @param Server $server
* @return array
*/
public function getHTTPHeaders(Server $server) {
return array();
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* BadRequest
*
* The BadRequest is thrown when the user submitted an invalid HTTP request
* BadRequest
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class BadRequest extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 400;
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* Conflict
*
* A 409 Conflict is thrown when a user tried to make a directory over an existing
* file or in a parent directory that doesn't exist.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Conflict extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 409;
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* ConflictingLock
*
* Similar to the Locked exception, this exception thrown when a LOCK request
* was made, on a resource which was already locked
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ConflictingLock extends Locked {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode) {
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:no-conflicting-lock');
$errorNode->appendChild($error);
if (!is_object($this->lock)) var_dump($this->lock);
$error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri));
}
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* FileNotFound
*
* Deprecated: Warning, this class is deprecated and will be removed in a
* future version of SabreDAV. Please use Sabre\DAV\Exception\NotFound instead.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @deprecated Use Sabre\DAV\Exception\NotFound instead
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class FileNotFound extends NotFound {
}

View file

@ -1,27 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* Forbidden
*
* This exception is thrown whenever a user tries to do an operation he's not allowed to
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Forbidden extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 403;
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* InsufficientStorage
*
* This Exception can be thrown, when for example a harddisk is full or a quota is exceeded
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class InsufficientStorage extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 507;
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* InvalidResourceType
*
* This exception is thrown when the user tried to create a new collection, with
* a special resourcetype value that was not recognized by the server.
*
* See RFC5689 section 3.3
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class InvalidResourceType extends Forbidden {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:valid-resourcetype');
$errorNode->appendChild($error);
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* LockTokenMatchesRequestUri
*
* This exception is thrown by UNLOCK if a supplied lock-token is invalid
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class LockTokenMatchesRequestUri extends Conflict {
/**
* Creates the exception
*/
public function __construct() {
$this->message = 'The locktoken supplied does not match any locks on this entity';
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-matches-request-uri');
$errorNode->appendChild($error);
}
}

View file

@ -1,69 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Locked
*
* The 423 is thrown when a client tried to access a resource that was locked, without supplying a valid lock token
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Locked extends DAV\Exception {
/**
* Lock information
*
* @var Sabre\DAV\Locks\LockInfo
*/
protected $lock;
/**
* Creates the exception
*
* A LockInfo object should be passed if the user should be informed
* which lock actually has the file locked.
*
* @param DAV\Locks\LockInfo $lock
*/
public function __construct(DAV\Locks\LockInfo $lock = null) {
$this->lock = $lock;
}
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 423;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $errorNode) {
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-submitted');
$errorNode->appendChild($error);
if (!is_object($this->lock)) var_dump($this->lock);
$error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri));
}
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* MethodNotAllowed
*
* The 405 is thrown when a client tried to create a directory on an already existing directory
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class MethodNotAllowed extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 405;
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @param \Sabre\DAV\Server $server
* @return array
*/
public function getHTTPHeaders(\Sabre\DAV\Server $server) {
$methods = $server->getAllowedMethods($server->getRequestUri());
return array(
'Allow' => strtoupper(implode(', ',$methods)),
);
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* NotAuthenticated
*
* This exception is thrown when the client did not provide valid
* authentication credentials.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class NotAuthenticated extends DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 401;
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* NotFound
*
* This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class NotFound extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 404;
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
/**
* NotImplemented
*
* This exception is thrown when the client tried to call an unsupported HTTP method or other feature
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class NotImplemented extends \Sabre\DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 501;
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Payment Required
*
* The PaymentRequired exception may be thrown in a case where a user must pay
* to access a certain resource or operation.
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class PaymentRequired extends DAV\Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 402;
}
}

View file

@ -1,71 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* PreconditionFailed
*
* This exception is normally thrown when a client submitted a conditional request,
* like for example an If, If-None-Match or If-Match header, which caused the HTTP
* request to not execute (the condition of the header failed)
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class PreconditionFailed extends DAV\Exception {
/**
* When this exception is thrown, the header-name might be set.
*
* This allows the exception-catching code to determine which HTTP header
* caused the exception.
*
* @var string
*/
public $header = null;
/**
* Create the exception
*
* @param string $message
* @param string $header
*/
public function __construct($message, $header=null) {
parent::__construct($message);
$this->header = $header;
}
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 412;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $errorNode) {
if ($this->header) {
$prop = $errorNode->ownerDocument->createElement('s:header');
$prop->nodeValue = $this->header;
$errorNode->appendChild($prop);
}
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* ReportNotSupported
*
* This exception is thrown when the client requested an unknown report through the REPORT method
*
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class ReportNotSupported extends Forbidden {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:supported-report');
$errorNode->appendChild($error);
}
}

Some files were not shown because too many files have changed in this diff Show more