commit vendor
This commit is contained in:
216
vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
vendored
Normal file
216
vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\VObject;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class AbstractBackend implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* Updates properties for a calendar.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*/
|
||||
public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of calendar objects.
|
||||
*
|
||||
* This method should work identical to getCalendarObject, but instead
|
||||
* return all the calendar objects in the list as an array.
|
||||
*
|
||||
* If the backend supports this, it may allow for some speed-ups.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCalendarObjects($calendarId, array $uris)
|
||||
{
|
||||
return array_map(function ($uri) use ($calendarId) {
|
||||
return $this->getCalendarObject($calendarId, $uri);
|
||||
}, $uris);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery($calendarId, array $filters)
|
||||
{
|
||||
$result = [];
|
||||
$objects = $this->getCalendarObjects($calendarId);
|
||||
|
||||
foreach ($objects as $object) {
|
||||
if ($this->validateFilterForObject($object, $filters)) {
|
||||
$result[] = $object['uri'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method validates if a filter (as passed to calendarQuery) matches
|
||||
* the given object.
|
||||
*
|
||||
* @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']);
|
||||
}
|
||||
|
||||
$vObject = VObject\Reader::read($object['calendardata']);
|
||||
|
||||
$validator = new CalDAV\CalendarQueryValidator();
|
||||
$result = $validator->validate($vObject, $filters);
|
||||
|
||||
// Destroy circular references so PHP will GC the object.
|
||||
$vObject->destroy();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all of a users calendars and calendar objects to find
|
||||
* an object with a specific UID.
|
||||
*
|
||||
* This method should return the path to this object, relative to the
|
||||
* calendar home, so this path usually only contains two parts:
|
||||
*
|
||||
* calendarpath/objectpath.ics
|
||||
*
|
||||
* If the uid is not found, return null.
|
||||
*
|
||||
* This method should only consider * objects that the principal owns, so
|
||||
* any calendars owned by other principals that also appear in this
|
||||
* collection should be ignored.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $uid
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCalendarObjectByUID($principalUri, $uid)
|
||||
{
|
||||
// Note: this is a super slow naive implementation of this method. You
|
||||
// are highly recommended to optimize it, if your backend allows it.
|
||||
foreach ($this->getCalendarsForUser($principalUri) as $calendar) {
|
||||
// We must ignore calendars owned by other principals.
|
||||
if ($calendar['principaluri'] !== $principalUri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore calendars that are shared.
|
||||
if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$results = $this->calendarQuery(
|
||||
$calendar['id'],
|
||||
[
|
||||
'name' => 'VCALENDAR',
|
||||
'prop-filters' => [],
|
||||
'comp-filters' => [
|
||||
[
|
||||
'name' => 'VEVENT',
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [
|
||||
[
|
||||
'name' => 'UID',
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
'text-match' => [
|
||||
'value' => $uid,
|
||||
'negate-condition' => false,
|
||||
'collation' => 'i;octet',
|
||||
],
|
||||
'param-filters' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
if ($results) {
|
||||
// We have a match
|
||||
return $calendar['uri'].'/'.$results[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
273
vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
vendored
Normal file
273
vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
/**
|
||||
* Every CalDAV backend must at least implement this interface.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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 is 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'.
|
||||
*
|
||||
* Many clients also require:
|
||||
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
|
||||
* For this property, you can just return an instance of
|
||||
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
|
||||
*
|
||||
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
|
||||
* ACL will automatically be put in read-only mode.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* The id can be any type, including ints, strings, objects or array.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $calendarUri
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createCalendar($principalUri, $calendarUri, array $properties);
|
||||
|
||||
/**
|
||||
* Updates properties for a calendar.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*/
|
||||
public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
|
||||
|
||||
/**
|
||||
* Delete a calendar and all its objects.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*/
|
||||
public function deleteCalendar($calendarId);
|
||||
|
||||
/**
|
||||
* Returns all calendar objects within a calendar.
|
||||
*
|
||||
* Every item contains an array with the following keys:
|
||||
* * calendardata - The iCalendar-compatible calendar data
|
||||
* * uri - a unique key which will be used to construct the uri. This can
|
||||
* be any arbitrary string, but making sure it ends with '.ics' is a
|
||||
* good idea. This is only the basename, or filename, not the full
|
||||
* path.
|
||||
* * lastmodified - a timestamp of the last modification time
|
||||
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
|
||||
* '"abcdef"')
|
||||
* * size - The size of the calendar objects, in bytes.
|
||||
* * component - optional, a string containing the type of object, such
|
||||
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
|
||||
* the Content-Type header.
|
||||
*
|
||||
* 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 object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* The returned array must have the same keys as getCalendarObjects. The
|
||||
* 'calendardata' object is required here though, while it's not required
|
||||
* for getCalendarObjects.
|
||||
*
|
||||
* This method must return null if the object did not exist.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getCalendarObject($calendarId, $objectUri);
|
||||
|
||||
/**
|
||||
* Returns a list of calendar objects.
|
||||
*
|
||||
* This method should work identical to getCalendarObject, but instead
|
||||
* return all the calendar objects in the list as an array.
|
||||
*
|
||||
* If the backend supports this, it may allow for some speed-ups.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCalendarObjects($calendarId, array $uris);
|
||||
|
||||
/**
|
||||
* Creates a new calendar object.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* It is possible to 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.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery($calendarId, array $filters);
|
||||
|
||||
/**
|
||||
* Searches through all of a users calendars and calendar objects to find
|
||||
* an object with a specific UID.
|
||||
*
|
||||
* This method should return the path to this object, relative to the
|
||||
* calendar home, so this path usually only contains two parts:
|
||||
*
|
||||
* calendarpath/objectpath.ics
|
||||
*
|
||||
* If the uid is not found, return null.
|
||||
*
|
||||
* This method should only consider * objects that the principal owns, so
|
||||
* any calendars owned by other principals that also appear in this
|
||||
* collection should be ignored.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $uid
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCalendarObjectByUID($principalUri, $uid);
|
||||
}
|
||||
62
vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
vendored
Normal file
62
vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface NotificationSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns a list of notifications for a given principal url.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*
|
||||
* @return NotificationInterface[]
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function deleteNotification($principalUri, NotificationInterface $notification);
|
||||
|
||||
/**
|
||||
* 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 string|null
|
||||
*/
|
||||
public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
|
||||
}
|
||||
1463
vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
vendored
Normal file
1463
vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
66
vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
vendored
Normal file
66
vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
/**
|
||||
* Implementing this interface adds CalDAV Scheduling support to your caldav
|
||||
* server, as defined in rfc6638.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface SchedulingSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns a single scheduling object for the inbox collection.
|
||||
*
|
||||
* The returned array should contain the following elements:
|
||||
* * uri - A unique basename for the object. This will be used to
|
||||
* construct a full uri.
|
||||
* * calendardata - The iCalendar object
|
||||
* * lastmodified - The last modification date. Can be an int for a unix
|
||||
* timestamp, or a PHP DateTime object.
|
||||
* * etag - A unique token that must change if the object changed.
|
||||
* * size - The size of the object, in bytes.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $objectUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSchedulingObject($principalUri, $objectUri);
|
||||
|
||||
/**
|
||||
* Returns all scheduling objects for the inbox collection.
|
||||
*
|
||||
* These objects should be returned as an array. Every item in the array
|
||||
* should follow the same structure as returned from getSchedulingObject.
|
||||
*
|
||||
* The main difference is that 'calendardata' is optional.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSchedulingObjects($principalUri);
|
||||
|
||||
/**
|
||||
* Deletes a scheduling object from the inbox collection.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $objectUri
|
||||
*/
|
||||
public function deleteSchedulingObject($principalUri, $objectUri);
|
||||
|
||||
/**
|
||||
* Creates a new scheduling object. This should land in a users' inbox.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $objectUri
|
||||
* @param string|resource $objectData
|
||||
*/
|
||||
public function createSchedulingObject($principalUri, $objectUri, $objectData);
|
||||
}
|
||||
60
vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
vendored
Normal file
60
vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
/**
|
||||
* Adds support for sharing features to a CalDAV server.
|
||||
*
|
||||
* CalDAV backends that implement this interface, must make the following
|
||||
* modifications to getCalendarsForUser:
|
||||
*
|
||||
* 1. Return shared calendars for users.
|
||||
* 2. For every calendar, return calendar-resource-uri. This strings is a URI or
|
||||
* relative URI reference that must be unique for every calendar, but
|
||||
* identical for every instance of the same shared calendar.
|
||||
* 3. For every calendar, you must return a share-access element. This element
|
||||
* should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* constants and
|
||||
* indicates the access level the user has.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface SharingSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* Updates the list of shares.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
|
||||
*/
|
||||
public function updateInvites($calendarId, array $sharees);
|
||||
|
||||
/**
|
||||
* Returns the list of people whom this calendar is shared with.
|
||||
*
|
||||
* Every item in the returned list must be a Sharee object with at
|
||||
* least the following properties set:
|
||||
* $href
|
||||
* $shareAccess
|
||||
* $inviteStatus
|
||||
*
|
||||
* and optionally:
|
||||
* $properties
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
*
|
||||
* @return \Sabre\DAV\Xml\Element\Sharee[]
|
||||
*/
|
||||
public function getInvites($calendarId);
|
||||
|
||||
/**
|
||||
* Publishes a calendar.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setPublishStatus($calendarId, $value);
|
||||
}
|
||||
289
vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php
vendored
Normal file
289
vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\DAV;
|
||||
|
||||
/**
|
||||
* Simple PDO CalDAV backend.
|
||||
*
|
||||
* This class is basically the most minimum example to get a caldav backend up
|
||||
* and running. This class uses the following schema (MySQL example):
|
||||
*
|
||||
* CREATE TABLE simple_calendars (
|
||||
* id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
* uri VARBINARY(200) NOT NULL,
|
||||
* principaluri VARBINARY(200) NOT NULL
|
||||
* );
|
||||
*
|
||||
* CREATE TABLE simple_calendarobjects (
|
||||
* id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
* calendarid INT UNSIGNED NOT NULL,
|
||||
* uri VARBINARY(200) NOT NULL,
|
||||
* calendardata MEDIUMBLOB
|
||||
* )
|
||||
*
|
||||
* To make this class work, you absolutely need to have the PropertyStorage
|
||||
* plugin enabled.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SimplePDO extends AbstractBackend
|
||||
{
|
||||
/**
|
||||
* pdo.
|
||||
*
|
||||
* @var \PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* Creates the backend.
|
||||
*/
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. This is just the 'base uri' or 'filename' of the calendar.
|
||||
* * 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'.
|
||||
*
|
||||
* Many clients also require:
|
||||
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
|
||||
* For this property, you can just return an instance of
|
||||
* Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
|
||||
*
|
||||
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
|
||||
* ACL will automatically be put in read-only mode.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarsForUser($principalUri)
|
||||
{
|
||||
// Making fields a comma-delimited list
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC');
|
||||
$stmt->execute([$principalUri]);
|
||||
|
||||
$calendars = [];
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$calendars[] = [
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'principaluri' => $principalUri,
|
||||
];
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createCalendar($principalUri, $calendarUri, array $properties)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)');
|
||||
$stmt->execute([$principalUri, $calendarUri]);
|
||||
|
||||
return $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a calendar and all it's objects.
|
||||
*
|
||||
* @param string $calendarId
|
||||
*/
|
||||
public function deleteCalendar($calendarId)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?');
|
||||
$stmt->execute([$calendarId]);
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?');
|
||||
$stmt->execute([$calendarId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all calendar objects within a calendar.
|
||||
*
|
||||
* Every item contains an array with the following keys:
|
||||
* * calendardata - The iCalendar-compatible calendar data
|
||||
* * uri - a unique key which will be used to construct the uri. This can
|
||||
* be any arbitrary string, but making sure it ends with '.ics' is a
|
||||
* good idea. This is only the basename, or filename, not the full
|
||||
* path.
|
||||
* * lastmodified - a timestamp of the last modification time
|
||||
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
|
||||
* ' "abcdef"')
|
||||
* * size - The size of the calendar objects, in bytes.
|
||||
* * component - optional, a string containing the type of object, such
|
||||
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
|
||||
* the Content-Type header.
|
||||
*
|
||||
* 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, calendardata FROM simple_calendarobjects WHERE calendarid = ?');
|
||||
$stmt->execute([$calendarId]);
|
||||
|
||||
$result = [];
|
||||
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
|
||||
$result[] = [
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'etag' => '"'.md5($row['calendardata']).'"',
|
||||
'calendarid' => $calendarId,
|
||||
'size' => strlen($row['calendardata']),
|
||||
'calendardata' => $row['calendardata'],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information from a single calendar object, based on it's object
|
||||
* uri.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* The returned array must have the same keys as getCalendarObjects. The
|
||||
* 'calendardata' object is required here though, while it's not required
|
||||
* for getCalendarObjects.
|
||||
*
|
||||
* This method must return null if the object did not exist.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param string $objectUri
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getCalendarObject($calendarId, $objectUri)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute([$calendarId, $objectUri]);
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'etag' => '"'.md5($row['calendardata']).'"',
|
||||
'calendarid' => $calendarId,
|
||||
'size' => strlen($row['calendardata']),
|
||||
'calendardata' => $row['calendardata'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar object.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)');
|
||||
$stmt->execute([
|
||||
$calendarId,
|
||||
$objectUri,
|
||||
$calendarData,
|
||||
]);
|
||||
|
||||
return '"'.md5($calendarData).'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing calendarobject, based on it's uri.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute([$calendarData, $calendarId, $objectUri]);
|
||||
|
||||
return '"'.md5($calendarData).'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing calendar object.
|
||||
*
|
||||
* The object uri is only the basename, or filename and not a full path.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param string $objectUri
|
||||
*/
|
||||
public function deleteCalendarObject($calendarId, $objectUri)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute([$calendarId, $objectUri]);
|
||||
}
|
||||
}
|
||||
89
vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
vendored
Normal file
89
vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
use Sabre\DAV;
|
||||
|
||||
/**
|
||||
* Every CalDAV backend must at least implement this interface.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface SubscriptionSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns a list of subscriptions for a principal.
|
||||
*
|
||||
* Every subscription is an array with the following keys:
|
||||
* * id, a unique id that will be used by other functions to modify the
|
||||
* subscription. This can be the same as the uri or a database key.
|
||||
* * uri. This is just the 'base uri' or 'filename' of the subscription.
|
||||
* * principaluri. The owner of the subscription. Almost always the same as
|
||||
* principalUri passed to this method.
|
||||
*
|
||||
* Furthermore, all the subscription info must be returned too:
|
||||
*
|
||||
* 1. {DAV:}displayname
|
||||
* 2. {http://apple.com/ns/ical/}refreshrate
|
||||
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
|
||||
* should not be stripped).
|
||||
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
|
||||
* should not be stripped).
|
||||
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
|
||||
* attachments should not be stripped).
|
||||
* 6. {http://calendarserver.org/ns/}source (Must be a
|
||||
* Sabre\DAV\Property\Href).
|
||||
* 7. {http://apple.com/ns/ical/}calendar-color
|
||||
* 8. {http://apple.com/ns/ical/}calendar-order
|
||||
* 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
|
||||
* (should just be an instance of
|
||||
* Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
|
||||
* default components).
|
||||
*
|
||||
* @param string $principalUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSubscriptionsForUser($principalUri);
|
||||
|
||||
/**
|
||||
* Creates a new subscription for a principal.
|
||||
*
|
||||
* If the creation was a success, an id must be returned that can be used to reference
|
||||
* this subscription in other methods, such as updateSubscription.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $uri
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createSubscription($principalUri, $uri, array $properties);
|
||||
|
||||
/**
|
||||
* Updates a subscription.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param mixed $subscriptionId
|
||||
* @param \Sabre\DAV\PropPatch $propPatch
|
||||
*/
|
||||
public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch);
|
||||
|
||||
/**
|
||||
* Deletes a subscription.
|
||||
*
|
||||
* @param mixed $subscriptionId
|
||||
*/
|
||||
public function deleteSubscription($subscriptionId);
|
||||
}
|
||||
83
vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php
vendored
Normal file
83
vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Backend;
|
||||
|
||||
/**
|
||||
* WebDAV-sync support for CalDAV backends.
|
||||
*
|
||||
* In order for backends to advertise support for WebDAV-sync, this interface
|
||||
* must be implemented.
|
||||
*
|
||||
* Implementing this can result in a significant reduction of bandwidth and CPU
|
||||
* time.
|
||||
*
|
||||
* For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
|
||||
* property from getCalendarsFromUser.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface SyncSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken in the specified calendar.
|
||||
*
|
||||
* This function should return an array, such as the following:
|
||||
*
|
||||
* [
|
||||
* 'syncToken' => 'The current synctoken',
|
||||
* 'added' => [
|
||||
* 'new.txt',
|
||||
* ],
|
||||
* 'modified' => [
|
||||
* 'modified.txt',
|
||||
* ],
|
||||
* 'deleted' => [
|
||||
* 'foo.php.bak',
|
||||
* 'old.txt'
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* The returned syncToken property should reflect the *current* syncToken
|
||||
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
|
||||
* property This is * needed here too, to ensure the operation is atomic.
|
||||
*
|
||||
* If the $syncToken argument is specified as null, this is an initial
|
||||
* sync, and all members should be reported.
|
||||
*
|
||||
* The modified property is an array of nodenames that have changed since
|
||||
* the last token.
|
||||
*
|
||||
* The deleted property is an array with nodenames, that have been deleted
|
||||
* from collection.
|
||||
*
|
||||
* The $syncLevel argument is basically the 'depth' of the report. If it's
|
||||
* 1, you only have to report changes that happened only directly in
|
||||
* immediate descendants. If it's 2, it should also include changes from
|
||||
* the nodes below the child collections. (grandchildren)
|
||||
*
|
||||
* The $limit argument allows a client to specify how many results should
|
||||
* be returned at most. If the limit is not specified, it should be treated
|
||||
* as infinite.
|
||||
*
|
||||
* If the limit (infinite or not) is higher than you're willing to return,
|
||||
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
|
||||
*
|
||||
* If the syncToken is expired (due to data cleanup) or unknown, you must
|
||||
* return null.
|
||||
*
|
||||
* The limit is 'suggestive'. You are free to ignore it.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param string $syncToken
|
||||
* @param int $syncLevel
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);
|
||||
}
|
||||
451
vendor/sabre/dav/lib/CalDAV/Calendar.php
vendored
Normal file
451
vendor/sabre/dav/lib/CalDAV/Calendar.php
vendored
Normal file
@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\PropPatch;
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* This is an array with calendar information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $calendarInfo;
|
||||
|
||||
/**
|
||||
* CalDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @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 on this node.
|
||||
*
|
||||
* This method received a PropPatch object, which contains all the
|
||||
* information about the update.
|
||||
*
|
||||
* To update specific properties, call the 'handle' method on this object.
|
||||
* Read the PropPatch documentation for more information.
|
||||
*/
|
||||
public function propPatch(PropPatch $propPatch)
|
||||
{
|
||||
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of properties.
|
||||
*
|
||||
* @param array $requestedProperties
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties($requestedProperties)
|
||||
{
|
||||
$response = [];
|
||||
|
||||
foreach ($this->calendarInfo as $propName => $propValue) {
|
||||
if (!is_null($propValue) && '{' === $propName[0]) {
|
||||
$response[$propName] = $this->calendarInfo[$propName];
|
||||
}
|
||||
}
|
||||
|
||||
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->getChildACL();
|
||||
|
||||
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 = [];
|
||||
foreach ($objs as $obj) {
|
||||
$obj['acl'] = $this->getChildACL();
|
||||
$children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method receives a list of paths in it's first argument.
|
||||
* It must return an array with Node objects.
|
||||
*
|
||||
* If any children are not found, you do not have to return them.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleChildren(array $paths)
|
||||
{
|
||||
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
$obj['acl'] = $this->getChildACL();
|
||||
$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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function setName($newName)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp.
|
||||
*/
|
||||
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 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 = [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{'.Plugin::NS_CALDAV.'}read-free-busy',
|
||||
'principal' => '{DAV:}authenticated',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ACL's for calendar objects in this calendar.
|
||||
* The result of this method automatically gets passed to the
|
||||
* calendar-object nodes in the calendar.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildACL()
|
||||
{
|
||||
$acl = [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery(array $filters)
|
||||
{
|
||||
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the current sync-token for this collection.
|
||||
* This can be any string.
|
||||
*
|
||||
* If null is returned from this function, the plugin assumes there's no
|
||||
* sync information available.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSyncToken()
|
||||
{
|
||||
if (
|
||||
$this->caldavBackend instanceof Backend\SyncSupport &&
|
||||
isset($this->calendarInfo['{DAV:}sync-token'])
|
||||
) {
|
||||
return $this->calendarInfo['{DAV:}sync-token'];
|
||||
}
|
||||
if (
|
||||
$this->caldavBackend instanceof Backend\SyncSupport &&
|
||||
isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
|
||||
) {
|
||||
return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken and the current collection.
|
||||
*
|
||||
* This function should return an array, such as the following:
|
||||
*
|
||||
* [
|
||||
* 'syncToken' => 'The current synctoken',
|
||||
* 'added' => [
|
||||
* 'new.txt',
|
||||
* ],
|
||||
* 'modified' => [
|
||||
* 'modified.txt',
|
||||
* ],
|
||||
* 'deleted' => [
|
||||
* 'foo.php.bak',
|
||||
* 'old.txt'
|
||||
* ]
|
||||
* ];
|
||||
*
|
||||
* The syncToken property should reflect the *current* syncToken of the
|
||||
* collection, as reported getSyncToken(). This is needed here too, to
|
||||
* ensure the operation is atomic.
|
||||
*
|
||||
* If the syncToken is specified as null, this is an initial sync, and all
|
||||
* members should be reported.
|
||||
*
|
||||
* The modified property is an array of nodenames that have changed since
|
||||
* the last token.
|
||||
*
|
||||
* The deleted property is an array with nodenames, that have been deleted
|
||||
* from collection.
|
||||
*
|
||||
* The second argument is basically the 'depth' of the report. If it's 1,
|
||||
* you only have to report changes that happened only directly in immediate
|
||||
* descendants. If it's 2, it should also include changes from the nodes
|
||||
* below the child collections. (grandchildren)
|
||||
*
|
||||
* The third (optional) argument allows a client to specify how many
|
||||
* results should be returned at most. If the limit is not specified, it
|
||||
* should be treated as infinite.
|
||||
*
|
||||
* If the limit (infinite or not) is higher than you're willing to return,
|
||||
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
|
||||
*
|
||||
* If the syncToken is expired (due to data cleanup) or unknown, you must
|
||||
* return null.
|
||||
*
|
||||
* The limit is 'suggestive'. You are free to ignore it.
|
||||
*
|
||||
* @param string $syncToken
|
||||
* @param int $syncLevel
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null)
|
||||
{
|
||||
if (!$this->caldavBackend instanceof Backend\SyncSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->caldavBackend->getChangesForCalendar(
|
||||
$this->calendarInfo['id'],
|
||||
$syncToken,
|
||||
$syncLevel,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
}
|
||||
356
vendor/sabre/dav/lib/CalDAV/CalendarHome.php
vendored
Normal file
356
vendor/sabre/dav/lib/CalDAV/CalendarHome.php
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\MkCol;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\Uri;
|
||||
|
||||
/**
|
||||
* The CalendarHome represents a node that is usually in a users'
|
||||
* calendar-homeset.
|
||||
*
|
||||
* It contains all the users' calendars, and can optionally contain a
|
||||
* notifications collection, calendar subscriptions, a users' inbox, and a
|
||||
* users' outbox.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* CalDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Principal information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalInfo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $principalInfo
|
||||
*/
|
||||
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) = Uri\split($this->principalInfo['uri']);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the name of this object.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
throw new DAV\Exception\Forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this object.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return Calendar
|
||||
*/
|
||||
public function getChild($name)
|
||||
{
|
||||
// Special nodes
|
||||
if ('inbox' === $name && $this->caldavBackend instanceof Backend\SchedulingSupport) {
|
||||
return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
|
||||
}
|
||||
if ('outbox' === $name && $this->caldavBackend instanceof Backend\SchedulingSupport) {
|
||||
return new Schedule\Outbox($this->principalInfo['uri']);
|
||||
}
|
||||
if ('notifications' === $name && $this->caldavBackend instanceof Backend\NotificationSupport) {
|
||||
return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
|
||||
}
|
||||
|
||||
// Calendars
|
||||
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
|
||||
if ($calendar['uri'] === $name) {
|
||||
if ($this->caldavBackend instanceof Backend\SharingSupport) {
|
||||
return new SharedCalendar($this->caldavBackend, $calendar);
|
||||
} else {
|
||||
return new Calendar($this->caldavBackend, $calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
|
||||
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
|
||||
if ($subscription['uri'] === $name) {
|
||||
return new Subscriptions\Subscription($this->caldavBackend, $subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFound('Node with name \''.$name.'\' could not be found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a calendar exists.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function childExists($name)
|
||||
{
|
||||
try {
|
||||
return (bool) $this->getChild($name);
|
||||
} catch (NotFound $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of calendars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
|
||||
$objs = [];
|
||||
foreach ($calendars as $calendar) {
|
||||
if ($this->caldavBackend instanceof Backend\SharingSupport) {
|
||||
$objs[] = new SharedCalendar($this->caldavBackend, $calendar);
|
||||
} else {
|
||||
$objs[] = new Calendar($this->caldavBackend, $calendar);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
|
||||
$objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
|
||||
$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']);
|
||||
}
|
||||
|
||||
// If the backend supports subscriptions, we'll add those as well,
|
||||
if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
|
||||
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
|
||||
$objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
|
||||
}
|
||||
}
|
||||
|
||||
return $objs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar or subscription.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @throws DAV\Exception\InvalidResourceType
|
||||
*/
|
||||
public function createExtendedCollection($name, MkCol $mkCol)
|
||||
{
|
||||
$isCalendar = false;
|
||||
$isSubscription = false;
|
||||
foreach ($mkCol->getResourceType() 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;
|
||||
case '{http://calendarserver.org/ns/}subscribed':
|
||||
$isSubscription = true;
|
||||
break;
|
||||
default:
|
||||
throw new DAV\Exception\InvalidResourceType('Unknown resourceType: '.$rt);
|
||||
}
|
||||
}
|
||||
|
||||
$properties = $mkCol->getRemainingValues();
|
||||
$mkCol->setRemainingResultCode(201);
|
||||
|
||||
if ($isSubscription) {
|
||||
if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
|
||||
throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
|
||||
}
|
||||
$this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
|
||||
} elseif ($isCalendar) {
|
||||
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
|
||||
} else {
|
||||
throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner of the calendar home.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->principalInfo['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'],
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->principalInfo['uri'],
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->principalInfo['uri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string|null
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all of a users calendars and calendar objects to find
|
||||
* an object with a specific UID.
|
||||
*
|
||||
* This method should return the path to this object, relative to the
|
||||
* calendar home, so this path usually only contains two parts:
|
||||
*
|
||||
* calendarpath/objectpath.ics
|
||||
*
|
||||
* If the uid is not found, return null.
|
||||
*
|
||||
* This method should only consider * objects that the principal owns, so
|
||||
* any calendars owned by other principals that also appear in this
|
||||
* collection should be ignored.
|
||||
*
|
||||
* @param string $uid
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCalendarObjectByUID($uid)
|
||||
{
|
||||
return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
|
||||
}
|
||||
}
|
||||
223
vendor/sabre/dav/lib/CalDAV/CalendarObject.php
vendored
Normal file
223
vendor/sabre/dav/lib/CalDAV/CalendarObject.php
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
/**
|
||||
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL
|
||||
{
|
||||
use \Sabre\DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* Sabre\CalDAV\Backend\BackendInterface.
|
||||
*
|
||||
* @var 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.
|
||||
*
|
||||
* The following properties may be passed within $objectData:
|
||||
*
|
||||
* * calendarid - This must refer to a calendarid from a caldavBackend
|
||||
* * uri - A unique uri. Only the 'basename' must be passed.
|
||||
* * calendardata (optional) - The iCalendar data
|
||||
* * etag - (optional) The etag for this object, MUST be encloded with
|
||||
* double-quotes.
|
||||
* * size - (optional) The size of the data in bytes.
|
||||
* * lastmodified - (optional) format as a unix timestamp.
|
||||
* * acl - (optional) Use this to override the default ACL for the node.
|
||||
*/
|
||||
public function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData)
|
||||
{
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
|
||||
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->calendarInfo['id'], $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.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime content-type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
$mime = 'text/calendar; charset=utf-8';
|
||||
if (isset($this->objectData['component']) && $this->objectData['component']) {
|
||||
$mime .= '; component='.$this->objectData['component'];
|
||||
}
|
||||
|
||||
return $mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
340
vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
vendored
Normal file
340
vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
vendored
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use DateTime;
|
||||
use Sabre\VObject;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(VObject\Component\VCalendar $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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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']]->getParts() as $paramPart) {
|
||||
if ($this->validateTextMatch($paramPart, $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|string $check value to check against
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateTextMatch($check, array $textMatch)
|
||||
{
|
||||
if ($check instanceof VObject\Node) {
|
||||
$check = $check->getValue();
|
||||
}
|
||||
|
||||
$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $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 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 ('VEVENT' === $component->parent->name && $component->parent->RRULE) {
|
||||
// Fire up the iterator!
|
||||
$it = new VObject\Recur\EventIterator($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 (null !== $expandedEvent->VALARM) {
|
||||
foreach ($expandedEvent->VALARM as $expandedAlarm) {
|
||||
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
|
||||
if ($expandedAlarm->isInTimeRange($start, $end)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('DATE-TIME' === (string) $expandedAlarm->TRIGGER['VALUE']) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// no break
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
75
vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
vendored
Normal file
75
vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAVACL\PrincipalBackend;
|
||||
|
||||
/**
|
||||
* Calendars collection.
|
||||
*
|
||||
* This object is responsible for generating a list of calendar-homes for each
|
||||
* user.
|
||||
*
|
||||
* This is the top-most node for the calendars tree. In most servers this class
|
||||
* represents the "/calendars" path.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection
|
||||
{
|
||||
/**
|
||||
* CalDAV backend.
|
||||
*
|
||||
* @var 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 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.
|
||||
*
|
||||
* @return \Sabre\DAV\INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal)
|
||||
{
|
||||
return new CalendarHome($this->caldavBackend, $principal);
|
||||
}
|
||||
}
|
||||
31
vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
vendored
Normal file
31
vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Exception;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\DAV;
|
||||
|
||||
/**
|
||||
* InvalidComponentType.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
377
vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
vendored
Normal file
377
vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
vendored
Normal file
@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
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.
|
||||
*
|
||||
* To use this, point a http client to a caldav calendar, and add ?expand to
|
||||
* the url.
|
||||
*
|
||||
* Further options that can be added to the url:
|
||||
* start=123456789 - Only return events after the given unix timestamp
|
||||
* end=123245679 - Only return events from before the given unix timestamp
|
||||
* expand=1 - Strip timezone information and expand recurring events.
|
||||
* If you'd like to expand, you _must_ also specify start
|
||||
* and end.
|
||||
*
|
||||
* By default this plugin returns data in the text/calendar format (iCalendar
|
||||
* 2.0). If you'd like to receive jCal data instead, you can use an Accept
|
||||
* header:
|
||||
*
|
||||
* Accept: application/calendar+json
|
||||
*
|
||||
* Alternatively, you can also specify this in the url using
|
||||
* accept=application/calendar+json, or accept=jcal for short. If the url
|
||||
* parameter and Accept header is specified, the url parameter wins.
|
||||
*
|
||||
* Note that specifying a start or end data implies that only events will be
|
||||
* returned. VTODO and VJOURNAL will be stripped.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
$server->on('method:GET', [$this, 'httpGet'], 90);
|
||||
$server->on('browserButtonActions', function ($path, $node, &$actions) {
|
||||
if ($node instanceof ICalendar) {
|
||||
$actions .= '<a href="'.htmlspecialchars($path, ENT_QUOTES, 'UTF-8').'?export"><span class="oi" data-glyph="calendar"></span></a>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts GET requests on calendar urls ending with ?export.
|
||||
*
|
||||
* @throws BadRequest
|
||||
* @throws DAV\Exception\NotFound
|
||||
* @throws VObject\InvalidDataException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function httpGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$queryParams = $request->getQueryParameters();
|
||||
if (!array_key_exists('export', $queryParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$node = $this->server->getProperties($path, [
|
||||
'{DAV:}resourcetype',
|
||||
'{DAV:}displayname',
|
||||
'{http://sabredav.org/ns}sync-token',
|
||||
'{DAV:}sync-token',
|
||||
'{http://apple.com/ns/ical/}calendar-color',
|
||||
]);
|
||||
|
||||
if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{'.Plugin::NS_CALDAV.'}calendar')) {
|
||||
return;
|
||||
}
|
||||
// Marking the transactionType, for logging purposes.
|
||||
$this->server->transactionType = 'get-calendar-export';
|
||||
|
||||
$properties = $node;
|
||||
|
||||
$start = null;
|
||||
$end = null;
|
||||
$expand = false;
|
||||
$componentType = false;
|
||||
if (isset($queryParams['start'])) {
|
||||
if (!ctype_digit($queryParams['start'])) {
|
||||
throw new BadRequest('The start= parameter must contain a unix timestamp');
|
||||
}
|
||||
$start = DateTime::createFromFormat('U', $queryParams['start']);
|
||||
}
|
||||
if (isset($queryParams['end'])) {
|
||||
if (!ctype_digit($queryParams['end'])) {
|
||||
throw new BadRequest('The end= parameter must contain a unix timestamp');
|
||||
}
|
||||
$end = DateTime::createFromFormat('U', $queryParams['end']);
|
||||
}
|
||||
if (isset($queryParams['expand']) && (bool) $queryParams['expand']) {
|
||||
if (!$start || !$end) {
|
||||
throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
|
||||
}
|
||||
$expand = true;
|
||||
$componentType = 'VEVENT';
|
||||
}
|
||||
if (isset($queryParams['componentType'])) {
|
||||
if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
|
||||
throw new BadRequest('You are not allowed to search for components of type: '.$queryParams['componentType'].' here');
|
||||
}
|
||||
$componentType = $queryParams['componentType'];
|
||||
}
|
||||
|
||||
$format = \Sabre\HTTP\negotiateContentType(
|
||||
$request->getHeader('Accept'),
|
||||
[
|
||||
'text/calendar',
|
||||
'application/calendar+json',
|
||||
]
|
||||
);
|
||||
|
||||
if (isset($queryParams['accept'])) {
|
||||
if ('application/calendar+json' === $queryParams['accept'] || 'jcal' === $queryParams['accept']) {
|
||||
$format = 'application/calendar+json';
|
||||
}
|
||||
}
|
||||
if (!$format) {
|
||||
$format = 'text/calendar';
|
||||
}
|
||||
|
||||
$this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
|
||||
|
||||
// Returning false to break the event chain
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for generating the actual, full response.
|
||||
*
|
||||
* @param string $path
|
||||
* @param DateTime|null $start
|
||||
* @param DateTime|null $end
|
||||
* @param bool $expand
|
||||
* @param string $componentType
|
||||
* @param string $format
|
||||
* @param array $properties
|
||||
*
|
||||
* @throws DAV\Exception\NotFound
|
||||
* @throws VObject\InvalidDataException
|
||||
*/
|
||||
protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response)
|
||||
{
|
||||
$calDataProp = '{'.Plugin::NS_CALDAV.'}calendar-data';
|
||||
$calendarNode = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
$blobs = [];
|
||||
if ($start || $end || $componentType) {
|
||||
// If there was a start or end filter, we need to enlist
|
||||
// calendarQuery for speed.
|
||||
$queryResult = $calendarNode->calendarQuery([
|
||||
'name' => 'VCALENDAR',
|
||||
'comp-filters' => [
|
||||
[
|
||||
'name' => $componentType,
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [],
|
||||
'is-not-defined' => false,
|
||||
'time-range' => [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
],
|
||||
],
|
||||
],
|
||||
'prop-filters' => [],
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
]);
|
||||
|
||||
// queryResult is just a list of base urls. We need to prefix the
|
||||
// calendar path.
|
||||
$queryResult = array_map(
|
||||
function ($item) use ($path) {
|
||||
return $path.'/'.$item;
|
||||
},
|
||||
$queryResult
|
||||
);
|
||||
$nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
|
||||
unset($queryResult);
|
||||
} else {
|
||||
$nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
|
||||
}
|
||||
|
||||
// Flattening the arrays
|
||||
foreach ($nodes as $node) {
|
||||
if (isset($node[200][$calDataProp])) {
|
||||
$blobs[$node['href']] = $node[200][$calDataProp];
|
||||
}
|
||||
}
|
||||
unset($nodes);
|
||||
|
||||
$mergedCalendar = $this->mergeObjects(
|
||||
$properties,
|
||||
$blobs
|
||||
);
|
||||
|
||||
if ($expand) {
|
||||
$calendarTimeZone = null;
|
||||
// We're expanding, and for that we need to figure out the
|
||||
// calendar's timezone.
|
||||
$tzProp = '{'.Plugin::NS_CALDAV.'}calendar-timezone';
|
||||
$tzResult = $this->server->getProperties($path, [$tzProp]);
|
||||
if (isset($tzResult[$tzProp])) {
|
||||
// This property contains a VCALENDAR with a single
|
||||
// VTIMEZONE.
|
||||
$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
|
||||
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vtimezoneObj->destroy();
|
||||
unset($vtimezoneObj);
|
||||
} else {
|
||||
// Defaulting to UTC.
|
||||
$calendarTimeZone = new DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
|
||||
}
|
||||
|
||||
$filenameExtension = '.ics';
|
||||
|
||||
switch ($format) {
|
||||
case 'text/calendar':
|
||||
$mergedCalendar = $mergedCalendar->serialize();
|
||||
$filenameExtension = '.ics';
|
||||
break;
|
||||
case 'application/calendar+json':
|
||||
$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
|
||||
$filenameExtension = '.json';
|
||||
break;
|
||||
}
|
||||
|
||||
$filename = preg_replace(
|
||||
'/[^a-zA-Z0-9-_ ]/um',
|
||||
'',
|
||||
$calendarNode->getName()
|
||||
);
|
||||
$filename .= '-'.date('Y-m-d').$filenameExtension;
|
||||
|
||||
$response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
|
||||
$response->setHeader('Content-Type', $format);
|
||||
|
||||
$response->setStatus(200);
|
||||
$response->setBody($mergedCalendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all calendar objects, and builds one big iCalendar blob.
|
||||
*
|
||||
* @param array $properties Some CalDAV properties
|
||||
*
|
||||
* @return VObject\Component\VCalendar
|
||||
*/
|
||||
public function mergeObjects(array $properties, array $inputObjects)
|
||||
{
|
||||
$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';
|
||||
}
|
||||
if (isset($properties['{DAV:}displayname'])) {
|
||||
$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
|
||||
}
|
||||
if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
|
||||
$calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
|
||||
}
|
||||
|
||||
$collectedTimezones = [];
|
||||
|
||||
$timezones = [];
|
||||
$objects = [];
|
||||
|
||||
foreach ($inputObjects as $href => $inputObject) {
|
||||
$nodeComp = VObject\Reader::read($inputObject);
|
||||
|
||||
foreach ($nodeComp->children() as $child) {
|
||||
switch ($child->name) {
|
||||
case 'VEVENT':
|
||||
case 'VTODO':
|
||||
case 'VJOURNAL':
|
||||
$objects[] = clone $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)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$timezones[] = clone $child;
|
||||
$collectedTimezones[] = $child->TZID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$nodeComp->destroy();
|
||||
unset($nodeComp);
|
||||
}
|
||||
|
||||
foreach ($timezones as $tz) {
|
||||
$calendar->add($tz);
|
||||
}
|
||||
foreach ($objects as $obj) {
|
||||
$calendar->add($obj);
|
||||
}
|
||||
|
||||
return $calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'ics-export';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
|
||||
'link' => 'http://sabre.io/dav/ics-export-plugin/',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
vendor/sabre/dav/lib/CalDAV/ICalendar.php
vendored
Normal file
20
vendor/sabre/dav/lib/CalDAV/ICalendar.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* Calendar interface.
|
||||
*
|
||||
* Implement this interface to allow a node to be recognized as an calendar.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL
|
||||
{
|
||||
}
|
||||
23
vendor/sabre/dav/lib/CalDAV/ICalendarObject.php
vendored
Normal file
23
vendor/sabre/dav/lib/CalDAV/ICalendarObject.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ICalendarObject extends DAV\IFile
|
||||
{
|
||||
}
|
||||
39
vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php
vendored
Normal file
39
vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
/**
|
||||
* This interface represents a node that may contain calendar objects.
|
||||
*
|
||||
* This is the shared parent for both the Inbox collection and calendars
|
||||
* resources.
|
||||
*
|
||||
* In most cases you will likely want to look at ICalendar instead of this
|
||||
* interface.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ICalendarObjectContainer extends \Sabre\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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery(array $filters);
|
||||
}
|
||||
27
vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
vendored
Normal file
27
vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAV\Sharing\ISharedNode;
|
||||
|
||||
/**
|
||||
* This interface represents a Calendar that is shared by a different user.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ISharedCalendar extends ISharedNode
|
||||
{
|
||||
/**
|
||||
* Marks this calendar as published.
|
||||
*
|
||||
* Publishing a calendar should automatically create a read-only, public,
|
||||
* subscribable calendar.
|
||||
*
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setPublishStatus($value);
|
||||
}
|
||||
96
vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
vendored
Normal file
96
vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Notifications;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\DAV;
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* The notification backend.
|
||||
*
|
||||
* @var CalDAV\Backend\NotificationSupport
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Principal uri.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @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 = [];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
25
vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
vendored
Normal file
25
vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ICollection extends DAV\ICollection
|
||||
{
|
||||
}
|
||||
41
vendor/sabre/dav/lib/CalDAV/Notifications/INode.php
vendored
Normal file
41
vendor/sabre/dav/lib/CalDAV/Notifications/INode.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Notifications;
|
||||
|
||||
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface INode
|
||||
{
|
||||
/**
|
||||
* This method must return an xml element, using the
|
||||
* Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
|
||||
*
|
||||
* @return NotificationInterface
|
||||
*/
|
||||
public function getNotificationType();
|
||||
|
||||
/**
|
||||
* Returns the etag for the notification.
|
||||
*
|
||||
* The etag must be surrounded by literal double-quotes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag();
|
||||
}
|
||||
112
vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
vendored
Normal file
112
vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Notifications;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
|
||||
use Sabre\DAV;
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Node extends DAV\File implements INode, DAVACL\IACL
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* The notification backend.
|
||||
*
|
||||
* @var CalDAV\Backend\NotificationSupport
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* The actual notification.
|
||||
*
|
||||
* @var NotificationInterface
|
||||
*/
|
||||
protected $notification;
|
||||
|
||||
/**
|
||||
* Owner principal of the notification.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*/
|
||||
public function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification)
|
||||
{
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->principalUri = $principalUri;
|
||||
$this->notification = $notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path name for this notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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\Xml\Notification\NotificationInterface classes.
|
||||
*
|
||||
* @return NotificationInterface
|
||||
*/
|
||||
public function getNotificationType()
|
||||
{
|
||||
return $this->notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this notification.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
161
vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
vendored
Normal file
161
vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Notifications;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\INode as BaseINode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Notifications plugin.
|
||||
*
|
||||
* This plugin implements several features required by the caldav-notification
|
||||
* draft specification.
|
||||
*
|
||||
* Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but
|
||||
* this has since been split up.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Plugin extends ServerPlugin
|
||||
{
|
||||
/**
|
||||
* This is the namespace for the proprietary calendarserver extensions.
|
||||
*/
|
||||
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
|
||||
|
||||
/**
|
||||
* Reference to the main server object.
|
||||
*
|
||||
* @var Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* 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 'notifications';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function initialize(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
$server->on('method:GET', [$this, 'httpGet'], 90);
|
||||
$server->on('propFind', [$this, 'propFind']);
|
||||
|
||||
$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
|
||||
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{'.self::NS_CALENDARSERVER.'}notification';
|
||||
|
||||
array_push($server->protectedProperties,
|
||||
'{'.self::NS_CALENDARSERVER.'}notification-URL',
|
||||
'{'.self::NS_CALENDARSERVER.'}notificationtype'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* PropFind.
|
||||
*/
|
||||
public function propFind(PropFind $propFind, BaseINode $node)
|
||||
{
|
||||
$caldavPlugin = $this->server->getPlugin('caldav');
|
||||
|
||||
if ($node instanceof DAVACL\IPrincipal) {
|
||||
$principalUrl = $node->getPrincipalUrl();
|
||||
|
||||
// notification-URL property
|
||||
$propFind->handle('{'.self::NS_CALENDARSERVER.'}notification-URL', function () use ($principalUrl, $caldavPlugin) {
|
||||
$notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl).'/notifications/';
|
||||
|
||||
return new DAV\Xml\Property\Href($notificationPath);
|
||||
});
|
||||
}
|
||||
|
||||
if ($node instanceof INode) {
|
||||
$propFind->handle(
|
||||
'{'.self::NS_CALENDARSERVER.'}notificationtype',
|
||||
[$node, 'getNotificationType']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered before the usual GET request handler.
|
||||
*
|
||||
* We use this to intercept GET calls to notification nodes, and return the
|
||||
* proper response.
|
||||
*/
|
||||
public function httpGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$path = $request->getPath();
|
||||
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
} catch (DAV\Exception\NotFound $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$node instanceof INode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$writer = $this->server->xml->getWriter();
|
||||
$writer->contextUri = $this->server->getBaseUri();
|
||||
$writer->openMemory();
|
||||
$writer->startDocument('1.0', 'UTF-8');
|
||||
$writer->startElement('{http://calendarserver.org/ns/}notification');
|
||||
$node->getNotificationType()->xmlSerializeFull($writer);
|
||||
$writer->endElement();
|
||||
|
||||
$response->setHeader('Content-Type', 'application/xml');
|
||||
$response->setHeader('ETag', $node->getETag());
|
||||
$response->setStatus(200);
|
||||
$response->setBody($writer->outputMemory());
|
||||
|
||||
// Return false to break the event chain.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
|
||||
'link' => 'http://sabre.io/dav/caldav-sharing/',
|
||||
];
|
||||
}
|
||||
}
|
||||
1011
vendor/sabre/dav/lib/CalDAV/Plugin.php
vendored
Normal file
1011
vendor/sabre/dav/lib/CalDAV/Plugin.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
vendored
Normal file
32
vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Collection extends DAVACL\PrincipalCollection
|
||||
{
|
||||
/**
|
||||
* Returns a child object based on principal information.
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getChildForPrincipal(array $principalInfo)
|
||||
{
|
||||
return new User($this->principalBackend, $principalInfo);
|
||||
}
|
||||
}
|
||||
21
vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
vendored
Normal file
21
vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IProxyRead extends DAVACL\IPrincipal
|
||||
{
|
||||
}
|
||||
21
vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php
vendored
Normal file
21
vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IProxyWrite extends DAVACL\IPrincipal
|
||||
{
|
||||
}
|
||||
161
vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php
vendored
Normal file
161
vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Principal;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current node.
|
||||
*
|
||||
* @throws DAV\Exception\Forbidden
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
throw new DAV\Exception\Forbidden('Permission denied to delete node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the node.
|
||||
*
|
||||
* @param string $name The new name
|
||||
*
|
||||
* @throws DAV\Exception\Forbidden
|
||||
*/
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
161
vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
vendored
Normal file
161
vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Principal;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current node.
|
||||
*
|
||||
* @throws DAV\Exception\Forbidden
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
throw new DAV\Exception\Forbidden('Permission denied to delete node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the node.
|
||||
*
|
||||
* @param string $name The new name
|
||||
*
|
||||
* @throws DAV\Exception\Forbidden
|
||||
*/
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
136
vendor/sabre/dav/lib/CalDAV/Principal/User.php
vendored
Normal file
136
vendor/sabre/dav/lib/CalDAV/Principal/User.php
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 ('calendar-proxy-read' === $name) {
|
||||
return new ProxyRead($this->principalBackend, $this->principalProperties);
|
||||
}
|
||||
|
||||
if ('calendar-proxy-write' === $name) {
|
||||
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 = [];
|
||||
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[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalProperties['uri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalProperties['uri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
|
||||
return $acl;
|
||||
}
|
||||
}
|
||||
17
vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
vendored
Normal file
17
vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
/**
|
||||
* Implement this interface to have a node be recognized as a CalDAV scheduling
|
||||
* inbox.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL
|
||||
{
|
||||
}
|
||||
185
vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php
vendored
Normal file
185
vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\VObject\ITip;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class IMipPlugin extends DAV\ServerPlugin
|
||||
{
|
||||
/**
|
||||
* Email address used in From: header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $senderEmail;
|
||||
|
||||
/**
|
||||
* ITipMessage.
|
||||
*
|
||||
* @var ITip\Message
|
||||
*/
|
||||
protected $itipMessage;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
$server->on('schedule', [$this, 'schedule'], 120);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'imip';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the 'schedule' event.
|
||||
*/
|
||||
public function schedule(ITip\Message $iTipMessage)
|
||||
{
|
||||
// Not sending any emails if the system considers the update
|
||||
// insignificant.
|
||||
if (!$iTipMessage->significantChange) {
|
||||
if (!$iTipMessage->scheduleStatus) {
|
||||
$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$summary = $iTipMessage->message->VEVENT->SUMMARY;
|
||||
|
||||
if ('mailto' !== parse_url($iTipMessage->sender, PHP_URL_SCHEME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('mailto' !== parse_url($iTipMessage->recipient, PHP_URL_SCHEME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sender = substr($iTipMessage->sender, 7);
|
||||
$recipient = substr($iTipMessage->recipient, 7);
|
||||
|
||||
if ($iTipMessage->senderName) {
|
||||
$sender = $iTipMessage->senderName.' <'.$sender.'>';
|
||||
}
|
||||
if ($iTipMessage->recipientName && $iTipMessage->recipientName != $recipient) {
|
||||
$recipient = $iTipMessage->recipientName.' <'.$recipient.'>';
|
||||
}
|
||||
|
||||
$subject = 'SabreDAV iTIP message';
|
||||
switch (strtoupper($iTipMessage->method)) {
|
||||
case 'REPLY':
|
||||
$subject = 'Re: '.$summary;
|
||||
break;
|
||||
case 'REQUEST':
|
||||
$subject = 'Invitation: '.$summary;
|
||||
break;
|
||||
case 'CANCEL':
|
||||
$subject = 'Cancelled: '.$summary;
|
||||
break;
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'Reply-To: '.$sender,
|
||||
'From: '.$iTipMessage->senderName.' <'.$this->senderEmail.'>',
|
||||
'MIME-Version: 1.0',
|
||||
'Content-Type: text/calendar; charset=UTF-8; method='.$iTipMessage->method,
|
||||
];
|
||||
if (DAV\Server::$exposeVersion) {
|
||||
$headers[] = 'X-Sabre-Version: '.DAV\Version::VERSION;
|
||||
}
|
||||
$this->mail(
|
||||
$recipient,
|
||||
$subject,
|
||||
$iTipMessage->message->serialize(),
|
||||
$headers
|
||||
);
|
||||
$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// This is deemed untestable in a reasonable manner
|
||||
|
||||
/**
|
||||
* This function is responsible 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
|
||||
*/
|
||||
protected function mail($to, $subject, $body, array $headers)
|
||||
{
|
||||
mail($to, $subject, $body, implode("\r\n", $headers));
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Email delivery (rfc6047) for CalDAV scheduling',
|
||||
'link' => 'http://sabre.io/dav/scheduling/',
|
||||
];
|
||||
}
|
||||
}
|
||||
17
vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
vendored
Normal file
17
vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
/**
|
||||
* Implement this interface to have a node be recognized as a CalDAV scheduling
|
||||
* outbox.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL
|
||||
{
|
||||
}
|
||||
15
vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php
vendored
Normal file
15
vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
/**
|
||||
* The SchedulingObject represents a scheduling object in the Inbox collection.
|
||||
*
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
*/
|
||||
interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject
|
||||
{
|
||||
}
|
||||
198
vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
vendored
Normal file
198
vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\CalDAV\Backend;
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\VObject;
|
||||
|
||||
/**
|
||||
* The CalDAV scheduling inbox.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Inbox extends DAV\Collection implements IInbox
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* CalDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* The principal Uri.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $principalUri
|
||||
*/
|
||||
public function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri)
|
||||
{
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->principalUri = $principalUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the node.
|
||||
*
|
||||
* This is used to generate the url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'inbox';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the child nodes.
|
||||
*
|
||||
* @return \Sabre\DAV\INode[]
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
$objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
//$obj['acl'] = $this->getACL();
|
||||
$obj['principaluri'] = $this->principalUri;
|
||||
$children[] = new SchedulingObject($this->caldavBackend, $obj);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 successful 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 string|null
|
||||
*/
|
||||
public function createFile($name, $data = null)
|
||||
{
|
||||
$this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => '{DAV:}authenticated',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}unbind',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}unbind',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-deliver',
|
||||
'principal' => '{DAV:}authenticated',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery(array $filters)
|
||||
{
|
||||
$result = [];
|
||||
$validator = new CalDAV\CalendarQueryValidator();
|
||||
|
||||
$objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
|
||||
foreach ($objects as $object) {
|
||||
$vObject = VObject\Reader::read($object['calendardata']);
|
||||
if ($validator->validate($vObject, $filters)) {
|
||||
$result[] = $object['uri'];
|
||||
}
|
||||
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vObject->destroy();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
119
vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
vendored
Normal file
119
vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\DAV;
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Outbox extends DAV\Collection implements IOutbox
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 [
|
||||
[
|
||||
'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{'.CalDAV\Plugin::NS_CALDAV.'}schedule-send',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
999
vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
vendored
Normal file
999
vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
vendored
Normal file
@ -0,0 +1,999 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
use DateTimeZone;
|
||||
use Sabre\CalDAV\ICalendar;
|
||||
use Sabre\CalDAV\ICalendarObject;
|
||||
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Exception\NotImplemented;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
use Sabre\DAV\Sharing;
|
||||
use Sabre\DAV\Xml\Property\LocalHref;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\VObject;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\ITip;
|
||||
use Sabre\VObject\ITip\Message;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
/**
|
||||
* CalDAV scheduling plugin.
|
||||
* =========================.
|
||||
*
|
||||
* This plugin provides the functionality added by the "Scheduling Extensions
|
||||
* to CalDAV" standard, as defined in RFC6638.
|
||||
*
|
||||
* calendar-auto-schedule largely works by intercepting a users request to
|
||||
* update their local calendar. If a user creates a new event with attendees,
|
||||
* this plugin is supposed to grab the information from that event, and notify
|
||||
* the attendees of this.
|
||||
*
|
||||
* There's 3 possible transports for this:
|
||||
* * local delivery
|
||||
* * delivery through email (iMip)
|
||||
* * server-to-server delivery (iSchedule)
|
||||
*
|
||||
* iMip is simply, because we just need to add the iTip message as an email
|
||||
* attachment. Local delivery is harder, because we both need to add this same
|
||||
* message to a local DAV inbox, as well as live-update the relevant events.
|
||||
*
|
||||
* iSchedule is something for later.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Plugin extends ServerPlugin
|
||||
{
|
||||
/**
|
||||
* This is the official CalDAV namespace.
|
||||
*/
|
||||
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
|
||||
|
||||
/**
|
||||
* Reference to main Server object.
|
||||
*
|
||||
* @var Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Returns a list of features for the DAV: HTTP header.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeatures()
|
||||
{
|
||||
return ['calendar-auto-schedule', 'calendar-availability'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the plugin.
|
||||
*
|
||||
* Using this name other plugins will be able to access other plugins
|
||||
* using Server::getPlugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPluginName()
|
||||
{
|
||||
return 'caldav-schedule';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the plugin.
|
||||
*/
|
||||
public function initialize(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
$server->on('method:POST', [$this, 'httpPost']);
|
||||
$server->on('propFind', [$this, 'propFind']);
|
||||
$server->on('propPatch', [$this, 'propPatch']);
|
||||
$server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
|
||||
$server->on('beforeUnbind', [$this, 'beforeUnbind']);
|
||||
$server->on('schedule', [$this, 'scheduleLocalDelivery']);
|
||||
$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
|
||||
|
||||
$ns = '{'.self::NS_CALDAV.'}';
|
||||
|
||||
/*
|
||||
* This information ensures that the {DAV:}resourcetype property has
|
||||
* the correct values.
|
||||
*/
|
||||
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns.'schedule-outbox';
|
||||
$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns.'schedule-inbox';
|
||||
|
||||
/*
|
||||
* Properties we protect are made read-only by the server.
|
||||
*/
|
||||
array_push($server->protectedProperties,
|
||||
$ns.'schedule-inbox-URL',
|
||||
$ns.'schedule-outbox-URL',
|
||||
$ns.'calendar-user-address-set',
|
||||
$ns.'calendar-user-type',
|
||||
$ns.'schedule-default-calendar-URL'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to tell the server this plugin defines additional
|
||||
* HTTP methods.
|
||||
*
|
||||
* This method is passed a uri. It should only return HTTP methods that are
|
||||
* available for the specified uri.
|
||||
*
|
||||
* @param string $uri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHTTPMethods($uri)
|
||||
{
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($uri);
|
||||
} catch (NotFound $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($node instanceof IOutbox) {
|
||||
return ['POST'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles POST request for the outbox.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function httpPost(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
// Checking if this is a text/calendar content type
|
||||
$contentType = $request->getHeader('Content-Type');
|
||||
if (!$contentType || 0 !== strpos($contentType, 'text/calendar')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
// Checking if we're talking to an outbox
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
} catch (NotFound $e) {
|
||||
return;
|
||||
}
|
||||
if (!$node instanceof IOutbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->transactionType = 'post-caldav-outbox';
|
||||
$this->outboxRequest($node, $request, $response);
|
||||
|
||||
// Returning false breaks the event chain and tells the server we've
|
||||
// handled the request.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handler is invoked during fetching of properties.
|
||||
*
|
||||
* We use this event to add calendar-auto-schedule-specific properties.
|
||||
*/
|
||||
public function propFind(PropFind $propFind, INode $node)
|
||||
{
|
||||
if ($node instanceof DAVACL\IPrincipal) {
|
||||
$caldavPlugin = $this->server->getPlugin('caldav');
|
||||
$principalUrl = $node->getPrincipalUrl();
|
||||
|
||||
// schedule-outbox-URL property
|
||||
$propFind->handle('{'.self::NS_CALDAV.'}schedule-outbox-URL', function () use ($principalUrl, $caldavPlugin) {
|
||||
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
|
||||
if (!$calendarHomePath) {
|
||||
return null;
|
||||
}
|
||||
$outboxPath = $calendarHomePath.'/outbox/';
|
||||
|
||||
return new LocalHref($outboxPath);
|
||||
});
|
||||
// schedule-inbox-URL property
|
||||
$propFind->handle('{'.self::NS_CALDAV.'}schedule-inbox-URL', function () use ($principalUrl, $caldavPlugin) {
|
||||
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
|
||||
if (!$calendarHomePath) {
|
||||
return null;
|
||||
}
|
||||
$inboxPath = $calendarHomePath.'/inbox/';
|
||||
|
||||
return new LocalHref($inboxPath);
|
||||
});
|
||||
|
||||
$propFind->handle('{'.self::NS_CALDAV.'}schedule-default-calendar-URL', function () use ($principalUrl, $caldavPlugin) {
|
||||
// We don't support customizing this property yet, so in the
|
||||
// meantime we just grab the first calendar in the home-set.
|
||||
$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
|
||||
|
||||
if (!$calendarHomePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sccs = '{'.self::NS_CALDAV.'}supported-calendar-component-set';
|
||||
|
||||
$result = $this->server->getPropertiesForPath($calendarHomePath, [
|
||||
'{DAV:}resourcetype',
|
||||
'{DAV:}share-access',
|
||||
$sccs,
|
||||
], 1);
|
||||
|
||||
foreach ($result as $child) {
|
||||
if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{'.self::NS_CALDAV.'}calendar')) {
|
||||
// Node is either not a calendar
|
||||
continue;
|
||||
}
|
||||
if (isset($child[200]['{DAV:}share-access'])) {
|
||||
$shareAccess = $child[200]['{DAV:}share-access']->getValue();
|
||||
if (Sharing\Plugin::ACCESS_NOTSHARED !== $shareAccess && Sharing\Plugin::ACCESS_SHAREDOWNER !== $shareAccess) {
|
||||
// Node is a shared node, not owned by the relevant
|
||||
// user.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
|
||||
// Either there is no supported-calendar-component-set
|
||||
// (which is fine) or we found one that supports VEVENT.
|
||||
return new LocalHref($child['href']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The server currently reports every principal to be of type
|
||||
// 'INDIVIDUAL'
|
||||
$propFind->handle('{'.self::NS_CALDAV.'}calendar-user-type', function () {
|
||||
return 'INDIVIDUAL';
|
||||
});
|
||||
}
|
||||
|
||||
// Mapping the old property to the new property.
|
||||
$propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function () use ($propFind, $node) {
|
||||
// In case it wasn't clear, the only difference is that we map the
|
||||
// old property to a different namespace.
|
||||
$availProp = '{'.self::NS_CALDAV.'}calendar-availability';
|
||||
$subPropFind = new PropFind(
|
||||
$propFind->getPath(),
|
||||
[$availProp]
|
||||
);
|
||||
|
||||
$this->server->getPropertiesByNode(
|
||||
$subPropFind,
|
||||
$node
|
||||
);
|
||||
|
||||
$propFind->set(
|
||||
'{http://calendarserver.org/ns/}calendar-availability',
|
||||
$subPropFind->get($availProp),
|
||||
$subPropFind->getStatus($availProp)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called during property updates.
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function propPatch($path, PropPatch $propPatch)
|
||||
{
|
||||
// Mapping the old property to the new property.
|
||||
$propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function ($value) use ($path) {
|
||||
$availProp = '{'.self::NS_CALDAV.'}calendar-availability';
|
||||
$subPropPatch = new PropPatch([$availProp => $value]);
|
||||
$this->server->emit('propPatch', [$path, $subPropPatch]);
|
||||
$subPropPatch->commit();
|
||||
|
||||
return $subPropPatch->getResult()[$availProp];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered whenever there was a calendar object gets
|
||||
* created or updated.
|
||||
*
|
||||
* @param RequestInterface $request HTTP request
|
||||
* @param ResponseInterface $response HTTP Response
|
||||
* @param VCalendar $vCal Parsed iCalendar object
|
||||
* @param mixed $calendarPath Path to calendar collection
|
||||
* @param mixed $modified the iCalendar object has been touched
|
||||
* @param mixed $isNew Whether this was a new item or we're updating one
|
||||
*/
|
||||
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew)
|
||||
{
|
||||
if (!$this->scheduleReply($this->server->httpRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
|
||||
|
||||
$addresses = $this->getAddressesForPrincipal(
|
||||
$calendarNode->getOwner()
|
||||
);
|
||||
|
||||
if (!$isNew) {
|
||||
$node = $this->server->tree->getNodeForPath($request->getPath());
|
||||
$oldObj = Reader::read($node->get());
|
||||
} else {
|
||||
$oldObj = null;
|
||||
}
|
||||
|
||||
$this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
|
||||
|
||||
if ($oldObj) {
|
||||
// Destroy circular references so PHP will GC the object.
|
||||
$oldObj->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for delivering the ITip message.
|
||||
*/
|
||||
public function deliver(ITip\Message $iTipMessage)
|
||||
{
|
||||
$this->server->emit('schedule', [$iTipMessage]);
|
||||
if (!$iTipMessage->scheduleStatus) {
|
||||
$iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
|
||||
}
|
||||
// In case the change was considered 'insignificant', we are going to
|
||||
// remove any error statuses, if any. See ticket #525.
|
||||
list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
|
||||
if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
|
||||
$iTipMessage->scheduleStatus = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered before a file gets deleted.
|
||||
*
|
||||
* We use this event to make sure that when this happens, attendees get
|
||||
* cancellations, and organizers get 'DECLINED' statuses.
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function beforeUnbind($path)
|
||||
{
|
||||
// FIXME: We shouldn't trigger this functionality when we're issuing a
|
||||
// MOVE. This is a hack.
|
||||
if ('MOVE' === $this->server->httpRequest->getMethod()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->scheduleReply($this->server->httpRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$addresses = $this->getAddressesForPrincipal(
|
||||
$node->getOwner()
|
||||
);
|
||||
|
||||
$broker = new ITip\Broker();
|
||||
$messages = $broker->parseEvent(null, $addresses, $node->get());
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$this->deliver($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the 'schedule' event.
|
||||
*
|
||||
* This handler attempts to look at local accounts to deliver the
|
||||
* scheduling object.
|
||||
*/
|
||||
public function scheduleLocalDelivery(ITip\Message $iTipMessage)
|
||||
{
|
||||
$aclPlugin = $this->server->getPlugin('acl');
|
||||
|
||||
// Local delivery is not available if the ACL plugin is not loaded.
|
||||
if (!$aclPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
$caldavNS = '{'.self::NS_CALDAV.'}';
|
||||
|
||||
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
|
||||
if (!$principalUri) {
|
||||
$iTipMessage->scheduleStatus = '3.7;Could not find principal.';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We found a principal URL, now we need to find its inbox.
|
||||
// Unfortunately we may not have sufficient privileges to find this, so
|
||||
// we are temporarily turning off ACL to let this come through.
|
||||
//
|
||||
// Once we support PHP 5.5, this should be wrapped in a try..finally
|
||||
// block so we can ensure that this privilege gets added again after.
|
||||
$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
|
||||
|
||||
$result = $this->server->getProperties(
|
||||
$principalUri,
|
||||
[
|
||||
'{DAV:}principal-URL',
|
||||
$caldavNS.'calendar-home-set',
|
||||
$caldavNS.'schedule-inbox-URL',
|
||||
$caldavNS.'schedule-default-calendar-URL',
|
||||
'{http://sabredav.org/ns}email-address',
|
||||
]
|
||||
);
|
||||
|
||||
// Re-registering the ACL event
|
||||
$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
|
||||
|
||||
if (!isset($result[$caldavNS.'schedule-inbox-URL'])) {
|
||||
$iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
|
||||
|
||||
return;
|
||||
}
|
||||
if (!isset($result[$caldavNS.'calendar-home-set'])) {
|
||||
$iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
|
||||
|
||||
return;
|
||||
}
|
||||
if (!isset($result[$caldavNS.'schedule-default-calendar-URL'])) {
|
||||
$iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$calendarPath = $result[$caldavNS.'schedule-default-calendar-URL']->getHref();
|
||||
$homePath = $result[$caldavNS.'calendar-home-set']->getHref();
|
||||
$inboxPath = $result[$caldavNS.'schedule-inbox-URL']->getHref();
|
||||
|
||||
if ('REPLY' === $iTipMessage->method) {
|
||||
$privilege = 'schedule-deliver-reply';
|
||||
} else {
|
||||
$privilege = 'schedule-deliver-invite';
|
||||
}
|
||||
|
||||
if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS.$privilege, DAVACL\Plugin::R_PARENT, false)) {
|
||||
$iTipMessage->scheduleStatus = '3.8;insufficient privileges: '.$privilege.' is required on the recipient schedule inbox.';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Next, we're going to find out if the item already exits in one of
|
||||
// the users' calendars.
|
||||
$uid = $iTipMessage->uid;
|
||||
|
||||
$newFileName = 'sabredav-'.\Sabre\DAV\UUIDUtil::getUUID().'.ics';
|
||||
|
||||
$home = $this->server->tree->getNodeForPath($homePath);
|
||||
$inbox = $this->server->tree->getNodeForPath($inboxPath);
|
||||
|
||||
$currentObject = null;
|
||||
$objectNode = null;
|
||||
$isNewNode = false;
|
||||
|
||||
$result = $home->getCalendarObjectByUID($uid);
|
||||
if ($result) {
|
||||
// There was an existing object, we need to update probably.
|
||||
$objectPath = $homePath.'/'.$result;
|
||||
$objectNode = $this->server->tree->getNodeForPath($objectPath);
|
||||
$oldICalendarData = $objectNode->get();
|
||||
$currentObject = Reader::read($oldICalendarData);
|
||||
} else {
|
||||
$isNewNode = true;
|
||||
}
|
||||
|
||||
$broker = new ITip\Broker();
|
||||
$newObject = $broker->processMessage($iTipMessage, $currentObject);
|
||||
|
||||
$inbox->createFile($newFileName, $iTipMessage->message->serialize());
|
||||
|
||||
if (!$newObject) {
|
||||
// We received an iTip message referring to a UID that we don't
|
||||
// have in any calendars yet, and processMessage did not give us a
|
||||
// calendarobject back.
|
||||
//
|
||||
// The implication is that processMessage did not understand the
|
||||
// iTip message.
|
||||
$iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that we are bypassing ACL on purpose by calling this directly.
|
||||
// We may need to look a bit deeper into this later. Supporting ACL
|
||||
// here would be nice.
|
||||
if ($isNewNode) {
|
||||
$calendar = $this->server->tree->getNodeForPath($calendarPath);
|
||||
$calendar->createFile($newFileName, $newObject->serialize());
|
||||
} else {
|
||||
// If the message was a reply, we may have to inform other
|
||||
// attendees of this attendees status. Therefore we're shooting off
|
||||
// another itipMessage.
|
||||
if ('REPLY' === $iTipMessage->method) {
|
||||
$this->processICalendarChange(
|
||||
$oldICalendarData,
|
||||
$newObject,
|
||||
[$iTipMessage->recipient],
|
||||
[$iTipMessage->sender]
|
||||
);
|
||||
}
|
||||
$objectNode->put($newObject->serialize());
|
||||
}
|
||||
$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered whenever a subsystem requests the privileges
|
||||
* that are supported on a particular node.
|
||||
*
|
||||
* We need to add a number of privileges for scheduling purposes.
|
||||
*/
|
||||
public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
|
||||
{
|
||||
$ns = '{'.self::NS_CALDAV.'}';
|
||||
if ($node instanceof IOutbox) {
|
||||
$supportedPrivilegeSet[$ns.'schedule-send'] = [
|
||||
'abstract' => false,
|
||||
'aggregates' => [
|
||||
$ns.'schedule-send-invite' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
$ns.'schedule-send-reply' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
$ns.'schedule-send-freebusy' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
// Privilege from an earlier scheduling draft, but still
|
||||
// used by some clients.
|
||||
$ns.'schedule-post-vevent' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
if ($node instanceof IInbox) {
|
||||
$supportedPrivilegeSet[$ns.'schedule-deliver'] = [
|
||||
'abstract' => false,
|
||||
'aggregates' => [
|
||||
$ns.'schedule-deliver-invite' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
$ns.'schedule-deliver-reply' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
$ns.'schedule-query-freebusy' => [
|
||||
'abstract' => false,
|
||||
'aggregates' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks at an old iCalendar object, a new iCalendar object and
|
||||
* starts sending scheduling messages based on the changes.
|
||||
*
|
||||
* A list of addresses needs to be specified, so the system knows who made
|
||||
* the update, because the behavior may be different based on if it's an
|
||||
* attendee or an organizer.
|
||||
*
|
||||
* This method may update $newObject to add any status changes.
|
||||
*
|
||||
* @param VCalendar|string $oldObject
|
||||
* @param array $ignore any addresses to not send messages to
|
||||
* @param bool $modified a marker to indicate that the original object
|
||||
* modified by this process
|
||||
*/
|
||||
protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false)
|
||||
{
|
||||
$broker = new ITip\Broker();
|
||||
$messages = $broker->parseEvent($newObject, $addresses, $oldObject);
|
||||
|
||||
if ($messages) {
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
if (in_array($message->recipient, $ignore)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->deliver($message);
|
||||
|
||||
if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
|
||||
if ($message->scheduleStatus) {
|
||||
$newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
|
||||
}
|
||||
unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
|
||||
} else {
|
||||
if (isset($newObject->VEVENT->ATTENDEE)) {
|
||||
foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
|
||||
if ($attendee->getNormalizedValue() === $message->recipient) {
|
||||
if ($message->scheduleStatus) {
|
||||
$attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
|
||||
}
|
||||
unset($attendee['SCHEDULE-FORCE-SEND']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of addresses that are associated with a principal.
|
||||
*
|
||||
* @param string $principal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAddressesForPrincipal($principal)
|
||||
{
|
||||
$CUAS = '{'.self::NS_CALDAV.'}calendar-user-address-set';
|
||||
|
||||
$properties = $this->server->getProperties(
|
||||
$principal,
|
||||
[$CUAS]
|
||||
);
|
||||
|
||||
// If we can't find this information, we'll stop processing
|
||||
if (!isset($properties[$CUAS])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$addresses = $properties[$CUAS]->getHrefs();
|
||||
|
||||
return $addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles POST requests to the schedule-outbox.
|
||||
*
|
||||
* Currently, two types of requests are supported:
|
||||
* * FREEBUSY requests from RFC 6638
|
||||
* * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
|
||||
*
|
||||
* The latter is from an expired early draft of the CalDAV scheduling
|
||||
* extensions, but iCal depends on a feature from that spec, so we
|
||||
* implement it.
|
||||
*/
|
||||
public function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$outboxPath = $request->getPath();
|
||||
|
||||
// Parsing the request body
|
||||
try {
|
||||
$vObject = VObject\Reader::read($request->getBody());
|
||||
} catch (VObject\ParseException $e) {
|
||||
throw new BadRequest('The request body must be a valid iCalendar object. Parse error: '.$e->getMessage());
|
||||
}
|
||||
|
||||
// The incoming iCalendar object must have a METHOD property, and a
|
||||
// component. The combination of both determines what type of request
|
||||
// this is.
|
||||
$componentType = null;
|
||||
foreach ($vObject->getComponents() as $component) {
|
||||
if ('VTIMEZONE' !== $component->name) {
|
||||
$componentType = $component->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($componentType)) {
|
||||
throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
|
||||
}
|
||||
|
||||
// Validating the METHOD
|
||||
$method = strtoupper((string) $vObject->METHOD);
|
||||
if (!$method) {
|
||||
throw new BadRequest('A METHOD property must be specified in iTIP messages');
|
||||
}
|
||||
|
||||
// So we support one type of request:
|
||||
//
|
||||
// REQUEST with a VFREEBUSY component
|
||||
|
||||
$acl = $this->server->getPlugin('acl');
|
||||
|
||||
if ('VFREEBUSY' === $componentType && 'REQUEST' === $method) {
|
||||
$acl && $acl->checkPrivileges($outboxPath, '{'.self::NS_CALDAV.'}schedule-send-freebusy');
|
||||
$this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
|
||||
|
||||
// Destroy circular references so PHP can GC the object.
|
||||
$vObject->destroy();
|
||||
unset($vObject);
|
||||
} else {
|
||||
throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for parsing a free-busy query request and
|
||||
* returning it's result.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$vFreeBusy = $vObject->VFREEBUSY;
|
||||
$organizer = $vFreeBusy->ORGANIZER;
|
||||
|
||||
$organizer = (string) $organizer;
|
||||
|
||||
// Validating if the organizer matches the owner of the inbox.
|
||||
$owner = $outbox->getOwner();
|
||||
|
||||
$caldavNS = '{'.self::NS_CALDAV.'}';
|
||||
|
||||
$uas = $caldavNS.'calendar-user-address-set';
|
||||
$props = $this->server->getProperties($owner, [$uas]);
|
||||
|
||||
if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
|
||||
throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
|
||||
}
|
||||
|
||||
if (!isset($vFreeBusy->ATTENDEE)) {
|
||||
throw new BadRequest('You must at least specify 1 attendee');
|
||||
}
|
||||
|
||||
$attendees = [];
|
||||
foreach ($vFreeBusy->ATTENDEE as $attendee) {
|
||||
$attendees[] = (string) $attendee;
|
||||
}
|
||||
|
||||
if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
|
||||
throw new BadRequest('DTSTART and DTEND must both be specified');
|
||||
}
|
||||
|
||||
$startRange = $vFreeBusy->DTSTART->getDateTime();
|
||||
$endRange = $vFreeBusy->DTEND->getDateTime();
|
||||
|
||||
$results = [];
|
||||
foreach ($attendees as $attendee) {
|
||||
$results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
$scheduleResponse = $dom->createElement('cal:schedule-response');
|
||||
foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
|
||||
$scheduleResponse->setAttribute('xmlns:'.$prefix, $namespace);
|
||||
}
|
||||
$dom->appendChild($scheduleResponse);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$xresponse = $dom->createElement('cal:response');
|
||||
|
||||
$recipient = $dom->createElement('cal:recipient');
|
||||
$recipientHref = $dom->createElement('d:href');
|
||||
|
||||
$recipientHref->appendChild($dom->createTextNode($result['href']));
|
||||
$recipient->appendChild($recipientHref);
|
||||
$xresponse->appendChild($recipient);
|
||||
|
||||
$reqStatus = $dom->createElement('cal:request-status');
|
||||
$reqStatus->appendChild($dom->createTextNode($result['request-status']));
|
||||
$xresponse->appendChild($reqStatus);
|
||||
|
||||
if (isset($result['calendar-data'])) {
|
||||
$calendardata = $dom->createElement('cal:calendar-data');
|
||||
$calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
|
||||
$xresponse->appendChild($calendardata);
|
||||
}
|
||||
$scheduleResponse->appendChild($xresponse);
|
||||
}
|
||||
|
||||
$response->setStatus(200);
|
||||
$response->setHeader('Content-Type', 'application/xml');
|
||||
$response->setBody($dom->saveXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns free-busy information for a specific address. The returned
|
||||
* data is an array containing the following properties:.
|
||||
*
|
||||
* calendar-data : A VFREEBUSY VObject
|
||||
* request-status : an iTip status code.
|
||||
* href: The principal's email address, as requested
|
||||
*
|
||||
* The following request status codes may be returned:
|
||||
* * 2.0;description
|
||||
* * 3.7;description
|
||||
*
|
||||
* @param string $email address
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request)
|
||||
{
|
||||
$caldavNS = '{'.self::NS_CALDAV.'}';
|
||||
|
||||
$aclPlugin = $this->server->getPlugin('acl');
|
||||
if ('mailto:' === substr($email, 0, 7)) {
|
||||
$email = substr($email, 7);
|
||||
}
|
||||
|
||||
$result = $aclPlugin->principalSearch(
|
||||
['{http://sabredav.org/ns}email-address' => $email],
|
||||
[
|
||||
'{DAV:}principal-URL',
|
||||
$caldavNS.'calendar-home-set',
|
||||
$caldavNS.'schedule-inbox-URL',
|
||||
'{http://sabredav.org/ns}email-address',
|
||||
]
|
||||
);
|
||||
|
||||
if (!count($result)) {
|
||||
return [
|
||||
'request-status' => '3.7;Could not find principal',
|
||||
'href' => 'mailto:'.$email,
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($result[0][200][$caldavNS.'calendar-home-set'])) {
|
||||
return [
|
||||
'request-status' => '3.7;No calendar-home-set property found',
|
||||
'href' => 'mailto:'.$email,
|
||||
];
|
||||
}
|
||||
if (!isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
|
||||
return [
|
||||
'request-status' => '3.7;No schedule-inbox-URL property found',
|
||||
'href' => 'mailto:'.$email,
|
||||
];
|
||||
}
|
||||
$homeSet = $result[0][200][$caldavNS.'calendar-home-set']->getHref();
|
||||
$inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
|
||||
|
||||
// Do we have permission?
|
||||
$aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
|
||||
|
||||
// Grabbing the calendar list
|
||||
$objects = [];
|
||||
$calendarTimeZone = new DateTimeZone('UTC');
|
||||
|
||||
foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
|
||||
if (!$node instanceof ICalendar) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sct = $caldavNS.'schedule-calendar-transp';
|
||||
$ctz = $caldavNS.'calendar-timezone';
|
||||
$props = $node->getProperties([$sct, $ctz]);
|
||||
|
||||
if (isset($props[$sct]) && ScheduleCalendarTransp::TRANSPARENT == $props[$sct]->getValue()) {
|
||||
// If a calendar is marked as 'transparent', it means we must
|
||||
// ignore it for free-busy purposes.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($props[$ctz])) {
|
||||
$vtimezoneObj = VObject\Reader::read($props[$ctz]);
|
||||
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
|
||||
|
||||
// Destroy circular references so PHP can garbage collect the object.
|
||||
$vtimezoneObj->destroy();
|
||||
}
|
||||
|
||||
// Getting the list of object uris within the time-range
|
||||
$urls = $node->calendarQuery([
|
||||
'name' => 'VCALENDAR',
|
||||
'comp-filters' => [
|
||||
[
|
||||
'name' => 'VEVENT',
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [],
|
||||
'is-not-defined' => false,
|
||||
'time-range' => [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
],
|
||||
],
|
||||
],
|
||||
'prop-filters' => [],
|
||||
'is-not-defined' => false,
|
||||
'time-range' => null,
|
||||
]);
|
||||
|
||||
$calObjects = array_map(function ($url) use ($node) {
|
||||
$obj = $node->getChild($url)->get();
|
||||
|
||||
return $obj;
|
||||
}, $urls);
|
||||
|
||||
$objects = array_merge($objects, $calObjects);
|
||||
}
|
||||
|
||||
$inboxProps = $this->server->getProperties(
|
||||
$inboxUrl,
|
||||
$caldavNS.'calendar-availability'
|
||||
);
|
||||
|
||||
$vcalendar = new VObject\Component\VCalendar();
|
||||
$vcalendar->METHOD = 'REPLY';
|
||||
|
||||
$generator = new VObject\FreeBusyGenerator();
|
||||
$generator->setObjects($objects);
|
||||
$generator->setTimeRange($start, $end);
|
||||
$generator->setBaseObject($vcalendar);
|
||||
$generator->setTimeZone($calendarTimeZone);
|
||||
|
||||
if ($inboxProps) {
|
||||
$generator->setVAvailability(
|
||||
VObject\Reader::read(
|
||||
$inboxProps[$caldavNS.'calendar-availability']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$result = $generator->getResult();
|
||||
|
||||
$vcalendar->VFREEBUSY->ATTENDEE = 'mailto:'.$email;
|
||||
$vcalendar->VFREEBUSY->UID = (string) $request->VFREEBUSY->UID;
|
||||
$vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
|
||||
|
||||
return [
|
||||
'calendar-data' => $result,
|
||||
'request-status' => '2.0;Success',
|
||||
'href' => 'mailto:'.$email,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the 'Schedule-Reply' header
|
||||
* and returns false if it's 'F', otherwise true.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function scheduleReply(RequestInterface $request)
|
||||
{
|
||||
$scheduleReply = $request->getHeader('Schedule-Reply');
|
||||
|
||||
return 'F' !== $scheduleReply;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds calendar-auto-schedule, as defined in rfc6638',
|
||||
'link' => 'http://sabre.io/dav/scheduling/',
|
||||
];
|
||||
}
|
||||
}
|
||||
130
vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
vendored
Normal file
130
vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Schedule;
|
||||
|
||||
use Sabre\CalDAV\Backend;
|
||||
use Sabre\DAV\Exception\MethodNotAllowed;
|
||||
|
||||
/**
|
||||
* The SchedulingObject represents a scheduling object in the Inbox collection.
|
||||
*
|
||||
* @author Brett (https://github.com/bretten)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
*/
|
||||
class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* The following properties may be passed within $objectData:
|
||||
*
|
||||
* * uri - A unique uri. Only the 'basename' must be passed.
|
||||
* * principaluri - the principal that owns the object.
|
||||
* * calendardata (optional) - The iCalendar data
|
||||
* * etag - (optional) The etag for this object, MUST be encloded with
|
||||
* double-quotes.
|
||||
* * size - (optional) The size of the data in bytes.
|
||||
* * lastmodified - (optional) format as a unix timestamp.
|
||||
* * acl - (optional) Use this to override the default ACL for the node.
|
||||
*/
|
||||
public function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData)
|
||||
{
|
||||
parent::__construct($caldavBackend, [], $objectData);
|
||||
|
||||
if (!isset($objectData['uri'])) {
|
||||
throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
|
||||
}
|
||||
|
||||
return $this->objectData['calendardata'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ICalendar-formatted object.
|
||||
*
|
||||
* @param string|resource $calendarData
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function put($calendarData)
|
||||
{
|
||||
throw new MethodNotAllowed('Updating scheduling objects is not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the scheduling message.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->objectData['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => '{DAV:}owner',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->objectData['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->objectData['principaluri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
219
vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
vendored
Normal file
219
vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAV\Sharing\Plugin as SPlugin;
|
||||
|
||||
/**
|
||||
* This object represents a CalDAV calendar that is shared by a different user.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SharedCalendar extends Calendar implements ISharedCalendar
|
||||
{
|
||||
/**
|
||||
* Returns the 'access level' for the instance of this shared resource.
|
||||
*
|
||||
* The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
|
||||
* constants.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getShareAccess()
|
||||
{
|
||||
return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function must return a URI that uniquely identifies the shared
|
||||
* resource. This URI should be identical across instances, and is
|
||||
* also used in several other XML bodies to connect invites to
|
||||
* resources.
|
||||
*
|
||||
* This may simply be a relative reference to the original shared instance,
|
||||
* but it could also be a urn. As long as it's a valid URI and unique.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShareResourceUri()
|
||||
{
|
||||
return $this->calendarInfo['share-resource-uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of sharees.
|
||||
*
|
||||
* Every item must be a Sharee object.
|
||||
*
|
||||
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
|
||||
*/
|
||||
public function updateInvites(array $sharees)
|
||||
{
|
||||
$this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of people whom this resource is shared with.
|
||||
*
|
||||
* Every item in the returned array must be a Sharee object with
|
||||
* at least the following properties set:
|
||||
*
|
||||
* * $href
|
||||
* * $shareAccess
|
||||
* * $inviteStatus
|
||||
*
|
||||
* and optionally:
|
||||
*
|
||||
* * $properties
|
||||
*
|
||||
* @return \Sabre\DAV\Xml\Element\Sharee[]
|
||||
*/
|
||||
public function getInvites()
|
||||
{
|
||||
return $this->caldavBackend->getInvites($this->calendarInfo['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this calendar as published.
|
||||
*
|
||||
* Publishing a calendar should automatically create a read-only, public,
|
||||
* subscribable calendar.
|
||||
*
|
||||
* @param bool $value
|
||||
*/
|
||||
public function setPublishStatus($value)
|
||||
{
|
||||
$this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
switch ($this->getShareAccess()) {
|
||||
case SPlugin::ACCESS_NOTSHARED:
|
||||
case SPlugin::ACCESS_SHAREDOWNER:
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}share',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}share',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
// no break intentional!
|
||||
case SPlugin::ACCESS_READWRITE:
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
// no break intentional!
|
||||
case SPlugin::ACCESS_READ:
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{'.Plugin::NS_CALDAV.'}read-free-busy',
|
||||
'principal' => '{DAV:}authenticated',
|
||||
'protected' => true,
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ACL's for calendar objects in this calendar.
|
||||
* The result of this method automatically gets passed to the
|
||||
* calendar-object nodes in the calendar.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildACL()
|
||||
{
|
||||
$acl = [];
|
||||
|
||||
switch ($this->getShareAccess()) {
|
||||
case SPlugin::ACCESS_NOTSHARED:
|
||||
case SPlugin::ACCESS_SHAREDOWNER:
|
||||
case SPlugin::ACCESS_READWRITE:
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
// no break intentional
|
||||
case SPlugin::ACCESS_READ:
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'].'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
}
|
||||
350
vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
vendored
Normal file
350
vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Xml\Property\LocalHref;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SharingPlugin extends DAV\ServerPlugin
|
||||
{
|
||||
/**
|
||||
* Reference to SabreDAV server object.
|
||||
*
|
||||
* @var 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 ['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.
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
|
||||
if (is_null($this->server->getPlugin('sharing'))) {
|
||||
throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.');
|
||||
}
|
||||
|
||||
array_push(
|
||||
$this->server->protectedProperties,
|
||||
'{'.Plugin::NS_CALENDARSERVER.'}invite',
|
||||
'{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes',
|
||||
'{'.Plugin::NS_CALENDARSERVER.'}shared-url'
|
||||
);
|
||||
|
||||
$this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
|
||||
$this->server->xml->elementMap['{'.Plugin::NS_CALENDARSERVER.'}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
|
||||
|
||||
$this->server->on('propFind', [$this, 'propFindEarly']);
|
||||
$this->server->on('propFind', [$this, 'propFindLate'], 150);
|
||||
$this->server->on('propPatch', [$this, 'propPatch'], 40);
|
||||
$this->server->on('method:POST', [$this, 'httpPost']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when properties are requested for a certain
|
||||
* node.
|
||||
*
|
||||
* This allows us to inject any properties early.
|
||||
*/
|
||||
public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
|
||||
{
|
||||
if ($node instanceof ISharedCalendar) {
|
||||
$propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}invite', function () use ($node) {
|
||||
return new Xml\Property\Invite(
|
||||
$node->getInvites()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered *after* all properties have been retrieved.
|
||||
* This allows us to inject the correct resourcetype for calendars that
|
||||
* have been shared.
|
||||
*/
|
||||
public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
|
||||
{
|
||||
if ($node instanceof ISharedCalendar) {
|
||||
$shareAccess = $node->getShareAccess();
|
||||
if ($rt = $propFind->get('{DAV:}resourcetype')) {
|
||||
switch ($shareAccess) {
|
||||
case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER:
|
||||
$rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared-owner');
|
||||
break;
|
||||
case \Sabre\DAV\Sharing\Plugin::ACCESS_READ:
|
||||
case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE:
|
||||
$rt->add('{'.Plugin::NS_CALENDARSERVER.'}shared');
|
||||
break;
|
||||
}
|
||||
}
|
||||
$propFind->handle('{'.Plugin::NS_CALENDARSERVER.'}allowed-sharing-modes', function () {
|
||||
return new Xml\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 string $path
|
||||
*/
|
||||
public function propPatch($path, DAV\PropPatch $propPatch)
|
||||
{
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
if (!$node instanceof ISharedCalendar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $node->getShareAccess() || \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED === $node->getShareAccess()) {
|
||||
$propPatch->handle('{DAV:}resourcetype', function ($value) use ($node) {
|
||||
if ($value->is('{'.Plugin::NS_CALENDARSERVER.'}shared-owner')) {
|
||||
return false;
|
||||
}
|
||||
$shares = $node->getInvites();
|
||||
foreach ($shares as $share) {
|
||||
$share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
|
||||
}
|
||||
$node->updateInvites($shares);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We intercept this to handle POST requests on calendars.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function httpPost(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$path = $request->getPath();
|
||||
|
||||
// Only handling xml
|
||||
$contentType = $request->getHeader('Content-Type');
|
||||
if (null === $contentType) {
|
||||
return;
|
||||
}
|
||||
if (false === strpos($contentType, 'application/xml') && false === strpos($contentType, 'text/xml')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Making sure the node exists
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
} catch (DAV\Exception\NotFound $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$requestBody = $request->getBodyAsString();
|
||||
|
||||
// If this request handler could not deal with this POST request, it
|
||||
// will return 'null' and other plugins get a chance to handle the
|
||||
// request.
|
||||
//
|
||||
// However, we already requested the full body. This is a problem,
|
||||
// because a body can only be read once. This is why we preemptively
|
||||
// re-populated the request body with the existing data.
|
||||
$request->setBody($requestBody);
|
||||
|
||||
$message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
|
||||
|
||||
switch ($documentType) {
|
||||
// Both the DAV:share-resource and CALENDARSERVER:share requests
|
||||
// behave identically.
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}share':
|
||||
|
||||
$sharingPlugin = $this->server->getPlugin('sharing');
|
||||
$sharingPlugin->shareResource($path, $message->sharees);
|
||||
|
||||
$response->setStatus(200);
|
||||
// Adding this because sending a response body may cause issues,
|
||||
// and I wanted some type of indicator the response was handled.
|
||||
$response->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 CalendarHome) {
|
||||
return;
|
||||
}
|
||||
$this->server->transactionType = 'post-invite-reply';
|
||||
|
||||
// Getting ACL info
|
||||
$acl = $this->server->getPlugin('acl');
|
||||
|
||||
// If there's no ACL support, we allow everything
|
||||
if ($acl) {
|
||||
$acl->checkPrivileges($path, '{DAV:}write');
|
||||
}
|
||||
|
||||
$url = $node->shareReply(
|
||||
$message->href,
|
||||
$message->status,
|
||||
$message->calendarUri,
|
||||
$message->inReplyTo,
|
||||
$message->summary
|
||||
);
|
||||
|
||||
$response->setStatus(200);
|
||||
// Adding this because sending a response body may cause issues,
|
||||
// and I wanted some type of indicator the response was handled.
|
||||
$response->setHeader('X-Sabre-Status', 'everything-went-well');
|
||||
|
||||
if ($url) {
|
||||
$writer = $this->server->xml->getWriter();
|
||||
$writer->contextUri = $request->getUrl();
|
||||
$writer->openMemory();
|
||||
$writer->startDocument();
|
||||
$writer->startElement('{'.Plugin::NS_CALENDARSERVER.'}shared-as');
|
||||
$writer->write(new LocalHref($url));
|
||||
$writer->endElement();
|
||||
$response->setHeader('Content-Type', 'application/xml');
|
||||
$response->setBody($writer->outputMemory());
|
||||
}
|
||||
|
||||
// Breaking the event chain
|
||||
return false;
|
||||
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}publish-calendar':
|
||||
|
||||
// We can only deal with IShareableCalendar objects
|
||||
if (!$node instanceof ISharedCalendar) {
|
||||
return;
|
||||
}
|
||||
$this->server->transactionType = 'post-publish-calendar';
|
||||
|
||||
// Getting ACL info
|
||||
$acl = $this->server->getPlugin('acl');
|
||||
|
||||
// If there's no ACL support, we allow everything
|
||||
if ($acl) {
|
||||
$acl->checkPrivileges($path, '{DAV:}share');
|
||||
}
|
||||
|
||||
$node->setPublishStatus(true);
|
||||
|
||||
// iCloud sends back the 202, so we will too.
|
||||
$response->setStatus(202);
|
||||
|
||||
// Adding this because sending a response body may cause issues,
|
||||
// and I wanted some type of indicator the response was handled.
|
||||
$response->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 ISharedCalendar) {
|
||||
return;
|
||||
}
|
||||
$this->server->transactionType = 'post-unpublish-calendar';
|
||||
|
||||
// Getting ACL info
|
||||
$acl = $this->server->getPlugin('acl');
|
||||
|
||||
// If there's no ACL support, we allow everything
|
||||
if ($acl) {
|
||||
$acl->checkPrivileges($path, '{DAV:}share');
|
||||
}
|
||||
|
||||
$node->setPublishStatus(false);
|
||||
|
||||
$response->setStatus(200);
|
||||
|
||||
// Adding this because sending a response body may cause issues,
|
||||
// and I wanted some type of indicator the response was handled.
|
||||
$response->setHeader('X-Sabre-Status', 'everything-went-well');
|
||||
|
||||
// Breaking the event chain
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds support for caldav-sharing.',
|
||||
'link' => 'http://sabre.io/dav/caldav-sharing/',
|
||||
];
|
||||
}
|
||||
}
|
||||
41
vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
vendored
Normal file
41
vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Subscriptions;
|
||||
|
||||
use Sabre\DAV\ICollection;
|
||||
use Sabre\DAV\IProperties;
|
||||
|
||||
/**
|
||||
* ISubscription.
|
||||
*
|
||||
* Nodes implementing this interface represent calendar subscriptions.
|
||||
*
|
||||
* The subscription node doesn't do much, other than returning and updating
|
||||
* subscription-related properties.
|
||||
*
|
||||
* The following properties should be supported:
|
||||
*
|
||||
* 1. {DAV:}displayname
|
||||
* 2. {http://apple.com/ns/ical/}refreshrate
|
||||
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
|
||||
* should not be stripped).
|
||||
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
|
||||
* should not be stripped).
|
||||
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
|
||||
* attachments should not be stripped).
|
||||
* 6. {http://calendarserver.org/ns/}source (Must be a
|
||||
* Sabre\DAV\Property\Href).
|
||||
* 7. {http://apple.com/ns/ical/}calendar-color
|
||||
* 8. {http://apple.com/ns/ical/}calendar-order
|
||||
*
|
||||
* It is recommended to support every property.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ISubscription extends ICollection, IProperties
|
||||
{
|
||||
}
|
||||
108
vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php
vendored
Normal file
108
vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Subscriptions;
|
||||
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
|
||||
/**
|
||||
* This plugin adds calendar-subscription support to your CalDAV server.
|
||||
*
|
||||
* Some clients support 'managed subscriptions' server-side. This is basically
|
||||
* a list of subscription urls a user is using.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Plugin extends ServerPlugin
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function initialize(Server $server)
|
||||
{
|
||||
$server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] =
|
||||
'{http://calendarserver.org/ns/}subscribed';
|
||||
|
||||
$server->xml->elementMap['{http://calendarserver.org/ns/}source'] =
|
||||
'Sabre\\DAV\\Xml\\Property\\Href';
|
||||
|
||||
$server->on('propFind', [$this, 'propFind'], 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ['calendarserver-subscribed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered after properties have been fetched.
|
||||
*/
|
||||
public function propFind(PropFind $propFind, INode $node)
|
||||
{
|
||||
// There's a bunch of properties that must appear as a self-closing
|
||||
// xml-element. This event handler ensures that this will be the case.
|
||||
$props = [
|
||||
'{http://calendarserver.org/ns/}subscribed-strip-alarms',
|
||||
'{http://calendarserver.org/ns/}subscribed-strip-attachments',
|
||||
'{http://calendarserver.org/ns/}subscribed-strip-todos',
|
||||
];
|
||||
|
||||
foreach ($props as $prop) {
|
||||
if (200 === $propFind->getStatus($prop)) {
|
||||
$propFind->set($prop, '', 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'subscriptions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.',
|
||||
'link' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
204
vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
vendored
Normal file
204
vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Subscriptions;
|
||||
|
||||
use Sabre\CalDAV\Backend\SubscriptionSupport;
|
||||
use Sabre\DAV\Collection;
|
||||
use Sabre\DAV\PropPatch;
|
||||
use Sabre\DAV\Xml\Property\Href;
|
||||
use Sabre\DAVACL\ACLTrait;
|
||||
use Sabre\DAVACL\IACL;
|
||||
|
||||
/**
|
||||
* Subscription Node.
|
||||
*
|
||||
* This node represents a subscription.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Subscription extends Collection implements ISubscription, IACL
|
||||
{
|
||||
use ACLTrait;
|
||||
|
||||
/**
|
||||
* caldavBackend.
|
||||
*
|
||||
* @var SubscriptionSupport
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* subscriptionInfo.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $subscriptionInfo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo)
|
||||
{
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->subscriptionInfo = $subscriptionInfo;
|
||||
|
||||
$required = [
|
||||
'id',
|
||||
'uri',
|
||||
'principaluri',
|
||||
'source',
|
||||
];
|
||||
|
||||
foreach ($required as $r) {
|
||||
if (!isset($subscriptionInfo[$r])) {
|
||||
throw new \InvalidArgumentException('The '.$r.' field is required when creating a subscription node');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the node.
|
||||
*
|
||||
* This is used to generate the url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->subscriptionInfo['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification time.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
if (isset($this->subscriptionInfo['lastmodified'])) {
|
||||
return $this->subscriptionInfo['lastmodified'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current node.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->caldavBackend->deleteSubscription(
|
||||
$this->subscriptionInfo['id']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the child nodes.
|
||||
*
|
||||
* @return \Sabre\DAV\INode[]
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties on this node.
|
||||
*
|
||||
* This method received a PropPatch object, which contains all the
|
||||
* information about the update.
|
||||
*
|
||||
* To update specific properties, call the 'handle' method on this object.
|
||||
* Read the PropPatch documentation for more information.
|
||||
*/
|
||||
public function propPatch(PropPatch $propPatch)
|
||||
{
|
||||
return $this->caldavBackend->updateSubscription(
|
||||
$this->subscriptionInfo['id'],
|
||||
$propPatch
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Note that it's fine to liberally give properties back, instead of
|
||||
* conforming to the list of requested properties.
|
||||
* The Server class will filter out the extra.
|
||||
*
|
||||
* @param array $properties
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties($properties)
|
||||
{
|
||||
$r = [];
|
||||
|
||||
foreach ($properties as $prop) {
|
||||
switch ($prop) {
|
||||
case '{http://calendarserver.org/ns/}source':
|
||||
$r[$prop] = new Href($this->subscriptionInfo['source']);
|
||||
break;
|
||||
default:
|
||||
if (array_key_exists($prop, $this->subscriptionInfo)) {
|
||||
$r[$prop] = $this->subscriptionInfo[$prop];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->subscriptionInfo['principaluri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner().'/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
81
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
vendored
Normal file
81
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* CalendarData parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}calendar-data XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-9.6
|
||||
*
|
||||
* This element is used in three distinct places in the caldav spec, but in
|
||||
* this case, this element class only implements the calendar-data element as
|
||||
* it appears in a DAV:prop element, in a calendar-query or calendar-multiget
|
||||
* REPORT request.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarData implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar',
|
||||
'version' => $reader->getAttribute('version') ?: '2.0',
|
||||
];
|
||||
|
||||
$elems = (array) $reader->parseInnerTree();
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CALDAV.'}expand':
|
||||
|
||||
$result['expand'] = [
|
||||
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
|
||||
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
|
||||
];
|
||||
|
||||
if (!$result['expand']['start'] || !$result['expand']['end']) {
|
||||
throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data');
|
||||
}
|
||||
if ($result['expand']['end'] <= $result['expand']['start']) {
|
||||
throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
94
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
vendored
Normal file
94
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* CompFilter parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}comp-filter XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-9.6
|
||||
*
|
||||
* The result will be spit out as an array.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CompFilter implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'name' => null,
|
||||
'is-not-defined' => false,
|
||||
'comp-filters' => [],
|
||||
'prop-filters' => [],
|
||||
'time-range' => false,
|
||||
];
|
||||
|
||||
$att = $reader->parseAttributes();
|
||||
$result['name'] = $att['name'];
|
||||
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
if (is_array($elems)) {
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CALDAV.'}comp-filter':
|
||||
$result['comp-filters'][] = $elem['value'];
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}prop-filter':
|
||||
$result['prop-filters'][] = $elem['value'];
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
|
||||
$result['is-not-defined'] = true;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}time-range':
|
||||
if ('VCALENDAR' === $result['name']) {
|
||||
throw new BadRequest('You cannot add time-range filters on the VCALENDAR component');
|
||||
}
|
||||
$result['time-range'] = [
|
||||
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
|
||||
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
|
||||
];
|
||||
if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
|
||||
throw new BadRequest('The end-date must be larger than the start-date');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
79
vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
79
vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* PropFilter parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}param-filter XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-9.7.3
|
||||
*
|
||||
* The result will be spit out as an array.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class ParamFilter implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* Important note 2: You are responsible for advancing the reader to the
|
||||
* next element. Not doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'name' => null,
|
||||
'is-not-defined' => false,
|
||||
'text-match' => null,
|
||||
];
|
||||
|
||||
$att = $reader->parseAttributes();
|
||||
$result['name'] = $att['name'];
|
||||
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
if (is_array($elems)) {
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
|
||||
$result['is-not-defined'] = true;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}text-match':
|
||||
$result['text-match'] = [
|
||||
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
|
||||
'value' => $elem['value'],
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
95
vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
vendored
Normal file
95
vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* PropFilter parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}prop-filter XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-9.7.2
|
||||
*
|
||||
* The result will be spit out as an array.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class PropFilter implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'name' => null,
|
||||
'is-not-defined' => false,
|
||||
'param-filters' => [],
|
||||
'text-match' => null,
|
||||
'time-range' => false,
|
||||
];
|
||||
|
||||
$att = $reader->parseAttributes();
|
||||
$result['name'] = $att['name'];
|
||||
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
if (is_array($elems)) {
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CALDAV.'}param-filter':
|
||||
$result['param-filters'][] = $elem['value'];
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}is-not-defined':
|
||||
$result['is-not-defined'] = true;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}time-range':
|
||||
$result['time-range'] = [
|
||||
'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
|
||||
'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
|
||||
];
|
||||
if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
|
||||
throw new BadRequest('The end-date must be larger than the start-date');
|
||||
}
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}text-match':
|
||||
$result['text-match'] = [
|
||||
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
|
||||
'value' => $elem['value'],
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
290
vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
vendored
Normal file
290
vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
vendored
Normal file
@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Notification;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
|
||||
use Sabre\DAV;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* This class represents the cs:invite-notification notification element.
|
||||
*
|
||||
* This element is defined here:
|
||||
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Invite implements NotificationInterface
|
||||
{
|
||||
/**
|
||||
* 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 CalDAV\Xml\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 = [
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
$writer->writeElement('{'.CalDAV\Plugin::NS_CALENDARSERVER.'}invite-notification');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method serializes the entire notification, as it is used in the
|
||||
* response body.
|
||||
*/
|
||||
public function xmlSerializeFull(Writer $writer)
|
||||
{
|
||||
$cs = '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}';
|
||||
|
||||
$this->dtStamp->setTimezone(new \DateTimeZone('GMT'));
|
||||
$writer->writeElement($cs.'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
|
||||
|
||||
$writer->startElement($cs.'invite-notification');
|
||||
|
||||
$writer->writeElement($cs.'uid', $this->id);
|
||||
$writer->writeElement('{DAV:}href', $this->href);
|
||||
|
||||
switch ($this->type) {
|
||||
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
|
||||
$writer->writeElement($cs.'invite-accepted');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::INVITE_NORESPONSE:
|
||||
$writer->writeElement($cs.'invite-noresponse');
|
||||
break;
|
||||
}
|
||||
|
||||
$writer->writeElement($cs.'hosturl', [
|
||||
'{DAV:}href' => $writer->contextUri.$this->hostUrl,
|
||||
]);
|
||||
|
||||
if ($this->summary) {
|
||||
$writer->writeElement($cs.'summary', $this->summary);
|
||||
}
|
||||
|
||||
$writer->startElement($cs.'access');
|
||||
if ($this->readOnly) {
|
||||
$writer->writeElement($cs.'read');
|
||||
} else {
|
||||
$writer->writeElement($cs.'read-write');
|
||||
}
|
||||
$writer->endElement(); // access
|
||||
|
||||
$writer->startElement($cs.'organizer');
|
||||
// If the organizer contains a 'mailto:' part, it means it should be
|
||||
// treated as absolute.
|
||||
if ('mailto:' === strtolower(substr($this->organizer, 0, 7))) {
|
||||
$writer->writeElement('{DAV:}href', $this->organizer);
|
||||
} else {
|
||||
$writer->writeElement('{DAV:}href', $writer->contextUri.$this->organizer);
|
||||
}
|
||||
if ($this->commonName) {
|
||||
$writer->writeElement($cs.'common-name', $this->commonName);
|
||||
}
|
||||
if ($this->firstName) {
|
||||
$writer->writeElement($cs.'first-name', $this->firstName);
|
||||
}
|
||||
if ($this->lastName) {
|
||||
$writer->writeElement($cs.'last-name', $this->lastName);
|
||||
}
|
||||
$writer->endElement(); // organizer
|
||||
|
||||
if ($this->commonName) {
|
||||
$writer->writeElement($cs.'organizer-cn', $this->commonName);
|
||||
}
|
||||
if ($this->firstName) {
|
||||
$writer->writeElement($cs.'organizer-first', $this->firstName);
|
||||
}
|
||||
if ($this->lastName) {
|
||||
$writer->writeElement($cs.'organizer-last', $this->lastName);
|
||||
}
|
||||
if ($this->supportedComponents) {
|
||||
$writer->writeElement('{'.CalDAV\Plugin::NS_CALDAV.'}supported-calendar-component-set', $this->supportedComponents);
|
||||
}
|
||||
|
||||
$writer->endElement(); // invite-notification
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
199
vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
vendored
Normal file
199
vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Notification;
|
||||
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\CalDAV\SharingPlugin;
|
||||
use Sabre\DAV;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* This class represents the cs:invite-reply notification element.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class InviteReply implements NotificationInterface
|
||||
{
|
||||
/**
|
||||
* 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 = [
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
$writer->writeElement('{'.CalDAV\Plugin::NS_CALENDARSERVER.'}invite-reply');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method serializes the entire notification, as it is used in the
|
||||
* response body.
|
||||
*/
|
||||
public function xmlSerializeFull(Writer $writer)
|
||||
{
|
||||
$cs = '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}';
|
||||
|
||||
$this->dtStamp->setTimezone(new \DateTimeZone('GMT'));
|
||||
$writer->writeElement($cs.'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
|
||||
|
||||
$writer->startElement($cs.'invite-reply');
|
||||
|
||||
$writer->writeElement($cs.'uid', $this->id);
|
||||
$writer->writeElement($cs.'in-reply-to', $this->inReplyTo);
|
||||
$writer->writeElement('{DAV:}href', $this->href);
|
||||
|
||||
switch ($this->type) {
|
||||
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
|
||||
$writer->writeElement($cs.'invite-accepted');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::INVITE_DECLINED:
|
||||
$writer->writeElement($cs.'invite-declined');
|
||||
break;
|
||||
}
|
||||
|
||||
$writer->writeElement($cs.'hosturl', [
|
||||
'{DAV:}href' => $writer->contextUri.$this->hostUrl,
|
||||
]);
|
||||
|
||||
if ($this->summary) {
|
||||
$writer->writeElement($cs.'summary', $this->summary);
|
||||
}
|
||||
$writer->endElement(); // invite-reply
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
43
vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
vendored
Normal file
43
vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Notification;
|
||||
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* This interface reflects a single notification type.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface NotificationInterface extends XmlSerializable
|
||||
{
|
||||
/**
|
||||
* This method serializes the entire notification, as it is used in the
|
||||
* response body.
|
||||
*/
|
||||
public function xmlSerializeFull(Writer $writer);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Returns the ETag for this notification.
|
||||
*
|
||||
* The ETag must be surrounded by literal double-quotes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag();
|
||||
}
|
||||
171
vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php
vendored
Normal file
171
vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Notification;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* SystemStatus notification.
|
||||
*
|
||||
* This notification can be used to indicate to the user that the system is
|
||||
* down.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SystemStatus implements NotificationInterface
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The serialize method is called during xml writing.
|
||||
*
|
||||
* It should use the $writer argument to encode this object into XML.
|
||||
*
|
||||
* Important note: it is not needed to create the parent element. The
|
||||
* parent element is already created, and we only have to worry about
|
||||
* attributes, child elements and text (if any).
|
||||
*
|
||||
* Important note 2: If you are writing any new elements, you are also
|
||||
* responsible for closing them.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
$writer->startElement('{'.Plugin::NS_CALENDARSERVER.'}systemstatus');
|
||||
$writer->writeAttribute('type', $type);
|
||||
$writer->endElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method serializes the entire notification, as it is used in the
|
||||
* response body.
|
||||
*/
|
||||
public function xmlSerializeFull(Writer $writer)
|
||||
{
|
||||
$cs = '{'.Plugin::NS_CALENDARSERVER.'}';
|
||||
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;
|
||||
}
|
||||
|
||||
$writer->startElement($cs.'systemstatus');
|
||||
$writer->writeAttribute('type', $type);
|
||||
|
||||
if ($this->description) {
|
||||
$writer->writeElement($cs.'description', $this->description);
|
||||
}
|
||||
if ($this->href) {
|
||||
$writer->writeElement('{DAV:}href', $this->href);
|
||||
}
|
||||
|
||||
$writer->endElement(); // systemstatus
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
81
vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
vendored
Normal file
81
vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AllowedSharingModes implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function __construct($canBeShared, $canBePublished)
|
||||
{
|
||||
$this->canBeShared = $canBeShared;
|
||||
$this->canBePublished = $canBePublished;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
if ($this->canBeShared) {
|
||||
$writer->writeElement('{'.Plugin::NS_CALENDARSERVER.'}can-be-shared');
|
||||
}
|
||||
if ($this->canBePublished) {
|
||||
$writer->writeElement('{'.Plugin::NS_CALENDARSERVER.'}can-be-published');
|
||||
}
|
||||
}
|
||||
}
|
||||
71
vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
vendored
Normal file
71
vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* email-address-set property.
|
||||
*
|
||||
* This property represents the email-address-set property in the
|
||||
* http://calendarserver.org/ns/ namespace.
|
||||
*
|
||||
* It's a list of email addresses associated with a user.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class EmailAddressSet implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* emails.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $emails;
|
||||
|
||||
/**
|
||||
* __construct.
|
||||
*/
|
||||
public function __construct(array $emails)
|
||||
{
|
||||
$this->emails = $emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email addresses.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
foreach ($this->emails as $email) {
|
||||
$writer->writeElement('{http://calendarserver.org/ns/}email-address', $email);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
vendored
Normal file
120
vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Xml\Element\Sharee;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Invite implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* The list of users a calendar has been shared to.
|
||||
*
|
||||
* @var Sharee[]
|
||||
*/
|
||||
protected $sharees;
|
||||
|
||||
/**
|
||||
* Creates the property.
|
||||
*
|
||||
* @param Sharee[] $sharees
|
||||
*/
|
||||
public function __construct(array $sharees)
|
||||
{
|
||||
$this->sharees = $sharees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of users, as it was passed to the constructor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->sharees;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
$cs = '{'.Plugin::NS_CALENDARSERVER.'}';
|
||||
|
||||
foreach ($this->sharees as $sharee) {
|
||||
if (DAV\Sharing\Plugin::ACCESS_SHAREDOWNER === $sharee->access) {
|
||||
$writer->startElement($cs.'organizer');
|
||||
} else {
|
||||
$writer->startElement($cs.'user');
|
||||
|
||||
switch ($sharee->inviteStatus) {
|
||||
case DAV\Sharing\Plugin::INVITE_ACCEPTED:
|
||||
$writer->writeElement($cs.'invite-accepted');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::INVITE_DECLINED:
|
||||
$writer->writeElement($cs.'invite-declined');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::INVITE_NORESPONSE:
|
||||
$writer->writeElement($cs.'invite-noresponse');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::INVITE_INVALID:
|
||||
$writer->writeElement($cs.'invite-invalid');
|
||||
break;
|
||||
}
|
||||
|
||||
$writer->startElement($cs.'access');
|
||||
switch ($sharee->access) {
|
||||
case DAV\Sharing\Plugin::ACCESS_READWRITE:
|
||||
$writer->writeElement($cs.'read-write');
|
||||
break;
|
||||
case DAV\Sharing\Plugin::ACCESS_READ:
|
||||
$writer->writeElement($cs.'read');
|
||||
break;
|
||||
}
|
||||
$writer->endElement(); // access
|
||||
}
|
||||
|
||||
$href = new DAV\Xml\Property\Href($sharee->href);
|
||||
$href->xmlSerialize($writer);
|
||||
|
||||
if (isset($sharee->properties['{DAV:}displayname'])) {
|
||||
$writer->writeElement($cs.'common-name', $sharee->properties['{DAV:}displayname']);
|
||||
}
|
||||
if ($sharee->comment) {
|
||||
$writer->writeElement($cs.'summary', $sharee->comment);
|
||||
}
|
||||
$writer->endElement(); // organizer or user
|
||||
}
|
||||
}
|
||||
}
|
||||
124
vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
vendored
Normal file
124
vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Deserializer;
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* schedule-calendar-transp property.
|
||||
*
|
||||
* This property is a representation of the schedule-calendar-transp property.
|
||||
* This property is defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6638#section-9.1
|
||||
*
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class ScheduleCalendarTransp implements Element
|
||||
{
|
||||
const TRANSPARENT = 'transparent';
|
||||
const OPAQUE = 'opaque';
|
||||
|
||||
/**
|
||||
* value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Creates the property.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if (self::TRANSPARENT !== $value && self::OPAQUE !== $value) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
switch ($this->value) {
|
||||
case self::TRANSPARENT:
|
||||
$writer->writeElement('{'.Plugin::NS_CALDAV.'}transparent');
|
||||
break;
|
||||
case self::OPAQUE:
|
||||
$writer->writeElement('{'.Plugin::NS_CALDAV.'}opaque');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = Deserializer\enum($reader, Plugin::NS_CALDAV);
|
||||
|
||||
if (in_array('transparent', $elems)) {
|
||||
$value = self::TRANSPARENT;
|
||||
} else {
|
||||
$value = self::OPAQUE;
|
||||
}
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
}
|
||||
118
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
vendored
Normal file
118
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\ParseException;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* SupportedCalendarComponentSet property.
|
||||
*
|
||||
* This class represents the
|
||||
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set property, as
|
||||
* defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-5.2.3
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SupportedCalendarComponentSet implements Element
|
||||
{
|
||||
/**
|
||||
* List of supported components.
|
||||
*
|
||||
* This array will contain values such as VEVENT, VTODO and VJOURNAL.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $components = [];
|
||||
|
||||
/**
|
||||
* Creates the property.
|
||||
*/
|
||||
public function __construct(array $components)
|
||||
{
|
||||
$this->components = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported components.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
foreach ($this->components as $component) {
|
||||
$writer->startElement('{'.Plugin::NS_CALDAV.'}comp');
|
||||
$writer->writeAttributes(['name' => $component]);
|
||||
$writer->endElement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
$components = [];
|
||||
|
||||
foreach ((array) $elems as $elem) {
|
||||
if ($elem['name'] === '{'.Plugin::NS_CALDAV.'}comp') {
|
||||
$components[] = $elem['attributes']['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$components) {
|
||||
throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element');
|
||||
}
|
||||
|
||||
return new self($components);
|
||||
}
|
||||
}
|
||||
57
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
vendored
Normal file
57
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This property is defined in:
|
||||
* http://tools.ietf.org/html/rfc4791#section-5.2.4
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SupportedCalendarData implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
$writer->startElement('{'.Plugin::NS_CALDAV.'}calendar-data');
|
||||
$writer->writeAttributes([
|
||||
'content-type' => 'text/calendar',
|
||||
'version' => '2.0',
|
||||
]);
|
||||
$writer->endElement(); // calendar-data
|
||||
$writer->startElement('{'.Plugin::NS_CALDAV.'}calendar-data');
|
||||
$writer->writeAttributes([
|
||||
'content-type' => 'application/calendar+json',
|
||||
]);
|
||||
$writer->endElement(); // calendar-data
|
||||
}
|
||||
}
|
||||
54
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
54
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Property;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* supported-collation-set property.
|
||||
*
|
||||
* This property is a representation of the supported-collation-set property
|
||||
* in the CalDAV namespace.
|
||||
*
|
||||
* This property is defined in:
|
||||
* http://tools.ietf.org/html/rfc4791#section-7.5.1
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SupportedCollationSet implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
$collations = [
|
||||
'i;ascii-casemap',
|
||||
'i;octet',
|
||||
'i;unicode-casemap',
|
||||
];
|
||||
|
||||
foreach ($collations as $collation) {
|
||||
$writer->writeElement('{'.Plugin::NS_CALDAV.'}supported-collation', $collation);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
vendored
Normal file
119
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\Uri;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* CalendarMultiGetReport request parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}calendar-multiget
|
||||
* REPORT, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-7.9
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarMultiGetReport implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* An array with requested properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties;
|
||||
|
||||
/**
|
||||
* This is an array with the urls that are being requested.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $hrefs;
|
||||
|
||||
/**
|
||||
* If the calendar data must be expanded, this will contain an array with 2
|
||||
* elements: start and end.
|
||||
*
|
||||
* Each may be a DateTime or null.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $expand = null;
|
||||
|
||||
/**
|
||||
* The mimetype of the content that should be returend. Usually
|
||||
* text/calendar.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $contentType = null;
|
||||
|
||||
/**
|
||||
* The version of calendar-data that should be returned. Usually '2.0',
|
||||
* referring to iCalendar 2.0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = null;
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = $reader->parseInnerTree([
|
||||
'{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
|
||||
'{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
]);
|
||||
|
||||
$newProps = [
|
||||
'hrefs' => [],
|
||||
'properties' => [],
|
||||
];
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{DAV:}prop':
|
||||
$newProps['properties'] = array_keys($elem['value']);
|
||||
if (isset($elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'])) {
|
||||
$newProps += $elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'];
|
||||
}
|
||||
break;
|
||||
case '{DAV:}href':
|
||||
$newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
foreach ($newProps as $key => $value) {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
137
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
vendored
Normal file
137
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* CalendarQueryReport request parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query
|
||||
* REPORT, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-7.9
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class CalendarQueryReport implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* An array with requested properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties;
|
||||
|
||||
/**
|
||||
* List of property/component filters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* If the calendar data must be expanded, this will contain an array with 2
|
||||
* elements: start and end.
|
||||
*
|
||||
* Each may be a DateTime or null.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $expand = null;
|
||||
|
||||
/**
|
||||
* The mimetype of the content that should be returend. Usually
|
||||
* text/calendar.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $contentType = null;
|
||||
|
||||
/**
|
||||
* The version of calendar-data that should be returned. Usually '2.0',
|
||||
* referring to iCalendar 2.0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = null;
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = $reader->parseInnerTree([
|
||||
'{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter',
|
||||
'{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter',
|
||||
'{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter',
|
||||
'{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
|
||||
'{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
]);
|
||||
|
||||
$newProps = [
|
||||
'filters' => null,
|
||||
'properties' => [],
|
||||
];
|
||||
|
||||
if (!is_array($elems)) {
|
||||
$elems = [];
|
||||
}
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{DAV:}prop':
|
||||
$newProps['properties'] = array_keys($elem['value']);
|
||||
if (isset($elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'])) {
|
||||
$newProps += $elem['value']['{'.Plugin::NS_CALDAV.'}calendar-data'];
|
||||
}
|
||||
break;
|
||||
case '{'.Plugin::NS_CALDAV.'}filter':
|
||||
foreach ($elem['value'] as $subElem) {
|
||||
if ($subElem['name'] === '{'.Plugin::NS_CALDAV.'}comp-filter') {
|
||||
if (!is_null($newProps['filters'])) {
|
||||
throw new BadRequest('Only one top-level comp-filter may be defined');
|
||||
}
|
||||
$newProps['filters'] = $subElem['value'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($newProps['filters'])) {
|
||||
throw new BadRequest('The {'.Plugin::NS_CALDAV.'}filter element is required for this request');
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
foreach ($newProps as $key => $value) {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
90
vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
vendored
Normal file
90
vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\VObject\DateTimeParser;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* FreeBusyQueryReport.
|
||||
*
|
||||
* This class parses the {DAV:}free-busy-query REPORT, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc3253#section-3.8
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class FreeBusyQueryReport implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* Starttime of report.
|
||||
*
|
||||
* @var \DateTime|null
|
||||
*/
|
||||
public $start;
|
||||
|
||||
/**
|
||||
* End time of report.
|
||||
*
|
||||
* @var \DateTime|null
|
||||
*/
|
||||
public $end;
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$timeRange = '{'.Plugin::NS_CALDAV.'}time-range';
|
||||
|
||||
$start = null;
|
||||
$end = null;
|
||||
|
||||
foreach ((array) $reader->parseInnerTree([]) as $elem) {
|
||||
if ($elem['name'] !== $timeRange) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = empty($elem['attributes']['start']) ?: $elem['attributes']['start'];
|
||||
$end = empty($elem['attributes']['end']) ?: $elem['attributes']['end'];
|
||||
}
|
||||
if (!$start && !$end) {
|
||||
throw new BadRequest('The freebusy report must have a time-range element');
|
||||
}
|
||||
if ($start) {
|
||||
$start = DateTimeParser::parseDateTime($start);
|
||||
}
|
||||
if ($end) {
|
||||
$end = DateTimeParser::parseDateTime($end);
|
||||
}
|
||||
$result = new self();
|
||||
$result->start = $start;
|
||||
$result->end = $end;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
145
vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
vendored
Normal file
145
vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\CalDAV\SharingPlugin;
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Element\KeyValue;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* Invite-reply POST request parser.
|
||||
*
|
||||
* This class parses the invite-reply POST request, as defined in:
|
||||
*
|
||||
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class InviteReply implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The sharee calendar user address.
|
||||
*
|
||||
* This is the address that the original invite was set to
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $href;
|
||||
|
||||
/**
|
||||
* The uri to the calendar that was being shared.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $calendarUri;
|
||||
|
||||
/**
|
||||
* The id of the invite message that's being responded to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $inReplyTo;
|
||||
|
||||
/**
|
||||
* An optional message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $summary;
|
||||
|
||||
/**
|
||||
* Either SharingPlugin::STATUS_ACCEPTED or SharingPlugin::STATUS_DECLINED.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $href
|
||||
* @param string $calendarUri
|
||||
* @param string $inReplyTo
|
||||
* @param string $summary
|
||||
* @param int $status
|
||||
*/
|
||||
public function __construct($href, $calendarUri, $inReplyTo, $summary, $status)
|
||||
{
|
||||
$this->href = $href;
|
||||
$this->calendarUri = $calendarUri;
|
||||
$this->inReplyTo = $inReplyTo;
|
||||
$this->summary = $summary;
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = KeyValue::xmlDeserialize($reader);
|
||||
|
||||
$href = null;
|
||||
$calendarUri = null;
|
||||
$inReplyTo = null;
|
||||
$summary = null;
|
||||
$status = null;
|
||||
|
||||
foreach ($elems as $name => $value) {
|
||||
switch ($name) {
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}hosturl':
|
||||
foreach ($value as $bla) {
|
||||
if ('{DAV:}href' === $bla['name']) {
|
||||
$calendarUri = $bla['value'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}invite-accepted':
|
||||
$status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}invite-declined':
|
||||
$status = DAV\Sharing\Plugin::INVITE_DECLINED;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}in-reply-to':
|
||||
$inReplyTo = $value;
|
||||
break;
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}summary':
|
||||
$summary = $value;
|
||||
break;
|
||||
case '{DAV:}href':
|
||||
$href = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($calendarUri)) {
|
||||
throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist');
|
||||
}
|
||||
|
||||
return new self($href, $calendarUri, $inReplyTo, $summary, $status);
|
||||
}
|
||||
}
|
||||
77
vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
vendored
Normal file
77
vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* MKCALENDAR parser.
|
||||
*
|
||||
* This class parses the MKCALENDAR request, as defined in:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc4791#section-5.3.1
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class MkCalendar implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The list of properties that will be set.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties = [];
|
||||
|
||||
/**
|
||||
* Returns the list of properties the calendar will be initialized with.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$self = new self();
|
||||
|
||||
$elementMap = $reader->elementMap;
|
||||
$elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
|
||||
$elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
|
||||
$elems = $reader->parseInnerTree($elementMap);
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
if ('{DAV:}set' === $elem['name']) {
|
||||
$self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
|
||||
}
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
107
vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
vendored
Normal file
107
vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CalDAV\Xml\Request;
|
||||
|
||||
use Sabre\CalDAV\Plugin;
|
||||
use Sabre\DAV\Xml\Element\Sharee;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* Share POST request parser.
|
||||
*
|
||||
* This class parses the share POST request, as defined in:
|
||||
*
|
||||
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Share implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The list of new people added or updated or removed from the share.
|
||||
*
|
||||
* @var Sharee[]
|
||||
*/
|
||||
public $sharees = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Sharee[] $sharees
|
||||
*/
|
||||
public function __construct(array $sharees)
|
||||
{
|
||||
$this->sharees = $sharees;
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = $reader->parseGetElements([
|
||||
'{'.Plugin::NS_CALENDARSERVER.'}set' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
'{'.Plugin::NS_CALENDARSERVER.'}remove' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
]);
|
||||
|
||||
$sharees = [];
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}set':
|
||||
$sharee = $elem['value'];
|
||||
|
||||
$sumElem = '{'.Plugin::NS_CALENDARSERVER.'}summary';
|
||||
$commonName = '{'.Plugin::NS_CALENDARSERVER.'}common-name';
|
||||
|
||||
$properties = [];
|
||||
if (isset($sharee[$commonName])) {
|
||||
$properties['{DAV:}displayname'] = $sharee[$commonName];
|
||||
}
|
||||
|
||||
$access = array_key_exists('{'.Plugin::NS_CALENDARSERVER.'}read-write', $sharee)
|
||||
? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE
|
||||
: \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
|
||||
|
||||
$sharees[] = new Sharee([
|
||||
'href' => $sharee['{DAV:}href'],
|
||||
'properties' => $properties,
|
||||
'access' => $access,
|
||||
'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '{'.Plugin::NS_CALENDARSERVER.'}remove':
|
||||
$sharees[] = new Sharee([
|
||||
'href' => $elem['value']['{DAV:}href'],
|
||||
'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new self($sharees);
|
||||
}
|
||||
}
|
||||
335
vendor/sabre/dav/lib/CardDAV/AddressBook.php
vendored
Normal file
335
vendor/sabre/dav/lib/CardDAV/AddressBook.php
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* This is an array with addressbook information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $addressBookInfo;
|
||||
|
||||
/**
|
||||
* CardDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
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 Card
|
||||
*/
|
||||
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 = [];
|
||||
foreach ($objs as $obj) {
|
||||
$obj['acl'] = $this->getChildACL();
|
||||
$children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method receives a list of paths in it's first argument.
|
||||
* It must return an array with Node objects.
|
||||
*
|
||||
* If any children are not found, you do not have to return them.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleChildren(array $paths)
|
||||
{
|
||||
$objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
|
||||
$children = [];
|
||||
foreach ($objs as $obj) {
|
||||
$obj['acl'] = $this->getChildACL();
|
||||
$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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the addressbook.
|
||||
*
|
||||
* @param string $newName
|
||||
*/
|
||||
public function setName($newName)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp.
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties on this node.
|
||||
*
|
||||
* This method received a PropPatch object, which contains all the
|
||||
* information about the update.
|
||||
*
|
||||
* To update specific properties, call the 'handle' method on this object.
|
||||
* Read the PropPatch documentation for more information.
|
||||
*/
|
||||
public function propPatch(DAV\PropPatch $propPatch)
|
||||
{
|
||||
return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ACL's for card nodes in this address book.
|
||||
* The result of this method automatically gets passed to the
|
||||
* card nodes in this address book.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildACL()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the current sync-token for this collection.
|
||||
* This can be any string.
|
||||
*
|
||||
* If null is returned from this function, the plugin assumes there's no
|
||||
* sync information available.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSyncToken()
|
||||
{
|
||||
if (
|
||||
$this->carddavBackend instanceof Backend\SyncSupport &&
|
||||
isset($this->addressBookInfo['{DAV:}sync-token'])
|
||||
) {
|
||||
return $this->addressBookInfo['{DAV:}sync-token'];
|
||||
}
|
||||
if (
|
||||
$this->carddavBackend instanceof Backend\SyncSupport &&
|
||||
isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
|
||||
) {
|
||||
return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken and the current collection.
|
||||
*
|
||||
* This function should return an array, such as the following:
|
||||
*
|
||||
* [
|
||||
* 'syncToken' => 'The current synctoken',
|
||||
* 'added' => [
|
||||
* 'new.txt',
|
||||
* ],
|
||||
* 'modified' => [
|
||||
* 'modified.txt',
|
||||
* ],
|
||||
* 'deleted' => [
|
||||
* 'foo.php.bak',
|
||||
* 'old.txt'
|
||||
* ]
|
||||
* ];
|
||||
*
|
||||
* The syncToken property should reflect the *current* syncToken of the
|
||||
* collection, as reported getSyncToken(). This is needed here too, to
|
||||
* ensure the operation is atomic.
|
||||
*
|
||||
* If the syncToken is specified as null, this is an initial sync, and all
|
||||
* members should be reported.
|
||||
*
|
||||
* The modified property is an array of nodenames that have changed since
|
||||
* the last token.
|
||||
*
|
||||
* The deleted property is an array with nodenames, that have been deleted
|
||||
* from collection.
|
||||
*
|
||||
* The second argument is basically the 'depth' of the report. If it's 1,
|
||||
* you only have to report changes that happened only directly in immediate
|
||||
* descendants. If it's 2, it should also include changes from the nodes
|
||||
* below the child collections. (grandchildren)
|
||||
*
|
||||
* The third (optional) argument allows a client to specify how many
|
||||
* results should be returned at most. If the limit is not specified, it
|
||||
* should be treated as infinite.
|
||||
*
|
||||
* If the limit (infinite or not) is higher than you're willing to return,
|
||||
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
|
||||
*
|
||||
* If the syncToken is expired (due to data cleanup) or unknown, you must
|
||||
* return null.
|
||||
*
|
||||
* The limit is 'suggestive'. You are free to ignore it.
|
||||
*
|
||||
* @param string $syncToken
|
||||
* @param int $syncLevel
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null)
|
||||
{
|
||||
if (!$this->carddavBackend instanceof Backend\SyncSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->carddavBackend->getChangesForAddressBook(
|
||||
$this->addressBookInfo['id'],
|
||||
$syncToken,
|
||||
$syncLevel,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
}
|
||||
178
vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
vendored
Normal file
178
vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\MkCol;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\Uri;
|
||||
|
||||
/**
|
||||
* AddressBook Home class.
|
||||
*
|
||||
* This collection contains a list of addressbooks associated with one user.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* Principal uri.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* carddavBackend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @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) = Uri\split($this->principalUri);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the name of this object.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this object.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function createDirectory($filename)
|
||||
{
|
||||
throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single addressbook, 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 = [];
|
||||
foreach ($addressbooks as $addressbook) {
|
||||
$objs[] = new AddressBook($this->carddavBackend, $addressbook);
|
||||
}
|
||||
|
||||
return $objs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new address book.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @throws DAV\Exception\InvalidResourceType
|
||||
*/
|
||||
public function createExtendedCollection($name, MkCol $mkCol)
|
||||
{
|
||||
if (!$mkCol->hasResourceType('{'.Plugin::NS_CARDDAV.'}addressbook')) {
|
||||
throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
|
||||
}
|
||||
$properties = $mkCol->getRemainingValues();
|
||||
$mkCol->setRemainingResultCode(201);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
75
vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
vendored
Normal file
75
vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* AddressBook rootnode.
|
||||
*
|
||||
* This object lists a collection of users, which can contain addressbooks.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressBookRoot extends DAVACL\AbstractPrincipalCollection
|
||||
{
|
||||
/**
|
||||
* Principal Backend.
|
||||
*
|
||||
* @var DAVACL\PrincipalBackend\BackendInterface
|
||||
*/
|
||||
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 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.
|
||||
*
|
||||
* @return \Sabre\DAV\INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal)
|
||||
{
|
||||
return new AddressBookHome($this->carddavBackend, $principal['uri']);
|
||||
}
|
||||
}
|
||||
38
vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
vendored
Normal file
38
vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class AbstractBackend implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* Returns a list of cards.
|
||||
*
|
||||
* This method should work identical to getCard, but instead return all the
|
||||
* cards in the list as an array.
|
||||
*
|
||||
* If the backend supports this, it may allow for some speed-ups.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCards($addressBookId, array $uris)
|
||||
{
|
||||
return array_map(function ($uri) use ($addressBookId) {
|
||||
return $this->getCard($addressBookId, $uri);
|
||||
}, $uris);
|
||||
}
|
||||
}
|
||||
194
vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
vendored
Normal file
194
vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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 properties for an address book.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
*/
|
||||
public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
|
||||
|
||||
/**
|
||||
* Creates a new address book.
|
||||
*
|
||||
* This method should return the id of the new address book. The id can be
|
||||
* in any format, including ints, strings, arrays or objects.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url just the 'basename' of the url
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createAddressBook($principalUri, $url, array $properties);
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* If the card does not exist, you must return false.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCard($addressBookId, $cardUri);
|
||||
|
||||
/**
|
||||
* Returns a list of cards.
|
||||
*
|
||||
* This method should work identical to getCard, but instead return all the
|
||||
* cards in the list as an array.
|
||||
*
|
||||
* If the backend supports this, it may allow for some speed-ups.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCards($addressBookId, array $uris);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
538
vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
vendored
Normal file
538
vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
vendored
Normal file
@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Backend;
|
||||
|
||||
use Sabre\CardDAV;
|
||||
use Sabre\DAV;
|
||||
|
||||
/**
|
||||
* PDO CardDAV backend.
|
||||
*
|
||||
* This CardDAV backend uses PDO to store addressbooks
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class PDO extends AbstractBackend implements SyncSupport
|
||||
{
|
||||
/**
|
||||
* PDO connection.
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* The PDO table name used to store addressbooks.
|
||||
*/
|
||||
public $addressBooksTableName = 'addressbooks';
|
||||
|
||||
/**
|
||||
* The PDO table name used to store cards.
|
||||
*/
|
||||
public $cardsTableName = 'cards';
|
||||
|
||||
/**
|
||||
* The table name that will be used for tracking changes in address books.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $addressBookChangesTableName = 'addressbookchanges';
|
||||
|
||||
/**
|
||||
* Sets up the object.
|
||||
*/
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, synctoken FROM '.$this->addressBooksTableName.' WHERE principaluri = ?');
|
||||
$stmt->execute([$principalUri]);
|
||||
|
||||
$addressBooks = [];
|
||||
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$addressBooks[] = [
|
||||
'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['synctoken'],
|
||||
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
|
||||
];
|
||||
}
|
||||
|
||||
return $addressBooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties for an address book.
|
||||
*
|
||||
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
|
||||
* To do the actual updates, you must tell this object which properties
|
||||
* you're going to process with the handle() method.
|
||||
*
|
||||
* Calling the handle method is like telling the PropPatch object "I
|
||||
* promise I can handle updating this property".
|
||||
*
|
||||
* Read the PropPatch documentation for more info and examples.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
*/
|
||||
public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch)
|
||||
{
|
||||
$supportedProperties = [
|
||||
'{DAV:}displayname',
|
||||
'{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description',
|
||||
];
|
||||
|
||||
$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
|
||||
$updates = [];
|
||||
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;
|
||||
}
|
||||
}
|
||||
$query = 'UPDATE '.$this->addressBooksTableName.' SET ';
|
||||
$first = true;
|
||||
foreach ($updates as $key => $value) {
|
||||
if ($first) {
|
||||
$first = false;
|
||||
} else {
|
||||
$query .= ', ';
|
||||
}
|
||||
$query .= ' '.$key.' = :'.$key.' ';
|
||||
}
|
||||
$query .= ' WHERE id = :addressbookid';
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$updates['addressbookid'] = $addressBookId;
|
||||
|
||||
$stmt->execute($updates);
|
||||
|
||||
$this->addChange($addressBookId, '', 2);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new address book.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url just the 'basename' of the url
|
||||
*
|
||||
* @return int Last insert id
|
||||
*/
|
||||
public function createAddressBook($principalUri, $url, array $properties)
|
||||
{
|
||||
$values = [
|
||||
'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, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute($values);
|
||||
|
||||
return $this->pdo->lastInsertId(
|
||||
$this->addressBooksTableName.'_id_seq'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents.
|
||||
*
|
||||
* @param int $addressBookId
|
||||
*/
|
||||
public function deleteAddressBook($addressBookId)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->cardsTableName.' WHERE addressbookid = ?');
|
||||
$stmt->execute([$addressBookId]);
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBooksTableName.' WHERE id = ?');
|
||||
$stmt->execute([$addressBookId]);
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBookChangesTableName.' WHERE addressbookid = ?');
|
||||
$stmt->execute([$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, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ?');
|
||||
$stmt->execute([$addressbookId]);
|
||||
|
||||
$result = [];
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$row['etag'] = '"'.$row['etag'].'"';
|
||||
$row['lastmodified'] = (int) $row['lastmodified'];
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific card.
|
||||
*
|
||||
* The same set of properties must be returned as with getCards. The only
|
||||
* exception is that 'carddata' is absolutely required.
|
||||
*
|
||||
* If the card does not exist, you must return false.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCard($addressBookId, $cardUri)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri = ? LIMIT 1');
|
||||
$stmt->execute([$addressBookId, $cardUri]);
|
||||
|
||||
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result['etag'] = '"'.$result['etag'].'"';
|
||||
$result['lastmodified'] = (int) $result['lastmodified'];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of cards.
|
||||
*
|
||||
* This method should work identical to getCard, but instead return all the
|
||||
* cards in the list as an array.
|
||||
*
|
||||
* If the backend supports this, it may allow for some speed-ups.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMultipleCards($addressBookId, array $uris)
|
||||
{
|
||||
$query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri IN (';
|
||||
// Inserting a whole bunch of question marks
|
||||
$query .= implode(',', array_fill(0, count($uris), '?'));
|
||||
$query .= ')';
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute(array_merge([$addressBookId], $uris));
|
||||
$result = [];
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$row['etag'] = '"'.$row['etag'].'"';
|
||||
$row['lastmodified'] = (int) $row['lastmodified'];
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
|
||||
$etag = md5($cardData);
|
||||
|
||||
$stmt->execute([
|
||||
$cardData,
|
||||
$cardUri,
|
||||
time(),
|
||||
$addressBookId,
|
||||
strlen($cardData),
|
||||
$etag,
|
||||
]);
|
||||
|
||||
$this->addChange($addressBookId, $cardUri, 1);
|
||||
|
||||
return '"'.$etag.'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?');
|
||||
|
||||
$etag = md5($cardData);
|
||||
$stmt->execute([
|
||||
$cardData,
|
||||
time(),
|
||||
strlen($cardData),
|
||||
$etag,
|
||||
$cardUri,
|
||||
$addressBookId,
|
||||
]);
|
||||
|
||||
$this->addChange($addressBookId, $cardUri, 2);
|
||||
|
||||
return '"'.$etag.'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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([$addressBookId, $cardUri]);
|
||||
|
||||
$this->addChange($addressBookId, $cardUri, 3);
|
||||
|
||||
return 1 === $stmt->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken in the specified address book.
|
||||
*
|
||||
* This function should return an array, such as the following:
|
||||
*
|
||||
* [
|
||||
* 'syncToken' => 'The current synctoken',
|
||||
* 'added' => [
|
||||
* 'new.txt',
|
||||
* ],
|
||||
* 'modified' => [
|
||||
* 'updated.txt',
|
||||
* ],
|
||||
* 'deleted' => [
|
||||
* 'foo.php.bak',
|
||||
* 'old.txt'
|
||||
* ]
|
||||
* ];
|
||||
*
|
||||
* The returned syncToken property should reflect the *current* syncToken
|
||||
* of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
|
||||
* property. This is needed here too, to ensure the operation is atomic.
|
||||
*
|
||||
* If the $syncToken argument is specified as null, this is an initial
|
||||
* sync, and all members should be reported.
|
||||
*
|
||||
* The modified property is an array of nodenames that have changed since
|
||||
* the last token.
|
||||
*
|
||||
* The deleted property is an array with nodenames, that have been deleted
|
||||
* from collection.
|
||||
*
|
||||
* The $syncLevel argument is basically the 'depth' of the report. If it's
|
||||
* 1, you only have to report changes that happened only directly in
|
||||
* immediate descendants. If it's 2, it should also include changes from
|
||||
* the nodes below the child collections. (grandchildren)
|
||||
*
|
||||
* The $limit argument allows a client to specify how many results should
|
||||
* be returned at most. If the limit is not specified, it should be treated
|
||||
* as infinite.
|
||||
*
|
||||
* If the limit (infinite or not) is higher than you're willing to return,
|
||||
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
|
||||
*
|
||||
* If the syncToken is expired (due to data cleanup) or unknown, you must
|
||||
* return null.
|
||||
*
|
||||
* The limit is 'suggestive'. You are free to ignore it.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
* @param string $syncToken
|
||||
* @param int $syncLevel
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null)
|
||||
{
|
||||
// Current synctoken
|
||||
$stmt = $this->pdo->prepare('SELECT synctoken FROM '.$this->addressBooksTableName.' WHERE id = ?');
|
||||
$stmt->execute([$addressBookId]);
|
||||
$currentToken = $stmt->fetchColumn(0);
|
||||
|
||||
if (is_null($currentToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'syncToken' => $currentToken,
|
||||
'added' => [],
|
||||
'modified' => [],
|
||||
'deleted' => [],
|
||||
];
|
||||
|
||||
if ($syncToken) {
|
||||
$query = 'SELECT uri, operation FROM '.$this->addressBookChangesTableName.' WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken';
|
||||
if ($limit > 0) {
|
||||
$query .= ' LIMIT '.(int) $limit;
|
||||
}
|
||||
|
||||
// Fetching all changes
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute([$syncToken, $currentToken, $addressBookId]);
|
||||
|
||||
$changes = [];
|
||||
|
||||
// This loop ensures that any duplicates are overwritten, only the
|
||||
// last change on a node is relevant.
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$changes[$row['uri']] = $row['operation'];
|
||||
}
|
||||
|
||||
foreach ($changes as $uri => $operation) {
|
||||
switch ($operation) {
|
||||
case 1:
|
||||
$result['added'][] = $uri;
|
||||
break;
|
||||
case 2:
|
||||
$result['modified'][] = $uri;
|
||||
break;
|
||||
case 3:
|
||||
$result['deleted'][] = $uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No synctoken supplied, this is the initial sync.
|
||||
$query = 'SELECT uri FROM '.$this->cardsTableName.' WHERE addressbookid = ?';
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute([$addressBookId]);
|
||||
|
||||
$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a change record to the addressbookchanges table.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $objectUri
|
||||
* @param int $operation 1 = add, 2 = modify, 3 = delete
|
||||
*/
|
||||
protected function addChange($addressBookId, $objectUri, $operation)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('INSERT INTO '.$this->addressBookChangesTableName.' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM '.$this->addressBooksTableName.' WHERE id = ?');
|
||||
$stmt->execute([
|
||||
$objectUri,
|
||||
$addressBookId,
|
||||
$operation,
|
||||
$addressBookId,
|
||||
]);
|
||||
$stmt = $this->pdo->prepare('UPDATE '.$this->addressBooksTableName.' SET synctoken = synctoken + 1 WHERE id = ?');
|
||||
$stmt->execute([
|
||||
$addressBookId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
83
vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
vendored
Normal file
83
vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Backend;
|
||||
|
||||
/**
|
||||
* WebDAV-sync support for CardDAV backends.
|
||||
*
|
||||
* In order for backends to advertise support for WebDAV-sync, this interface
|
||||
* must be implemented.
|
||||
*
|
||||
* Implementing this can result in a significant reduction of bandwidth and CPU
|
||||
* time.
|
||||
*
|
||||
* For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
|
||||
* property from getAddressBooksForUser.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface SyncSupport extends BackendInterface
|
||||
{
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken in the specified address book.
|
||||
*
|
||||
* This function should return an array, such as the following:
|
||||
*
|
||||
* [
|
||||
* 'syncToken' => 'The current synctoken',
|
||||
* 'added' => [
|
||||
* 'new.txt',
|
||||
* ],
|
||||
* 'modified' => [
|
||||
* 'modified.txt',
|
||||
* ],
|
||||
* 'deleted' => [
|
||||
* 'foo.php.bak',
|
||||
* 'old.txt'
|
||||
* ]
|
||||
* ];
|
||||
*
|
||||
* The returned syncToken property should reflect the *current* syncToken
|
||||
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
|
||||
* property. This is needed here too, to ensure the operation is atomic.
|
||||
*
|
||||
* If the $syncToken argument is specified as null, this is an initial
|
||||
* sync, and all members should be reported.
|
||||
*
|
||||
* The modified property is an array of nodenames that have changed since
|
||||
* the last token.
|
||||
*
|
||||
* The deleted property is an array with nodenames, that have been deleted
|
||||
* from collection.
|
||||
*
|
||||
* The $syncLevel argument is basically the 'depth' of the report. If it's
|
||||
* 1, you only have to report changes that happened only directly in
|
||||
* immediate descendants. If it's 2, it should also include changes from
|
||||
* the nodes below the child collections. (grandchildren)
|
||||
*
|
||||
* The $limit argument allows a client to specify how many results should
|
||||
* be returned at most. If the limit is not specified, it should be treated
|
||||
* as infinite.
|
||||
*
|
||||
* If the limit (infinite or not) is higher than you're willing to return,
|
||||
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
|
||||
*
|
||||
* If the syncToken is expired (due to data cleanup) or unknown, you must
|
||||
* return null.
|
||||
*
|
||||
* The limit is 'suggestive'. You are free to ignore it.
|
||||
*
|
||||
* @param string $addressBookId
|
||||
* @param string $syncToken
|
||||
* @param int $syncLevel
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);
|
||||
}
|
||||
202
vendor/sabre/dav/lib/CardDAV/Card.php
vendored
Normal file
202
vendor/sabre/dav/lib/CardDAV/Card.php
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAVACL;
|
||||
|
||||
/**
|
||||
* The Card object represents a single Card from an addressbook.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Card extends DAV\File implements ICard, DAVACL\IACL
|
||||
{
|
||||
use DAVACL\ACLTrait;
|
||||
|
||||
/**
|
||||
* CardDAV backend.
|
||||
*
|
||||
* @var Backend\BackendInterface
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Array with information about this Card.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cardData;
|
||||
|
||||
/**
|
||||
* Array with information about the containing addressbook.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $addressBookInfo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime content-type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return 'text/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 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 through the cardData array.
|
||||
if (isset($this->cardData['acl'])) {
|
||||
return $this->cardData['acl'];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
20
vendor/sabre/dav/lib/CardDAV/IAddressBook.php
vendored
Normal file
20
vendor/sabre/dav/lib/CardDAV/IAddressBook.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
|
||||
/**
|
||||
* AddressBook interface.
|
||||
*
|
||||
* Implement this interface to allow a node to be recognized as an addressbook.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IAddressBook extends DAV\ICollection
|
||||
{
|
||||
}
|
||||
21
vendor/sabre/dav/lib/CardDAV/ICard.php
vendored
Normal file
21
vendor/sabre/dav/lib/CardDAV/ICard.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface ICard extends DAV\IFile
|
||||
{
|
||||
}
|
||||
22
vendor/sabre/dav/lib/CardDAV/IDirectory.php
vendored
Normal file
22
vendor/sabre/dav/lib/CardDAV/IDirectory.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface IDirectory extends IAddressBook
|
||||
{
|
||||
}
|
||||
879
vendor/sabre/dav/lib/CardDAV/Plugin.php
vendored
Normal file
879
vendor/sabre/dav/lib/CardDAV/Plugin.php
vendored
Normal file
@ -0,0 +1,879 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Exception\ReportNotSupported;
|
||||
use Sabre\DAV\Xml\Property\LocalHref;
|
||||
use Sabre\DAVACL;
|
||||
use Sabre\HTTP;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Sabre\Uri;
|
||||
use Sabre\VObject;
|
||||
|
||||
/**
|
||||
* CardDAV plugin.
|
||||
*
|
||||
* The CardDAV plugin adds CardDAV functionality to the WebDAV server
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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 = [];
|
||||
|
||||
/**
|
||||
* Server class.
|
||||
*
|
||||
* @var DAV\Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
|
||||
* which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
|
||||
* capping it to 10M here.
|
||||
*/
|
||||
protected $maxResourceSize = 10000000;
|
||||
|
||||
/**
|
||||
* Initializes the plugin.
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
/* Events */
|
||||
$server->on('propFind', [$this, 'propFindEarly']);
|
||||
$server->on('propFind', [$this, 'propFindLate'], 150);
|
||||
$server->on('report', [$this, 'report']);
|
||||
$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
|
||||
$server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
|
||||
$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
|
||||
$server->on('afterMethod:GET', [$this, 'httpAfterGet']);
|
||||
|
||||
$server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
|
||||
|
||||
$server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
|
||||
$server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
|
||||
|
||||
/* 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->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\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 ['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 [
|
||||
'{'.self::NS_CARDDAV.'}addressbook-multiget',
|
||||
'{'.self::NS_CARDDAV.'}addressbook-query',
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all CardDAV-specific properties.
|
||||
*/
|
||||
public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
|
||||
{
|
||||
$ns = '{'.self::NS_CARDDAV.'}';
|
||||
|
||||
if ($node instanceof IAddressBook) {
|
||||
$propFind->handle($ns.'max-resource-size', $this->maxResourceSize);
|
||||
$propFind->handle($ns.'supported-address-data', function () {
|
||||
return new Xml\Property\SupportedAddressData();
|
||||
});
|
||||
$propFind->handle($ns.'supported-collation-set', function () {
|
||||
return new Xml\Property\SupportedCollationSet();
|
||||
});
|
||||
}
|
||||
if ($node instanceof DAVACL\IPrincipal) {
|
||||
$path = $propFind->getPath();
|
||||
|
||||
$propFind->handle('{'.self::NS_CARDDAV.'}addressbook-home-set', function () use ($path) {
|
||||
return new LocalHref($this->getAddressBookHomeForPrincipal($path).'/');
|
||||
});
|
||||
|
||||
if ($this->directories) {
|
||||
$propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () {
|
||||
return new LocalHref($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.
|
||||
$propFind->handle('{'.self::NS_CARDDAV.'}address-data', function () use ($node) {
|
||||
$val = $node->get();
|
||||
if (is_resource($val)) {
|
||||
$val = stream_get_contents($val);
|
||||
}
|
||||
|
||||
return $val;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions handles REPORT requests specific to CardDAV.
|
||||
*
|
||||
* @param string $reportName
|
||||
* @param \DOMNode $dom
|
||||
* @param mixed $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function report($reportName, $dom, $path)
|
||||
{
|
||||
switch ($reportName) {
|
||||
case '{'.self::NS_CARDDAV.'}addressbook-multiget':
|
||||
$this->server->transactionType = 'report-addressbook-multiget';
|
||||
$this->addressbookMultiGetReport($dom);
|
||||
|
||||
return false;
|
||||
case '{'.self::NS_CARDDAV.'}addressbook-query':
|
||||
$this->server->transactionType = 'report-addressbook-query';
|
||||
$this->addressBookQueryReport($dom);
|
||||
|
||||
return false;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the addressbook home for a given principal.
|
||||
*
|
||||
* @param string $principal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAddressbookHomeForPrincipal($principal)
|
||||
{
|
||||
list(, $principalId) = Uri\split($principal);
|
||||
|
||||
return self::ADDRESSBOOK_ROOT.'/'.$principalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Xml\Request\AddressBookMultiGetReport $report
|
||||
*/
|
||||
public function addressbookMultiGetReport($report)
|
||||
{
|
||||
$contentType = $report->contentType;
|
||||
$version = $report->version;
|
||||
if ($version) {
|
||||
$contentType .= '; version='.$version;
|
||||
}
|
||||
|
||||
$vcardType = $this->negotiateVCard(
|
||||
$contentType
|
||||
);
|
||||
|
||||
$propertyList = [];
|
||||
$paths = array_map(
|
||||
[$this->server, 'calculateUri'],
|
||||
$report->hrefs
|
||||
);
|
||||
foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
|
||||
if (isset($props['200']['{'.self::NS_CARDDAV.'}address-data'])) {
|
||||
$props['200']['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
|
||||
$props[200]['{'.self::NS_CARDDAV.'}address-data'],
|
||||
$vcardType
|
||||
);
|
||||
}
|
||||
$propertyList[] = $props;
|
||||
}
|
||||
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
|
||||
$this->server->httpResponse->setStatus(207);
|
||||
$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
|
||||
$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
|
||||
$this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return']));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 resource $data
|
||||
* @param bool $modified should be set to true, if this event handler
|
||||
* changed &$data
|
||||
*/
|
||||
public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
|
||||
{
|
||||
if (!$node instanceof ICard) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validateVCard($data, $modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bool $modified should be set to true, if this event handler
|
||||
* changed &$data
|
||||
*/
|
||||
public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
|
||||
{
|
||||
if (!$parentNode instanceof IAddressBook) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validateVCard($data, $modified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the submitted iCalendar data is in fact, valid.
|
||||
*
|
||||
* An exception is thrown if it's not.
|
||||
*
|
||||
* @param resource|string $data
|
||||
* @param bool $modified should be set to true, if this event handler
|
||||
* changed &$data
|
||||
*/
|
||||
protected function validateVCard(&$data, &$modified)
|
||||
{
|
||||
// If it's a stream, we convert it to a string first.
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
|
||||
$before = $data;
|
||||
|
||||
try {
|
||||
// If the data starts with a [, we can reasonably assume we're dealing
|
||||
// with a jCal object.
|
||||
if ('[' === substr($data, 0, 1)) {
|
||||
$vobj = VObject\Reader::readJson($data);
|
||||
|
||||
// Converting $data back to iCalendar, as that's what we
|
||||
// technically support everywhere.
|
||||
$data = $vobj->serialize();
|
||||
$modified = true;
|
||||
} else {
|
||||
$vobj = VObject\Reader::read($data);
|
||||
}
|
||||
} catch (VObject\ParseException $e) {
|
||||
throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: '.$e->getMessage());
|
||||
}
|
||||
|
||||
if ('VCARD' !== $vobj->name) {
|
||||
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
|
||||
}
|
||||
|
||||
$options = VObject\Node::PROFILE_CARDDAV;
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
|
||||
if ('strict' !== $prefer['handling']) {
|
||||
$options |= VObject\Node::REPAIR;
|
||||
}
|
||||
|
||||
$messages = $vobj->validate($options);
|
||||
|
||||
$highestLevel = 0;
|
||||
$warningMessage = null;
|
||||
|
||||
// $messages contains a list of problems with the vcard, along with
|
||||
// their severity.
|
||||
foreach ($messages as $message) {
|
||||
if ($message['level'] > $highestLevel) {
|
||||
// Recording the highest reported error level.
|
||||
$highestLevel = $message['level'];
|
||||
$warningMessage = $message['message'];
|
||||
}
|
||||
|
||||
switch ($message['level']) {
|
||||
case 1:
|
||||
// Level 1 means that there was a problem, but it was repaired.
|
||||
$modified = true;
|
||||
break;
|
||||
case 2:
|
||||
// Level 2 means a warning, but not critical
|
||||
break;
|
||||
case 3:
|
||||
// Level 3 means a critical error
|
||||
throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']);
|
||||
}
|
||||
}
|
||||
if ($warningMessage) {
|
||||
$this->server->httpResponse->setHeader(
|
||||
'X-Sabre-Ew-Gross',
|
||||
'vCard validation warning: '.$warningMessage
|
||||
);
|
||||
|
||||
// Re-serializing object.
|
||||
$data = $vobj->serialize();
|
||||
if (!$modified && 0 !== strcmp($data, $before)) {
|
||||
// This ensures that the system does not send an ETag back.
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vobj->destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles the addressbook-query REPORT.
|
||||
*
|
||||
* This report is used by the client to filter an addressbook based on a
|
||||
* complex query.
|
||||
*
|
||||
* @param Xml\Request\AddressBookQueryReport $report
|
||||
*/
|
||||
protected function addressbookQueryReport($report)
|
||||
{
|
||||
$depth = $this->server->getHTTPDepth(0);
|
||||
|
||||
if (0 == $depth) {
|
||||
$candidateNodes = [
|
||||
$this->server->tree->getNodeForPath($this->server->getRequestUri()),
|
||||
];
|
||||
if (!$candidateNodes[0] instanceof ICard) {
|
||||
throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
|
||||
}
|
||||
} else {
|
||||
$candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
|
||||
}
|
||||
|
||||
$contentType = $report->contentType;
|
||||
if ($report->version) {
|
||||
$contentType .= '; version='.$report->version;
|
||||
}
|
||||
|
||||
$vcardType = $this->negotiateVCard(
|
||||
$contentType
|
||||
);
|
||||
|
||||
$validNodes = [];
|
||||
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, $report->filters, $report->test)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validNodes[] = $node;
|
||||
|
||||
if ($report->limit && $report->limit <= count($validNodes)) {
|
||||
// We hit the maximum number of items, we can stop now.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($validNodes as $validNode) {
|
||||
if (0 == $depth) {
|
||||
$href = $this->server->getRequestUri();
|
||||
} else {
|
||||
$href = $this->server->getRequestUri().'/'.$validNode->getName();
|
||||
}
|
||||
|
||||
list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
|
||||
|
||||
if (isset($props[200]['{'.self::NS_CARDDAV.'}address-data'])) {
|
||||
$props[200]['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard(
|
||||
$props[200]['{'.self::NS_CARDDAV.'}address-data'],
|
||||
$vcardType,
|
||||
$report->addressDataProperties
|
||||
);
|
||||
}
|
||||
$result[] = $props;
|
||||
}
|
||||
|
||||
$prefer = $this->server->getHTTPPrefer();
|
||||
|
||||
$this->server->httpResponse->setStatus(207);
|
||||
$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
|
||||
$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
|
||||
$this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a vcard makes it throught a list of filters.
|
||||
*
|
||||
* @param string $vcardData
|
||||
* @param string $test anyof or allof (which means OR or AND)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateFilters($vcardData, array $filters, $test)
|
||||
{
|
||||
if (!$filters) {
|
||||
return true;
|
||||
}
|
||||
$vcard = VObject\Reader::read($vcardData);
|
||||
|
||||
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 = [];
|
||||
if ($filter['param-filters']) {
|
||||
$results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
|
||||
}
|
||||
if ($filter['text-matches']) {
|
||||
$texts = [];
|
||||
foreach ($vProperties as $vProperty) {
|
||||
$texts[] = $vProperty->getValue();
|
||||
}
|
||||
|
||||
$results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
|
||||
}
|
||||
|
||||
if (1 === count($results)) {
|
||||
$success = $results[0];
|
||||
} else {
|
||||
if ('anyof' === $filter['test']) {
|
||||
$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 ('anyof' === $test && $success) {
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vcard->destroy();
|
||||
|
||||
return true;
|
||||
}
|
||||
if ('allof' === $test && !$success) {
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vcard->destroy();
|
||||
|
||||
return false;
|
||||
}
|
||||
} // foreach
|
||||
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vcard->destroy();
|
||||
|
||||
// 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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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']]->getValue(), $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 ('anyof' === $test && $success) {
|
||||
return true;
|
||||
}
|
||||
if ('allof' === $test && !$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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a text-filter can be applied to a specific property.
|
||||
*
|
||||
* @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 && 'anyof' === $test) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$success && 'allof' == $test) {
|
||||
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 'allof' === $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when fetching properties.
|
||||
*
|
||||
* This event is scheduled late in the process, after most work for
|
||||
* propfind has been done.
|
||||
*/
|
||||
public function propFindLate(DAV\PropFind $propFind, DAV\INode $node)
|
||||
{
|
||||
// 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 (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) {
|
||||
return;
|
||||
}
|
||||
$contentType = $propFind->get('{DAV:}getcontenttype');
|
||||
if (null !== $contentType) {
|
||||
list($part) = explode(';', $contentType);
|
||||
if ('text/x-vcard' === $part || 'text/vcard' === $part) {
|
||||
$propFind->set('{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 addressbooks.
|
||||
*
|
||||
* @param string $output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function htmlActionsPanel(DAV\INode $node, &$output)
|
||||
{
|
||||
if (!$node instanceof AddressBookHome) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output .= '<tr><td colspan="2"><form method="post" action="">
|
||||
<h3>Create new address book</h3>
|
||||
<input type="hidden" name="sabreAction" value="mkcol" />
|
||||
<input type="hidden" name="resourceType" value="{DAV:}collection,{'.self::NS_CARDDAV.'}addressbook" />
|
||||
<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 event is triggered after GET requests.
|
||||
*
|
||||
* This is used to transform data into jCal, if this was requested.
|
||||
*/
|
||||
public function httpAfterGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
if (null === $contentType || false === strpos($contentType, 'text/vcard')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
|
||||
|
||||
$newBody = $this->convertVCard(
|
||||
$response->getBody(),
|
||||
$target
|
||||
);
|
||||
|
||||
$response->setBody($newBody);
|
||||
$response->setHeader('Content-Type', $mimeType.'; charset=utf-8');
|
||||
$response->setHeader('Content-Length', strlen($newBody));
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper function performs the content-type negotiation for vcards.
|
||||
*
|
||||
* It will return one of the following strings:
|
||||
* 1. vcard3
|
||||
* 2. vcard4
|
||||
* 3. jcard
|
||||
*
|
||||
* It defaults to vcard3.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $mimeType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function negotiateVCard($input, &$mimeType = null)
|
||||
{
|
||||
$result = HTTP\negotiateContentType(
|
||||
$input,
|
||||
[
|
||||
// Most often used mime-type. Version 3
|
||||
'text/x-vcard',
|
||||
// The correct standard mime-type. Defaults to version 3 as
|
||||
// well.
|
||||
'text/vcard',
|
||||
// vCard 4
|
||||
'text/vcard; version=4.0',
|
||||
// vCard 3
|
||||
'text/vcard; version=3.0',
|
||||
// jCard
|
||||
'application/vcard+json',
|
||||
]
|
||||
);
|
||||
|
||||
$mimeType = $result;
|
||||
switch ($result) {
|
||||
default:
|
||||
case 'text/x-vcard':
|
||||
case 'text/vcard':
|
||||
case 'text/vcard; version=3.0':
|
||||
$mimeType = 'text/vcard';
|
||||
|
||||
return 'vcard3';
|
||||
case 'text/vcard; version=4.0':
|
||||
return 'vcard4';
|
||||
case 'application/vcard+json':
|
||||
return 'jcard';
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a vcard blob to a different version, or jcard.
|
||||
*
|
||||
* @param string|resource $data
|
||||
* @param string $target
|
||||
* @param array $propertiesFilter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function convertVCard($data, $target, array $propertiesFilter = null)
|
||||
{
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
$input = VObject\Reader::read($data);
|
||||
if (!empty($propertiesFilter)) {
|
||||
$propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
|
||||
$keys = array_unique(array_map(function ($child) {
|
||||
return $child->name;
|
||||
}, $input->children()));
|
||||
$keys = array_diff($keys, $propertiesFilter);
|
||||
foreach ($keys as $key) {
|
||||
unset($input->$key);
|
||||
}
|
||||
$data = $input->serialize();
|
||||
}
|
||||
$output = null;
|
||||
try {
|
||||
switch ($target) {
|
||||
default:
|
||||
case 'vcard3':
|
||||
if (VObject\Document::VCARD30 === $input->getDocumentType()) {
|
||||
// Do nothing
|
||||
return $data;
|
||||
}
|
||||
$output = $input->convert(VObject\Document::VCARD30);
|
||||
|
||||
return $output->serialize();
|
||||
case 'vcard4':
|
||||
if (VObject\Document::VCARD40 === $input->getDocumentType()) {
|
||||
// Do nothing
|
||||
return $data;
|
||||
}
|
||||
$output = $input->convert(VObject\Document::VCARD40);
|
||||
|
||||
return $output->serialize();
|
||||
case 'jcard':
|
||||
$output = $input->convert(VObject\Document::VCARD40);
|
||||
|
||||
return json_encode($output);
|
||||
}
|
||||
} finally {
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$input->destroy();
|
||||
if (!is_null($output)) {
|
||||
$output->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'carddav';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds support for CardDAV (rfc6352)',
|
||||
'link' => 'http://sabre.io/dav/carddav/',
|
||||
];
|
||||
}
|
||||
}
|
||||
165
vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
vendored
Normal file
165
vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @author Thomas Tanghus (http://tanghus.net/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class VCFExportPlugin extends DAV\ServerPlugin
|
||||
{
|
||||
/**
|
||||
* Reference to Server class.
|
||||
*
|
||||
* @var DAV\Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Initializes the plugin and registers event handlers.
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->server->on('method:GET', [$this, 'httpGet'], 90);
|
||||
$server->on('browserButtonActions', function ($path, $node, &$actions) {
|
||||
if ($node instanceof IAddressBook) {
|
||||
$actions .= '<a href="'.htmlspecialchars($path, ENT_QUOTES, 'UTF-8').'?export"><span class="oi" data-glyph="book"></span></a>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts GET requests on addressbook urls ending with ?export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function httpGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$queryParams = $request->getQueryParameters();
|
||||
if (!array_key_exists('export', $queryParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $request->getPath();
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($path);
|
||||
|
||||
if (!($node instanceof IAddressBook)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->server->transactionType = 'get-addressbook-export';
|
||||
|
||||
// Checking ACL, if available.
|
||||
if ($aclPlugin = $this->server->getPlugin('acl')) {
|
||||
$aclPlugin->checkPrivileges($path, '{DAV:}read');
|
||||
}
|
||||
|
||||
$nodes = $this->server->getPropertiesForPath($path, [
|
||||
'{'.Plugin::NS_CARDDAV.'}address-data',
|
||||
], 1);
|
||||
|
||||
$format = 'text/directory';
|
||||
|
||||
$output = null;
|
||||
$filenameExtension = null;
|
||||
|
||||
switch ($format) {
|
||||
case 'text/directory':
|
||||
$output = $this->generateVCF($nodes);
|
||||
$filenameExtension = '.vcf';
|
||||
break;
|
||||
}
|
||||
|
||||
$filename = preg_replace(
|
||||
'/[^a-zA-Z0-9-_ ]/um',
|
||||
'',
|
||||
$node->getName()
|
||||
);
|
||||
$filename .= '-'.date('Y-m-d').$filenameExtension;
|
||||
|
||||
$response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"');
|
||||
$response->setHeader('Content-Type', $format);
|
||||
|
||||
$response->setStatus(200);
|
||||
$response->setBody($output);
|
||||
|
||||
// Returning false to break the event chain
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all vcard objects, and builds one big vcf export.
|
||||
*
|
||||
* @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.
|
||||
$vcard = VObject\Reader::read($nodeData);
|
||||
$output .= $vcard->serialize();
|
||||
|
||||
// Destroy circular references to PHP will GC the object.
|
||||
$vcard->destroy();
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'vcf-export';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.',
|
||||
'link' => 'http://sabre.io/dav/vcf-export-plugin/',
|
||||
];
|
||||
}
|
||||
}
|
||||
66
vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
vendored
Normal file
66
vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Filter;
|
||||
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* AddressData parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:carddav}address-data XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-10.4
|
||||
*
|
||||
* This element is used in two distinct places, but this one specifically
|
||||
* encodes the address-data element as it appears in the addressbook-query
|
||||
* adressbook-multiget REPORT requests.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressData implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard',
|
||||
'version' => $reader->getAttribute('version') ?: '3.0',
|
||||
];
|
||||
|
||||
$elems = (array) $reader->parseInnerTree();
|
||||
$elems = array_filter($elems, function ($element) {
|
||||
return '{urn:ietf:params:xml:ns:carddav}prop' === $element['name'] &&
|
||||
isset($element['attributes']['name']);
|
||||
});
|
||||
$result['addressDataProperties'] = array_map(function ($element) {
|
||||
return $element['attributes']['name'];
|
||||
}, $elems);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
86
vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
86
vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\Reader;
|
||||
|
||||
/**
|
||||
* ParamFilter parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:carddav}param-filter XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-10.5.2
|
||||
*
|
||||
* The result will be spit out as an array.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class ParamFilter implements Element
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'name' => null,
|
||||
'is-not-defined' => false,
|
||||
'text-match' => null,
|
||||
];
|
||||
|
||||
$att = $reader->parseAttributes();
|
||||
$result['name'] = $att['name'];
|
||||
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
if (is_array($elems)) {
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CARDDAV.'}is-not-defined':
|
||||
$result['is-not-defined'] = true;
|
||||
break;
|
||||
case '{'.Plugin::NS_CARDDAV.'}text-match':
|
||||
$matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
|
||||
|
||||
if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
|
||||
throw new BadRequest('Unknown match-type: '.$matchType);
|
||||
}
|
||||
$result['text-match'] = [
|
||||
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
|
||||
'value' => $elem['value'],
|
||||
'match-type' => $matchType,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
95
vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
vendored
Normal file
95
vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Filter;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* PropFilter parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:carddav}prop-filter XML
|
||||
* element, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-10.5.1
|
||||
*
|
||||
* The result will be spit out as an array.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class PropFilter implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$result = [
|
||||
'name' => null,
|
||||
'test' => 'anyof',
|
||||
'is-not-defined' => false,
|
||||
'param-filters' => [],
|
||||
'text-matches' => [],
|
||||
];
|
||||
|
||||
$att = $reader->parseAttributes();
|
||||
$result['name'] = $att['name'];
|
||||
|
||||
if (isset($att['test']) && 'allof' === $att['test']) {
|
||||
$result['test'] = 'allof';
|
||||
}
|
||||
|
||||
$elems = $reader->parseInnerTree();
|
||||
|
||||
if (is_array($elems)) {
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{'.Plugin::NS_CARDDAV.'}param-filter':
|
||||
$result['param-filters'][] = $elem['value'];
|
||||
break;
|
||||
case '{'.Plugin::NS_CARDDAV.'}is-not-defined':
|
||||
$result['is-not-defined'] = true;
|
||||
break;
|
||||
case '{'.Plugin::NS_CARDDAV.'}text-match':
|
||||
$matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
|
||||
|
||||
if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
|
||||
throw new BadRequest('Unknown match-type: '.$matchType);
|
||||
}
|
||||
$result['text-matches'][] = [
|
||||
'negate-condition' => isset($elem['attributes']['negate-condition']) && 'yes' === $elem['attributes']['negate-condition'],
|
||||
'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
|
||||
'value' => $elem['value'],
|
||||
'match-type' => $matchType,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
77
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
vendored
Normal file
77
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Property;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* Supported-address-data property.
|
||||
*
|
||||
* This property is a representation of the supported-address-data property
|
||||
* in the CardDAV namespace.
|
||||
*
|
||||
* This property is defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-6.2.2
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SupportedAddressData implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* supported versions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $supportedData = [];
|
||||
|
||||
/**
|
||||
* Creates the property.
|
||||
*/
|
||||
public function __construct(array $supportedData = null)
|
||||
{
|
||||
if (is_null($supportedData)) {
|
||||
$supportedData = [
|
||||
['contentType' => 'text/vcard', 'version' => '3.0'],
|
||||
['contentType' => 'text/vcard', 'version' => '4.0'],
|
||||
['contentType' => 'application/vcard+json', 'version' => '4.0'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->supportedData = $supportedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
foreach ($this->supportedData as $supported) {
|
||||
$writer->startElement('{'.Plugin::NS_CARDDAV.'}address-data-type');
|
||||
$writer->writeAttributes([
|
||||
'content-type' => $supported['contentType'],
|
||||
'version' => $supported['version'],
|
||||
]);
|
||||
$writer->endElement(); // address-data-type
|
||||
}
|
||||
}
|
||||
}
|
||||
44
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
44
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Property;
|
||||
|
||||
use Sabre\Xml\Writer;
|
||||
use Sabre\Xml\XmlSerializable;
|
||||
|
||||
/**
|
||||
* supported-collation-set property.
|
||||
*
|
||||
* This property is a representation of the supported-collation-set property
|
||||
* in the CardDAV namespace.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class SupportedCollationSet implements XmlSerializable
|
||||
{
|
||||
/**
|
||||
* The xmlSerialize method is called during xml writing.
|
||||
*
|
||||
* Use the $writer argument to write its own xml serialization.
|
||||
*
|
||||
* An important note: do _not_ create a parent element. Any element
|
||||
* implementing XmlSerializable should only ever write what's considered
|
||||
* its 'inner xml'.
|
||||
*
|
||||
* The parent of the current element is responsible for writing a
|
||||
* containing element.
|
||||
*
|
||||
* This allows serializers to be re-used for different element names.
|
||||
*
|
||||
* If you are opening new elements, you must also close them again.
|
||||
*/
|
||||
public function xmlSerialize(Writer $writer)
|
||||
{
|
||||
foreach (['i;ascii-casemap', 'i;octet', 'i;unicode-casemap'] as $coll) {
|
||||
$writer->writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
vendored
Normal file
109
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Request;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\Uri;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* AddressBookMultiGetReport request parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-multiget
|
||||
* REPORT, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-8.7
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressBookMultiGetReport implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* An array with requested properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties;
|
||||
|
||||
/**
|
||||
* This is an array with the urls that are being requested.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $hrefs;
|
||||
|
||||
/**
|
||||
* The mimetype of the content that should be returend. Usually
|
||||
* text/vcard.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $contentType = null;
|
||||
|
||||
/**
|
||||
* The version of vcard data that should be returned. Usually 3.0,
|
||||
* referring to vCard 3.0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = null;
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = $reader->parseInnerTree([
|
||||
'{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
|
||||
'{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
]);
|
||||
|
||||
$newProps = [
|
||||
'hrefs' => [],
|
||||
'properties' => [],
|
||||
];
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{DAV:}prop':
|
||||
$newProps['properties'] = array_keys($elem['value']);
|
||||
if (isset($elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'])) {
|
||||
$newProps += $elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'];
|
||||
}
|
||||
break;
|
||||
case '{DAV:}href':
|
||||
$newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
foreach ($newProps as $key => $value) {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
194
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
vendored
Normal file
194
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\CardDAV\Xml\Request;
|
||||
|
||||
use Sabre\CardDAV\Plugin;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\XmlDeserializable;
|
||||
|
||||
/**
|
||||
* AddressBookQueryReport request parser.
|
||||
*
|
||||
* This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-query
|
||||
* REPORT, as defined in:
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc6352#section-8.6
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class AddressBookQueryReport implements XmlDeserializable
|
||||
{
|
||||
/**
|
||||
* An array with requested properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties;
|
||||
|
||||
/**
|
||||
* An array with requested vcard properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $addressDataProperties = [];
|
||||
|
||||
/**
|
||||
* List of property/component filters.
|
||||
*
|
||||
* This is an array with filters. Every item is a property filter. Every
|
||||
* property filter has the following keys:
|
||||
* * name - name of the component to filter on
|
||||
* * test - anyof or allof
|
||||
* * is-not-defined - Test for non-existence
|
||||
* * param-filters - A list of parameter filters on the property
|
||||
* * text-matches - A list of text values the filter needs to match
|
||||
*
|
||||
* Each param-filter has the following keys:
|
||||
* * name - name of the parameter
|
||||
* * is-not-defined - Test for non-existence
|
||||
* * text-match - Match the parameter value
|
||||
*
|
||||
* Each text-match in property filters, and the single text-match in
|
||||
* param-filters have the following keys:
|
||||
*
|
||||
* * value - value to match
|
||||
* * match-type - contains, starts-with, ends-with, equals
|
||||
* * negate-condition - Do the opposite match
|
||||
* * collation - Usually i;unicode-casemap
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Either 'anyof' or 'allof'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $test;
|
||||
|
||||
/**
|
||||
* The mimetype of the content that should be returend. Usually
|
||||
* text/vcard.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $contentType = null;
|
||||
|
||||
/**
|
||||
* The version of vcard data that should be returned. Usually 3.0,
|
||||
* referring to vCard 3.0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = null;
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* This method is called statically, this is because in theory this method
|
||||
* may be used as a type of constructor, or factory method.
|
||||
*
|
||||
* Often you want to return an instance of the current class, but you are
|
||||
* free to return other data as well.
|
||||
*
|
||||
* You are responsible for advancing the reader to the next element. Not
|
||||
* doing anything will result in a never-ending loop.
|
||||
*
|
||||
* If you just want to skip parsing for this element altogether, you can
|
||||
* just call $reader->next();
|
||||
*
|
||||
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
||||
* the next element.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function xmlDeserialize(Reader $reader)
|
||||
{
|
||||
$elems = (array) $reader->parseInnerTree([
|
||||
'{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
|
||||
'{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
|
||||
'{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
|
||||
'{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
|
||||
]);
|
||||
|
||||
$newProps = [
|
||||
'filters' => null,
|
||||
'properties' => [],
|
||||
'test' => 'anyof',
|
||||
'limit' => null,
|
||||
];
|
||||
|
||||
if (!is_array($elems)) {
|
||||
$elems = [];
|
||||
}
|
||||
|
||||
foreach ($elems as $elem) {
|
||||
switch ($elem['name']) {
|
||||
case '{DAV:}prop':
|
||||
$newProps['properties'] = array_keys($elem['value']);
|
||||
if (isset($elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'])) {
|
||||
$newProps += $elem['value']['{'.Plugin::NS_CARDDAV.'}address-data'];
|
||||
}
|
||||
break;
|
||||
case '{'.Plugin::NS_CARDDAV.'}filter':
|
||||
|
||||
if (!is_null($newProps['filters'])) {
|
||||
throw new BadRequest('You can only include 1 {'.Plugin::NS_CARDDAV.'}filter element');
|
||||
}
|
||||
if (isset($elem['attributes']['test'])) {
|
||||
$newProps['test'] = $elem['attributes']['test'];
|
||||
if ('allof' !== $newProps['test'] && 'anyof' !== $newProps['test']) {
|
||||
throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
|
||||
}
|
||||
}
|
||||
|
||||
$newProps['filters'] = [];
|
||||
foreach ((array) $elem['value'] as $subElem) {
|
||||
if ($subElem['name'] === '{'.Plugin::NS_CARDDAV.'}prop-filter') {
|
||||
$newProps['filters'][] = $subElem['value'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '{'.Plugin::NS_CARDDAV.'}limit':
|
||||
foreach ($elem['value'] as $child) {
|
||||
if ($child['name'] === '{'.Plugin::NS_CARDDAV.'}nresults') {
|
||||
$newProps['limit'] = (int) $child['value'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($newProps['filters'])) {
|
||||
/*
|
||||
* We are supposed to throw this error, but KDE sometimes does not
|
||||
* include the filter element, and we need to treat it as if no
|
||||
* filters are supplied
|
||||
*/
|
||||
//throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
|
||||
$newProps['filters'] = [];
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
foreach ($newProps as $key => $value) {
|
||||
$obj->$key = $value;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
136
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php
vendored
Normal file
136
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
use Sabre\HTTP;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author James David Low (http://jameslow.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class AbstractBasic implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* Authentication Realm.
|
||||
*
|
||||
* The realm is often displayed by browser clients when showing the
|
||||
* authentication dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $realm = 'sabre/dav';
|
||||
|
||||
/**
|
||||
* This is the prefix that will be used to generate principal urls.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalPrefix = 'principals/';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Sets the authentication realm for this backend.
|
||||
*
|
||||
* @param string $realm
|
||||
*/
|
||||
public function setRealm($realm)
|
||||
{
|
||||
$this->realm = $realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* When this method is called, the backend must check if authentication was
|
||||
* successful.
|
||||
*
|
||||
* The returned value must be one of the following
|
||||
*
|
||||
* [true, "principals/username"]
|
||||
* [false, "reason for failure"]
|
||||
*
|
||||
* If authentication was successful, it's expected that the authentication
|
||||
* backend returns a so-called principal url.
|
||||
*
|
||||
* Examples of a principal url:
|
||||
*
|
||||
* principals/admin
|
||||
* principals/user1
|
||||
* principals/users/joe
|
||||
* principals/uid/123457
|
||||
*
|
||||
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
||||
* return a string such as:
|
||||
*
|
||||
* principals/users/[username]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new HTTP\Auth\Basic(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
|
||||
$userpass = $auth->getCredentials();
|
||||
if (!$userpass) {
|
||||
return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
|
||||
}
|
||||
if (!$this->validateUserPass($userpass[0], $userpass[1])) {
|
||||
return [false, 'Username or password was incorrect'];
|
||||
}
|
||||
|
||||
return [true, $this->principalPrefix.$userpass[0]];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Basic Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new HTTP\Auth\Basic(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
$auth->requireLogin();
|
||||
}
|
||||
}
|
||||
130
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php
vendored
Normal file
130
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
use Sabre\HTTP;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* HTTP Bearer authentication backend class.
|
||||
*
|
||||
* This class can be used by authentication objects wishing to use HTTP Bearer
|
||||
* Most of the digest logic is handled, implementors just need to worry about
|
||||
* the validateBearerToken method.
|
||||
*
|
||||
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
|
||||
* @author François Kooman (https://tuxed.net/)
|
||||
* @author James David Low (http://jameslow.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class AbstractBearer implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* Authentication Realm.
|
||||
*
|
||||
* The realm is often displayed by browser clients when showing the
|
||||
* authentication dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $realm = 'sabre/dav';
|
||||
|
||||
/**
|
||||
* Validates a Bearer token.
|
||||
*
|
||||
* This method should return the full principal url, or false if the
|
||||
* token was incorrect.
|
||||
*
|
||||
* @param string $bearerToken
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
abstract protected function validateBearerToken($bearerToken);
|
||||
|
||||
/**
|
||||
* Sets the authentication realm for this backend.
|
||||
*
|
||||
* @param string $realm
|
||||
*/
|
||||
public function setRealm($realm)
|
||||
{
|
||||
$this->realm = $realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* When this method is called, the backend must check if authentication was
|
||||
* successful.
|
||||
*
|
||||
* The returned value must be one of the following
|
||||
*
|
||||
* [true, "principals/username"]
|
||||
* [false, "reason for failure"]
|
||||
*
|
||||
* If authentication was successful, it's expected that the authentication
|
||||
* backend returns a so-called principal url.
|
||||
*
|
||||
* Examples of a principal url:
|
||||
*
|
||||
* principals/admin
|
||||
* principals/user1
|
||||
* principals/users/joe
|
||||
* principals/uid/123457
|
||||
*
|
||||
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
||||
* return a string such as:
|
||||
*
|
||||
* principals/users/[username]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new HTTP\Auth\Bearer(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
|
||||
$bearerToken = $auth->getToken($request);
|
||||
if (!$bearerToken) {
|
||||
return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"];
|
||||
}
|
||||
$principalUrl = $this->validateBearerToken($bearerToken);
|
||||
if (!$principalUrl) {
|
||||
return [false, 'Bearer token was incorrect'];
|
||||
}
|
||||
|
||||
return [true, $principalUrl];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Bearer Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new HTTP\Auth\Bearer(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
$auth->requireLogin();
|
||||
}
|
||||
}
|
||||
160
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
vendored
Normal file
160
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\HTTP;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
abstract class AbstractDigest implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* Authentication Realm.
|
||||
*
|
||||
* The realm is often displayed by browser clients when showing the
|
||||
* authentication dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $realm = 'SabreDAV';
|
||||
|
||||
/**
|
||||
* This is the prefix that will be used to generate principal urls.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalPrefix = 'principals/';
|
||||
|
||||
/**
|
||||
* Sets the authentication realm for this backend.
|
||||
*
|
||||
* Be aware that for Digest authentication, the realm influences the digest
|
||||
* hash. Choose the realm wisely, because if you change it later, all the
|
||||
* existing hashes will break and nobody can authenticate.
|
||||
*
|
||||
* @param string $realm
|
||||
*/
|
||||
public function setRealm($realm)
|
||||
{
|
||||
$this->realm = $realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* When this method is called, the backend must check if authentication was
|
||||
* successful.
|
||||
*
|
||||
* The returned value must be one of the following
|
||||
*
|
||||
* [true, "principals/username"]
|
||||
* [false, "reason for failure"]
|
||||
*
|
||||
* If authentication was successful, it's expected that the authentication
|
||||
* backend returns a so-called principal url.
|
||||
*
|
||||
* Examples of a principal url:
|
||||
*
|
||||
* principals/admin
|
||||
* principals/user1
|
||||
* principals/users/joe
|
||||
* principals/uid/123457
|
||||
*
|
||||
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
||||
* return a string such as:
|
||||
*
|
||||
* principals/users/[username]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$digest = new HTTP\Auth\Digest(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
$digest->init();
|
||||
|
||||
$username = $digest->getUsername();
|
||||
|
||||
// No username was given
|
||||
if (!$username) {
|
||||
return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"];
|
||||
}
|
||||
|
||||
$hash = $this->getDigestHash($this->realm, $username);
|
||||
// If this was false, the user account didn't exist
|
||||
if (false === $hash || is_null($hash)) {
|
||||
return [false, 'Username or password was incorrect'];
|
||||
}
|
||||
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)) {
|
||||
return [false, 'Username or password was incorrect'];
|
||||
}
|
||||
|
||||
return [true, $this->principalPrefix.$username];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Basic Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$auth = new HTTP\Auth\Digest(
|
||||
$this->realm,
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
$auth->init();
|
||||
|
||||
$oldStatus = $response->getStatus() ?: 200;
|
||||
$auth->requireLogin();
|
||||
|
||||
// Preventing the digest utility from modifying the http status code,
|
||||
// this should be handled by the main plugin.
|
||||
$response->setStatus($oldStatus);
|
||||
}
|
||||
}
|
||||
93
vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php
vendored
Normal file
93
vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Apache (or NGINX) authenticator.
|
||||
*
|
||||
* This authentication backend assumes that authentication has been
|
||||
* configured in apache (or NGINX), rather than within SabreDAV.
|
||||
*
|
||||
* Make sure apache (or NGINX) is properly configured for this to work.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Apache implements BackendInterface
|
||||
{
|
||||
/**
|
||||
* This is the prefix that will be used to generate principal urls.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalPrefix = 'principals/';
|
||||
|
||||
/**
|
||||
* When this method is called, the backend must check if authentication was
|
||||
* successful.
|
||||
*
|
||||
* The returned value must be one of the following
|
||||
*
|
||||
* [true, "principals/username"]
|
||||
* [false, "reason for failure"]
|
||||
*
|
||||
* If authentication was successful, it's expected that the authentication
|
||||
* backend returns a so-called principal url.
|
||||
*
|
||||
* Examples of a principal url:
|
||||
*
|
||||
* principals/admin
|
||||
* principals/user1
|
||||
* principals/users/joe
|
||||
* principals/uid/123457
|
||||
*
|
||||
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
||||
* return a string such as:
|
||||
*
|
||||
* principals/users/[username]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$remoteUser = $request->getRawServerValue('REMOTE_USER');
|
||||
if (is_null($remoteUser)) {
|
||||
$remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
|
||||
}
|
||||
if (is_null($remoteUser)) {
|
||||
$remoteUser = $request->getRawServerValue('PHP_AUTH_USER');
|
||||
}
|
||||
if (is_null($remoteUser)) {
|
||||
return [false, 'No REMOTE_USER, REDIRECT_REMOTE_USER, or PHP_AUTH_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
|
||||
}
|
||||
|
||||
return [true, $this->principalPrefix.$remoteUser];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Basic Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
}
|
||||
65
vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php
vendored
Normal file
65
vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This is the base class for any authentication object.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface BackendInterface
|
||||
{
|
||||
/**
|
||||
* When this method is called, the backend must check if authentication was
|
||||
* successful.
|
||||
*
|
||||
* The returned value must be one of the following
|
||||
*
|
||||
* [true, "principals/username"]
|
||||
* [false, "reason for failure"]
|
||||
*
|
||||
* If authentication was successful, it's expected that the authentication
|
||||
* backend returns a so-called principal url.
|
||||
*
|
||||
* Examples of a principal url:
|
||||
*
|
||||
* principals/admin
|
||||
* principals/user1
|
||||
* principals/users/joe
|
||||
* principals/uid/123457
|
||||
*
|
||||
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
||||
* return a string such as:
|
||||
*
|
||||
* principals/users/[username]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response);
|
||||
|
||||
/**
|
||||
* This method is called when a user could not be authenticated, and
|
||||
* authentication was required for the current request.
|
||||
*
|
||||
* This gives you the opportunity to set authentication headers. The 401
|
||||
* status code will already be set.
|
||||
*
|
||||
* In this case of Basic Auth, this would for example mean that the
|
||||
* following header needs to be set:
|
||||
*
|
||||
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
|
||||
*
|
||||
* Keep in mind that in the case of multiple authentication backends, other
|
||||
* WWW-Authenticate headers may already have been set, and you'll want to
|
||||
* append your own WWW-Authenticate header instead of overwriting the
|
||||
* existing one.
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response);
|
||||
}
|
||||
56
vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php
vendored
Normal file
56
vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
/**
|
||||
* Extremely simply HTTP Basic auth backend.
|
||||
*
|
||||
* This backend basically works by calling a callback, which receives a
|
||||
* username and password.
|
||||
* The callback must return true or false depending on if authentication was
|
||||
* correct.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class BasicCallBack extends AbstractBasic
|
||||
{
|
||||
/**
|
||||
* Callback.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $callBack;
|
||||
|
||||
/**
|
||||
* Creates the backend.
|
||||
*
|
||||
* A callback must be provided to handle checking the username and
|
||||
* password.
|
||||
*/
|
||||
public function __construct(callable $callBack)
|
||||
{
|
||||
$this->callBack = $callBack;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected function validateUserPass($username, $password)
|
||||
{
|
||||
$cb = $this->callBack;
|
||||
|
||||
return $cb($username, $password);
|
||||
}
|
||||
}
|
||||
74
vendor/sabre/dav/lib/DAV/Auth/Backend/File.php
vendored
Normal file
74
vendor/sabre/dav/lib/DAV/Auth/Backend/File.php
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class File extends AbstractDigest
|
||||
{
|
||||
/**
|
||||
* List of users.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $users = [];
|
||||
|
||||
/**
|
||||
* Creates the backend object.
|
||||
*
|
||||
* If the filename argument is passed in, it will parse out the specified file first.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public function loadFile($filename)
|
||||
{
|
||||
foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) {
|
||||
if (2 !== substr_count($line, ':')) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
82
vendor/sabre/dav/lib/DAV/Auth/Backend/IMAP.php
vendored
Normal file
82
vendor/sabre/dav/lib/DAV/Auth/Backend/IMAP.php
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
/**
|
||||
* This is an authentication backend that uses imap.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Michael Niewöhner (foss@mniewoehner.de)
|
||||
* @author rosali (https://github.com/rosali)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class IMAP extends AbstractBasic
|
||||
{
|
||||
/**
|
||||
* IMAP server in the form {host[:port][/flag1/flag2...]}.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.imap-open.php
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $mailbox;
|
||||
|
||||
/**
|
||||
* Creates the backend object.
|
||||
*
|
||||
* @param string $mailbox
|
||||
*/
|
||||
public function __construct($mailbox)
|
||||
{
|
||||
$this->mailbox = $mailbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to an IMAP server and tries to authenticate.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function imapOpen($username, $password)
|
||||
{
|
||||
$success = false;
|
||||
|
||||
try {
|
||||
$imap = imap_open($this->mailbox, $username, $password, OP_HALFOPEN | OP_READONLY, 1);
|
||||
if ($imap) {
|
||||
$success = true;
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
error_log($e->getMessage());
|
||||
}
|
||||
|
||||
$errors = imap_errors();
|
||||
if ($errors) {
|
||||
foreach ($errors as $error) {
|
||||
error_log($error);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($imap) && $imap) {
|
||||
imap_close($imap);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a username and password by trying to authenticate against IMAP.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateUserPass($username, $password)
|
||||
{
|
||||
return $this->imapOpen($username, $password);
|
||||
}
|
||||
}
|
||||
55
vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php
vendored
Normal file
55
vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth\Backend;
|
||||
|
||||
/**
|
||||
* This is an authentication backend that uses a database to manage passwords.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/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
|
||||
*/
|
||||
public $tableName = 'users';
|
||||
|
||||
/**
|
||||
* Creates the backend object.
|
||||
*
|
||||
* If the filename argument is passed in, it will parse out the specified file fist.
|
||||
*/
|
||||
public function __construct(\PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 digesta1 FROM '.$this->tableName.' WHERE username = ?');
|
||||
$stmt->execute([$username]);
|
||||
|
||||
return $stmt->fetchColumn() ?: null;
|
||||
}
|
||||
}
|
||||
259
vendor/sabre/dav/lib/DAV/Auth/Plugin.php
vendored
Normal file
259
vendor/sabre/dav/lib/DAV/Auth/Plugin.php
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Auth;
|
||||
|
||||
use Sabre\DAV\Exception\NotAuthenticated;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* This plugin provides Authentication for a WebDAV server.
|
||||
*
|
||||
* It works by providing a Auth\Backend class. Several examples of these
|
||||
* classes can be found in the Backend directory.
|
||||
*
|
||||
* It's possible to provide more than one backend to this plugin. If more than
|
||||
* one backend was provided, each backend will attempt to authenticate. Only if
|
||||
* all backends fail, we throw a 401.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class Plugin extends ServerPlugin
|
||||
{
|
||||
/**
|
||||
* By default this plugin will require that the user is authenticated,
|
||||
* and refuse any access if the user is not authenticated.
|
||||
*
|
||||
* If this setting is set to false, we let the user through, whether they
|
||||
* are authenticated or not.
|
||||
*
|
||||
* This is useful if you want to allow both authenticated and
|
||||
* unauthenticated access to your server.
|
||||
*
|
||||
* @param bool
|
||||
*/
|
||||
public $autoRequireLogin = true;
|
||||
|
||||
/**
|
||||
* authentication backends.
|
||||
*/
|
||||
protected $backends;
|
||||
|
||||
/**
|
||||
* The currently logged in principal. Will be `null` if nobody is currently
|
||||
* logged in.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $currentPrincipal;
|
||||
|
||||
/**
|
||||
* Creates the authentication plugin.
|
||||
*
|
||||
* @param Backend\BackendInterface $authBackend
|
||||
*/
|
||||
public function __construct(Backend\BackendInterface $authBackend = null)
|
||||
{
|
||||
if (!is_null($authBackend)) {
|
||||
$this->addBackend($authBackend);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an authentication backend to the plugin.
|
||||
*/
|
||||
public function addBackend(Backend\BackendInterface $authBackend)
|
||||
{
|
||||
$this->backends[] = $authBackend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the plugin. This function is automatically called by the server.
|
||||
*/
|
||||
public function initialize(Server $server)
|
||||
{
|
||||
$server->on('beforeMethod:*', [$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 currently logged-in principal.
|
||||
*
|
||||
* This will return a string such as:
|
||||
*
|
||||
* principals/username
|
||||
* principals/users/username
|
||||
*
|
||||
* This method will return null if nobody is logged in.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCurrentPrincipal()
|
||||
{
|
||||
return $this->currentPrincipal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called before any HTTP method and forces users to be authenticated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function beforeMethod(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if ($this->currentPrincipal) {
|
||||
// We already have authentication information. This means that the
|
||||
// event has already fired earlier, and is now likely fired for a
|
||||
// sub-request.
|
||||
//
|
||||
// We don't want to authenticate users twice, so we simply don't do
|
||||
// anything here. See Issue #700 for additional reasoning.
|
||||
//
|
||||
// This is not a perfect solution, but will be fixed once the
|
||||
// "currently authenticated principal" is information that's not
|
||||
// not associated with the plugin, but rather per-request.
|
||||
//
|
||||
// See issue #580 for more information about that.
|
||||
return;
|
||||
}
|
||||
|
||||
$authResult = $this->check($request, $response);
|
||||
|
||||
if ($authResult[0]) {
|
||||
// Auth was successful
|
||||
$this->currentPrincipal = $authResult[1];
|
||||
$this->loginFailedReasons = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, it means that no authentication backend was
|
||||
// successful in authenticating the user.
|
||||
$this->currentPrincipal = null;
|
||||
$this->loginFailedReasons = $authResult[1];
|
||||
|
||||
if ($this->autoRequireLogin) {
|
||||
$this->challenge($request, $response);
|
||||
throw new NotAuthenticated(implode(', ', $authResult[1]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks authentication credentials, and logs the user in if possible.
|
||||
*
|
||||
* This method returns an array. The first item in the array is a boolean
|
||||
* indicating if login was successful.
|
||||
*
|
||||
* If login was successful, the second item in the array will contain the
|
||||
* current principal url/path of the logged in user.
|
||||
*
|
||||
* If login was not successful, the second item in the array will contain a
|
||||
* an array with strings. The strings are a list of reasons why login was
|
||||
* unsuccessful. For every auth backend there will be one reason, so usually
|
||||
* there's just one.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
if (!$this->backends) {
|
||||
throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
|
||||
}
|
||||
$reasons = [];
|
||||
foreach ($this->backends as $backend) {
|
||||
$result = $backend->check(
|
||||
$request,
|
||||
$response
|
||||
);
|
||||
|
||||
if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
|
||||
throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
|
||||
}
|
||||
|
||||
if ($result[0]) {
|
||||
$this->currentPrincipal = $result[1];
|
||||
// Exit early
|
||||
return [true, $result[1]];
|
||||
}
|
||||
$reasons[] = $result[1];
|
||||
}
|
||||
|
||||
return [false, $reasons];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sends authentication challenges to the user.
|
||||
*
|
||||
* This method will for example cause a HTTP Basic backend to set a
|
||||
* WWW-Authorization header, indicating to the client that it should
|
||||
* authenticate.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
foreach ($this->backends as $backend) {
|
||||
$backend->challenge($request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of reasons why login failed for the last login operation.
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
protected $loginFailedReasons;
|
||||
|
||||
/**
|
||||
* Returns a list of reasons why login was unsuccessful.
|
||||
*
|
||||
* This method will return the login failed reasons for the last login
|
||||
* operation. One for each auth backend.
|
||||
*
|
||||
* This method returns null if the last authentication attempt was
|
||||
* successful, or if there was no authentication attempt yet.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getLoginFailedReasons()
|
||||
{
|
||||
return $this->loginFailedReasons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bunch of meta-data about the plugin.
|
||||
*
|
||||
* Providing this information is optional, and is mainly displayed by the
|
||||
* Browser plugin.
|
||||
*
|
||||
* The description key in the returned array may contain html and will not
|
||||
* be sanitized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluginInfo()
|
||||
{
|
||||
return [
|
||||
'name' => $this->getPluginName(),
|
||||
'description' => 'Generic authentication plugin',
|
||||
'link' => 'http://sabre.io/dav/authentication/',
|
||||
];
|
||||
}
|
||||
}
|
||||
93
vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php
vendored
Normal file
93
vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Browser;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\PropFind;
|
||||
use Sabre\Uri;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class GuessContentType extends DAV\ServerPlugin
|
||||
{
|
||||
/**
|
||||
* List of recognized file extensions.
|
||||
*
|
||||
* Feel free to add more
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $extensionMap = [
|
||||
// images
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
|
||||
// groupware
|
||||
'ics' => 'text/calendar',
|
||||
'vcf' => 'text/vcard',
|
||||
|
||||
// text
|
||||
'txt' => 'text/plain',
|
||||
];
|
||||
|
||||
/**
|
||||
* Initializes the plugin.
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
// Using a relatively low priority (200) to allow other extensions
|
||||
// to set the content-type first.
|
||||
$server->on('propFind', [$this, 'propFind'], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Our PROPFIND handler.
|
||||
*
|
||||
* Here we set a contenttype, if the node didn't already have one.
|
||||
*/
|
||||
public function propFind(PropFind $propFind, INode $node)
|
||||
{
|
||||
$propFind->handle('{DAV:}getcontenttype', function () use ($propFind) {
|
||||
list(, $fileName) = Uri\split($propFind->getPath());
|
||||
|
||||
return $this->getContentType($fileName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple method to return the contenttype.
|
||||
*
|
||||
* @param string $fileName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType($fileName)
|
||||
{
|
||||
if (null !== $fileName) {
|
||||
// Just grabbing the extension
|
||||
$extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
|
||||
if (isset($this->extensionMap[$extension])) {
|
||||
return $this->extensionMap[$extension];
|
||||
}
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
34
vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php
vendored
Normal file
34
vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Browser;
|
||||
|
||||
/**
|
||||
* WebDAV properties that implement this interface are able to generate their
|
||||
* own html output for the browser plugin.
|
||||
*
|
||||
* This is only useful for display purposes, and might make it a bit easier for
|
||||
* people to read and understand the value of some properties.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
interface HtmlOutput
|
||||
{
|
||||
/**
|
||||
* Generate html representation for this value.
|
||||
*
|
||||
* The html output is 100% trusted, and no effort is being made to sanitize
|
||||
* it. It's up to the implementor to sanitize user provided values.
|
||||
*
|
||||
* The output must be in UTF-8.
|
||||
*
|
||||
* The baseUri parameter is a url to the root of the application, and can
|
||||
* be used to construct local links.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml(HtmlOutputHelper $html);
|
||||
}
|
||||
118
vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php
vendored
Normal file
118
vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Browser;
|
||||
|
||||
use Sabre\Uri;
|
||||
use Sabre\Xml\Service as XmlService;
|
||||
|
||||
/**
|
||||
* This class provides a few utility functions for easily generating HTML for
|
||||
* the browser plugin.
|
||||
*
|
||||
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class HtmlOutputHelper
|
||||
{
|
||||
/**
|
||||
* Link to the root of the application.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUri;
|
||||
|
||||
/**
|
||||
* List of xml namespaces.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $namespaceMap;
|
||||
|
||||
/**
|
||||
* Creates the object.
|
||||
*
|
||||
* baseUri must point to the root of the application. This will be used to
|
||||
* easily generate links.
|
||||
*
|
||||
* The namespaceMap contains an array with the list of xml namespaces and
|
||||
* their prefixes. WebDAV uses a lot of XML with complex namespaces, so
|
||||
* that can be used to make output a lot shorter.
|
||||
*
|
||||
* @param string $baseUri
|
||||
*/
|
||||
public function __construct($baseUri, array $namespaceMap)
|
||||
{
|
||||
$this->baseUri = $baseUri;
|
||||
$this->namespaceMap = $namespaceMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a 'full' url based on a relative one.
|
||||
*
|
||||
* For relative urls, the base of the application is taken as the reference
|
||||
* url, not the 'current url of the current request'.
|
||||
*
|
||||
* Absolute urls are left alone.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fullUrl($path)
|
||||
{
|
||||
return Uri\resolve($this->baseUri, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape string for HTML output.
|
||||
*
|
||||
* @param scalar $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function h($input)
|
||||
{
|
||||
return htmlspecialchars((string) $input, ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a full <a>-tag.
|
||||
*
|
||||
* Url is automatically expanded. If label is not specified, we re-use the
|
||||
* url.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function link($url, $label = null)
|
||||
{
|
||||
$url = $this->h($this->fullUrl($url));
|
||||
|
||||
return '<a href="'.$url.'">'.($label ? $this->h($label) : $url).'</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes an xml element in clark-notation, and turns it into a
|
||||
* shortened version with a prefix, if it was a known namespace.
|
||||
*
|
||||
* @param string $element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function xmlName($element)
|
||||
{
|
||||
list($ns, $localName) = XmlService::parseClarkNotation($element);
|
||||
if (isset($this->namespaceMap[$ns])) {
|
||||
$propName = $this->namespaceMap[$ns].':'.$localName;
|
||||
} else {
|
||||
$propName = $element;
|
||||
}
|
||||
|
||||
return '<span title="'.$this->h($element).'">'.$this->h($propName).'</span>';
|
||||
}
|
||||
}
|
||||
58
vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php
vendored
Normal file
58
vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabre\DAV\Browser;
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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) fruux GmbH (https://fruux.com/)
|
||||
* @author Evert Pot (http://evertpot.com/)
|
||||
* @license http://sabre.io/license/ Modified BSD License
|
||||
*/
|
||||
class MapGetToPropFind extends DAV\ServerPlugin
|
||||
{
|
||||
/**
|
||||
* reference to server class.
|
||||
*
|
||||
* @var DAV\Server
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* Initializes the plugin and subscribes to events.
|
||||
*/
|
||||
public function initialize(DAV\Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->server->on('method:GET', [$this, 'httpGet'], 90);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function httpGet(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$node = $this->server->tree->getNodeForPath($request->getPath());
|
||||
if ($node instanceof DAV\IFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subRequest = clone $request;
|
||||
$subRequest->setMethod('PROPFIND');
|
||||
|
||||
$this->server->invokeMethod($subRequest, $response);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user