aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sabre/dav/lib')
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php6
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php15
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/PDO.php381
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php225
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php296
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Calendar.php63
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarHome.php64
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarObject.php61
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php22
-rw-r--r--vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php2
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php28
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php76
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/Node.php76
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Plugin.php113
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php64
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php69
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php107
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php16
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php2
-rw-r--r--vendor/sabre/dav/lib/CalDAV/SharedCalendar.php237
-rw-r--r--vendor/sabre/dav/lib/CalDAV/SharingPlugin.php106
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php67
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php11
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php5
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php209
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php11
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php63
-rw-r--r--vendor/sabre/dav/lib/CardDAV/AddressBook.php82
-rw-r--r--vendor/sabre/dav/lib/CardDAV/AddressBookHome.php76
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php7
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Backend/PDO.php9
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Card.php53
-rw-r--r--vendor/sabre/dav/lib/CardDAV/Plugin.php122
-rw-r--r--vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php28
-rw-r--r--vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php6
-rw-r--r--vendor/sabre/dav/lib/DAV/Auth/Plugin.php126
-rw-r--r--vendor/sabre/dav/lib/DAV/Browser/Plugin.php13
-rw-r--r--vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css6
-rw-r--r--vendor/sabre/dav/lib/DAV/Client.php19
-rw-r--r--vendor/sabre/dav/lib/DAV/CorePlugin.php40
-rw-r--r--vendor/sabre/dav/lib/DAV/FS/Directory.php6
-rw-r--r--vendor/sabre/dav/lib/DAV/File.php20
-rw-r--r--vendor/sabre/dav/lib/DAV/ICollection.php4
-rw-r--r--vendor/sabre/dav/lib/DAV/IFile.php2
-rw-r--r--vendor/sabre/dav/lib/DAV/INode.php5
-rw-r--r--vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php33
-rw-r--r--vendor/sabre/dav/lib/DAV/Server.php39
-rw-r--r--vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php69
-rw-r--r--vendor/sabre/dav/lib/DAV/Sharing/Plugin.php342
-rw-r--r--vendor/sabre/dav/lib/DAV/Tree.php2
-rw-r--r--vendor/sabre/dav/lib/DAV/Version.php2
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php199
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Property/Href.php17
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php70
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php48
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php143
-rw-r--r--vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php81
-rw-r--r--vendor/sabre/dav/lib/DAVACL/ACLTrait.php100
-rw-r--r--vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php2
-rw-r--r--vendor/sabre/dav/lib/DAVACL/FS/Collection.php52
-rw-r--r--vendor/sabre/dav/lib/DAVACL/FS/File.php49
-rw-r--r--vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php72
-rw-r--r--vendor/sabre/dav/lib/DAVACL/IACL.php1
-rw-r--r--vendor/sabre/dav/lib/DAVACL/Plugin.php614
-rw-r--r--vendor/sabre/dav/lib/DAVACL/Principal.php71
-rw-r--r--vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php2
-rw-r--r--vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php57
-rw-r--r--vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php20
-rw-r--r--vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php67
-rw-r--r--vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php107
70 files changed, 3298 insertions, 1980 deletions
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
index 7513fb60d..bd8ee7602 100644
--- a/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
@@ -44,10 +44,12 @@ interface BackendInterface {
* 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
* @param array $properties
- * @return void
+ * @return mixed
*/
function createCalendar($principalUri, $calendarUri, array $properties);
@@ -63,7 +65,7 @@ interface BackendInterface {
*
* Read the PropPatch documentation for more info and examples.
*
- * @param string $path
+ * @param mixed $calendarId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
index 19b9b22a7..9c00a89ef 100644
--- a/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
@@ -43,4 +43,19 @@ interface NotificationSupport extends BackendInterface {
*/
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 null|string
+ */
+ function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
+
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php b/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
index 76b69dc6e..b1c013d62 100644
--- a/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
@@ -2,10 +2,11 @@
namespace Sabre\CalDAV\Backend;
-use Sabre\VObject;
use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\Forbidden;
+use Sabre\VObject;
+use Sabre\DAV\Xml\Element\Sharee;
/**
* PDO CalDAV backend
@@ -17,7 +18,12 @@ use Sabre\DAV\Exception\Forbidden;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
+class PDO extends AbstractBackend
+ implements
+ SyncSupport,
+ SubscriptionSupport,
+ SchedulingSupport,
+ SharingSupport {
/**
* We need to specify a max date, because we need to stop *somewhere*
@@ -44,6 +50,16 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
public $calendarTableName = 'calendars';
/**
+ * The table name that will be used for calendars instances.
+ *
+ * A single calendar can have multiple instances, if the calendar is
+ * shared.
+ *
+ * @var string
+ */
+ public $calendarInstancesTableName = 'calendarinstances';
+
+ /**
* The table name that will be used for calendar objects
*
* @var string
@@ -140,16 +156,23 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
function getCalendarsForUser($principalUri) {
$fields = array_values($this->propertyMap);
- $fields[] = 'id';
+ $fields[] = 'calendarid';
$fields[] = 'uri';
$fields[] = 'synctoken';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
+ $fields[] = 'access';
// Making fields a comma-delimited list
$fields = implode(', ', $fields);
- $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
+ $stmt = $this->pdo->prepare(<<<SQL
+SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
+ LEFT JOIN {$this->calendarTableName} ON
+ {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
+WHERE principaluri = ? ORDER BY calendarorder ASC
+SQL
+ );
$stmt->execute([$principalUri]);
$calendars = [];
@@ -161,15 +184,27 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
$calendar = [
- 'id' => $row['id'],
+ 'id' => [(int)$row['calendarid'], (int)$row['id']],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
'{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
'{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
+ 'share-resource-uri' => '/ns/share/' . $row['calendarid'],
];
+ $calendar['share-access'] = (int)$row['access'];
+ // 1 = owner, 2 = readonly, 3 = readwrite
+ if ($row['access'] > 1) {
+ // We need to find more information about the original owner.
+ //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
+ //$stmt2->execute([$row['id']]);
+
+ // read-only is for backwards compatbility. Might go away in
+ // the future.
+ $calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
+ }
foreach ($this->propertyMap as $xmlName => $dbName) {
$calendar[$xmlName] = $row[$dbName];
@@ -199,31 +234,38 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$fieldNames = [
'principaluri',
'uri',
- 'synctoken',
'transparent',
+ 'calendarid',
];
$values = [
':principaluri' => $principalUri,
':uri' => $calendarUri,
- ':synctoken' => 1,
':transparent' => 0,
];
- // Default value
+
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
- $fieldNames[] = 'components';
if (!isset($properties[$sccs])) {
- $values[':components'] = 'VEVENT,VTODO';
+ // Default value
+ $components = 'VEVENT,VTODO';
} else {
if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
}
- $values[':components'] = implode(',', $properties[$sccs]->getValue());
+ $components = implode(',', $properties[$sccs]->getValue());
}
$transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
if (isset($properties[$transp])) {
- $values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
+ $values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
}
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)");
+ $stmt->execute([$components]);
+
+ $calendarId = $this->pdo->lastInsertId(
+ $this->calendarTableName . '_id_seq'
+ );
+
+ $values[':calendarid'] = $calendarId;
foreach ($this->propertyMap as $xmlName => $dbName) {
if (isset($properties[$xmlName])) {
@@ -233,10 +275,14 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
}
- $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
+
$stmt->execute($values);
- return $this->pdo->lastInsertId();
+ return [
+ $calendarId,
+ $this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq')
+ ];
}
@@ -252,16 +298,21 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* Read the PropPatch documenation for more info and examples.
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$supportedProperties = array_keys($this->propertyMap);
$supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
- $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
+ $propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) {
$newValues = [];
foreach ($mutations as $propertyName => $propertyValue) {
@@ -282,8 +333,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$valuesSql[] = $fieldName . ' = ?';
}
- $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
- $newValues['id'] = $calendarId;
+ $stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
+ $newValues['id'] = $instanceId;
$stmt->execute(array_values($newValues));
$this->addChange($calendarId, "", 2);
@@ -297,19 +348,49 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
/**
* Delete a calendar and all it's objects
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @return void
*/
function deleteCalendar($calendarId) {
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
- $stmt->execute([$calendarId]);
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
- $stmt->execute([$calendarId]);
+ $stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?');
+ $stmt->execute([$instanceId]);
+ $access = (int)$stmt->fetchColumn();
+
+ if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
+
+ /**
+ * If the user is the owner of the calendar, we delete all data and all
+ * instances.
+ **/
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
+ $stmt->execute([$calendarId]);
+
+ } else {
+
+ /**
+ * If it was an instance of a shared calendar, we only delete that
+ * instance.
+ */
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
+ $stmt->execute([$instanceId]);
+
+ }
- $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
- $stmt->execute([$calendarId]);
}
@@ -341,11 +422,16 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
* 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
+ * @param mixed $calendarId
* @return array
*/
function getCalendarObjects($calendarId) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
$stmt->execute([$calendarId]);
@@ -354,9 +440,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$result[] = [
'id' => $row['id'],
'uri' => $row['uri'],
- 'lastmodified' => $row['lastmodified'],
+ 'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
- 'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'component' => strtolower($row['componenttype']),
];
@@ -378,12 +463,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* This method must return null if the object did not exist.
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @param string $objectUri
* @return array|null
*/
function getCalendarObject($calendarId, $objectUri) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
@@ -393,9 +483,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
return [
'id' => $row['id'],
'uri' => $row['uri'],
- 'lastmodified' => $row['lastmodified'],
+ 'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
- 'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
@@ -417,6 +506,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function getMultipleCalendarObjects($calendarId, array $uris) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
// Inserting a whole bunch of question marks
$query .= implode(',', array_fill(0, count($uris), '?'));
@@ -431,9 +525,8 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$result[] = [
'id' => $row['id'],
'uri' => $row['uri'],
- 'lastmodified' => $row['lastmodified'],
+ 'lastmodified' => (int)$row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
- 'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
'component' => strtolower($row['componenttype']),
@@ -465,6 +558,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function createCalendarObject($calendarId, $objectUri, $calendarData) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
@@ -506,6 +604,11 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*/
function updateCalendarObject($calendarId, $objectUri, $calendarData) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
@@ -583,6 +686,10 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
}
}
+
+ // Ensure Occurence values are positive
+ if ($firstOccurence < 0) $firstOccurence = 0;
+ if ($lastOccurence < 0) $lastOccurence = 0;
}
// Destroy circular references to PHP will GC the object.
@@ -604,12 +711,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
*
* The object uri is only the basename, or filename and not a full path.
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @param string $objectUri
* @return void
*/
function deleteCalendarObject($calendarId, $objectUri) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
$stmt->execute([$calendarId, $objectUri]);
@@ -665,12 +777,17 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
* This specific implementation (for the PDO) backend optimizes filters on
* specific components, and VEVENT time-ranges.
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @param array $filters
* @return array
*/
function calendarQuery($calendarId, array $filters) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
$componentType = null;
$requirePostFilter = true;
$timeRange = null;
@@ -766,14 +883,14 @@ class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, S
$query = <<<SQL
SELECT
- calendars.uri AS calendaruri, calendarobjects.uri as objecturi
+ calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
FROM
$this->calendarObjectTableName AS calendarobjects
LEFT JOIN
- $this->calendarTableName AS calendars
- ON calendarobjects.calendarid = calendars.id
+ $this->calendarInstancesTableName AS calendar_instances
+ ON calendarobjects.calendarid = calendar_instances.calendarid
WHERE
- calendars.principaluri = ?
+ calendar_instances.principaluri = ?
AND
calendarobjects.uid = ?
SQL;
@@ -837,7 +954,7 @@ SQL;
*
* The limit is 'suggestive'. You are free to ignore it.
*
- * @param string $calendarId
+ * @param mixed $calendarId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
@@ -845,6 +962,11 @@ SQL;
*/
function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
// Current synctoken
$stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
$stmt->execute([ $calendarId ]);
@@ -1043,7 +1165,9 @@ SQL;
$stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
$stmt->execute($values);
- return $this->pdo->lastInsertId();
+ return $this->pdo->lastInsertId(
+ $this->calendarSubscriptionsTableName . '_id_seq'
+ );
}
@@ -1207,4 +1331,179 @@ SQL;
}
+ /**
+ * Updates the list of shares.
+ *
+ * @param mixed $calendarId
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
+ * @return void
+ */
+ function updateInvites($calendarId, array $sharees) {
+
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
+ }
+ $currentInvites = $this->getInvites($calendarId);
+ list($calendarId, $instanceId) = $calendarId;
+
+ $removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)");
+ $updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?");
+
+ $insertStmt = $this->pdo->prepare('
+INSERT INTO ' . $this->calendarInstancesTableName . '
+ (
+ calendarid,
+ principaluri,
+ access,
+ displayname,
+ uri,
+ description,
+ calendarorder,
+ calendarcolor,
+ timezone,
+ transparent,
+ share_href,
+ share_displayname,
+ share_invitestatus
+ )
+ SELECT
+ ?,
+ ?,
+ ?,
+ displayname,
+ ?,
+ description,
+ calendarorder,
+ calendarcolor,
+ timezone,
+ 1,
+ ?,
+ ?,
+ ?
+ FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
+
+ foreach ($sharees as $sharee) {
+
+ if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) {
+ // if access was set no NOACCESS, it means access for an
+ // existing sharee was removed.
+ $removeStmt->execute([$calendarId, $sharee->href]);
+ continue;
+ }
+
+ if (is_null($sharee->principal)) {
+ // If the server could not determine the principal automatically,
+ // we will mark the invite status as invalid.
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
+ } else {
+ // Because sabre/dav does not yet have an invitation system,
+ // every invite is automatically accepted for now.
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
+ }
+
+ foreach ($currentInvites as $oldSharee) {
+
+ if ($oldSharee->href === $sharee->href) {
+ // This is an update
+ $sharee->properties = array_merge(
+ $oldSharee->properties,
+ $sharee->properties
+ );
+ $updateStmt->execute([
+ $sharee->access,
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
+ $sharee->inviteStatus ?: $oldSharee->inviteStatus,
+ $calendarId,
+ $sharee->href
+ ]);
+ continue 2;
+ }
+
+ }
+ // If we got here, it means it was a new sharee
+ $insertStmt->execute([
+ $calendarId,
+ $sharee->principal,
+ $sharee->access,
+ \Sabre\DAV\UUIDUtil::getUUID(),
+ $sharee->href,
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
+ $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
+ $instanceId
+ ]);
+
+ }
+
+ }
+
+ /**
+ * Returns the list of people whom a 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[]
+ */
+ function getInvites($calendarId) {
+
+ if (!is_array($calendarId)) {
+ throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
+ }
+ list($calendarId, $instanceId) = $calendarId;
+
+ $query = <<<SQL
+SELECT
+ principaluri,
+ access,
+ share_href,
+ share_displayname,
+ share_invitestatus
+FROM {$this->calendarInstancesTableName}
+WHERE
+ calendarid = ?
+SQL;
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$calendarId]);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $result[] = new Sharee([
+ 'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
+ 'access' => (int)$row['access'],
+ /// Everyone is always immediately accepted, for now.
+ 'inviteStatus' => (int)$row['share_invitestatus'],
+ 'properties' =>
+ !empty($row['share_displayname'])
+ ? [ '{DAV:}displayname' => $row['share_displayname'] ]
+ : [],
+ 'principal' => $row['principaluri'],
+ ]);
+
+ }
+ return $result;
+
+ }
+
+ /**
+ * Publishes a calendar
+ *
+ * @param mixed $calendarId
+ * @param bool $value
+ * @return void
+ */
+ function setPublishStatus($calendarId, $value) {
+
+ throw new \Exception('Not implemented');
+
+ }
+
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
index 6a11b0ab1..8b6e074e0 100644
--- a/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
@@ -5,231 +5,48 @@ namespace Sabre\CalDAV\Backend;
/**
* Adds support for sharing features to a CalDAV server.
*
- * Note: This feature is experimental, and may change in between different
- * SabreDAV versions.
+ * CalDAV backends that implement this interface, must make the following
+ * modifications to getCalendarsForUser:
*
- * Early warning: Currently SabreDAV provides no implementation for this. This
- * is, because in it's current state there is no elegant way to do this.
- * The problem lies in the fact that a real CalDAV server with sharing support
- * would first need email support (with invite notifications), and really also
- * a browser-frontend that allows people to accept or reject these shares.
- *
- * In addition, the CalDAV backends are currently kept as independent as
- * possible, and should not be aware of principals, email addresses or
- * accounts.
- *
- * Adding an implementation for Sharing to standard-sabredav would contradict
- * these goals, so for this reason this is currently not implemented, although
- * it may very well in the future; but probably not before SabreDAV 2.0.
- *
- * The interface works however, so if you implement all this, and do it
- * correctly sharing _will_ work. It's not particularly easy, and I _urge you_
- * to make yourself acquainted with the following document first:
- *
- * https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
- *
- * An overview
- * ===========
- *
- * Implementing this interface will allow a user to share his or her calendars
- * to other users. Effectively, when a calendar is shared the calendar will
- * show up in both the Sharer's and Sharee's calendar-home root.
- * This interface adds a few methods that ensure that this happens, and there
- * are also a number of new requirements in the base-class you must now follow.
- *
- *
- * How it works
- * ============
- *
- * When a user shares a calendar, the updateShares() method will be called with
- * a list of sharees that are now added, and a list of sharees that have been
- * removed.
- * Removal is instant, but when a sharee is added the sharee first gets a
- * chance to accept or reject the invitation for a share.
- *
- * After a share is accepted, the calendar will be returned from
- * getUserCalendars for both the sharer, and the sharee.
- *
- * If the sharee deletes the calendar, only their share gets deleted. When the
- * owner deletes a calendar, it will be removed for everybody.
- *
- *
- * Notifications
- * =============
- *
- * During all these sharing operations, a lot of notifications are sent back
- * and forward.
- *
- * Whenever the list of sharees for a calendar has been changed (they have been
- * added, removed or modified) all sharees should get a notification for this
- * change.
- * This notification is always represented by:
- *
- * Sabre\CalDAV\Notifications\Notification\Invite
- *
- * In the case of an invite, the sharee may reply with an 'accept' or
- * 'decline'. These are always represented by:
- *
- * Sabre\CalDAV\Notifications\Notification\InviteReply
- *
- *
- * Calendar access by sharees
- * ==========================
- *
- * As mentioned earlier, shared calendars must now also be returned for
- * getCalendarsForUser for sharees. A few things change though.
- *
- * The following properties must be specified:
- *
- * 1. {http://calendarserver.org/ns/}shared-url
- *
- * This property MUST contain the url to the original calendar, that is.. the
- * path to the calendar from the owner.
- *
- * 2. {http://sabredav.org/ns}owner-principal
- *
- * This is a url to to the principal who is sharing the calendar.
- *
- * 3. {http://sabredav.org/ns}read-only
- *
- * This should be either 0 or 1, depending on if the user has read-only or
- * read-write access to the calendar.
- *
- * Only when this is done, the calendar will correctly be marked as a calendar
- * that's shared to him, thus allowing clients to display the correct interface
- * and ACL enforcement.
- *
- * If a sharee deletes their calendar, only their instance of the calendar
- * should be deleted, the original should still exists.
- * Pretty much any 'dead' WebDAV properties on these shared calendars should be
- * specific to a user. This means that if the displayname is changed by a
- * sharee, the original is not affected. This is also true for:
- * * The description
- * * The color
- * * The order
- * * And any other dead properties.
- *
- * Properties like a ctag should not be different for multiple instances of the
- * calendar.
- *
- * Lastly, objects *within* calendars should also have user-specific data. The
- * two things that are user-specific are:
- * * VALARM objects
- * * The TRANSP property
- *
- * This _also_ implies that if a VALARM is deleted by a sharee for some event,
- * this has no effect on the original VALARM.
- *
- * Understandably, the this last requirement is one of the hardest.
- * Realisticly, I can see people ignoring this part of the spec, but that could
- * cause a different set of issues.
- *
- *
- * Publishing
- * ==========
- *
- * When a user publishes a url, the server should generate a 'publish url'.
- * This is a read-only url, anybody can use to consume the calendar feed.
- *
- * Calendars are in one of two states:
- * * published
- * * unpublished
- *
- * If a calendar is published, the following property should be returned
- * for each calendar in getCalendarsForUser.
- *
- * {http://calendarserver.org/ns/}publish-url
- *
- * This element should contain a {DAV:}href element, which points to the
- * public url that does not require authentication. Unlike every other href,
- * this url must be absolute.
- *
- * Ideally, the following property is always returned
- *
- * {http://calendarserver.org/ns/}pre-publish-url
- *
- * This property should contain the url that the calendar _would_ have, if it
- * were to be published. iCal uses this to display the url, before the user
- * will actually publish it.
- *
- *
- * Selectively disabling publish or share feature
- * ==============================================
- *
- * If Sabre\CalDAV\Property\AllowedSharingModes is returned from
- * getCalendarsForUser, this allows the server to specify whether either sharing,
- * or publishing is supported.
- *
- * This allows a client to determine in advance which features are available,
- * and update the interface appropriately. If this property is not returned by
- * the backend, the SharingPlugin automatically injects it and assumes both
- * features are available.
+ * 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 calenar.
+ * 3. For every calenar, you must return a share-access element. This element
+ * should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* contants 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 NotificationSupport {
+interface SharingSupport extends BackendInterface {
/**
* Updates the list of shares.
*
- * The first array is a list of people that are to be added to the
- * calendar.
- *
- * Every element in the add array has the following properties:
- * * href - A url. Usually a mailto: address
- * * commonName - Usually a first and last name, or false
- * * summary - A description of the share, can also be false
- * * readOnly - A boolean value
- *
- * Every element in the remove array is just the address string.
- *
- * Note that if the calendar is currently marked as 'not shared' by and
- * this method is called, the calendar should be 'upgraded' to a shared
- * calendar.
- *
* @param mixed $calendarId
- * @param array $add
- * @param array $remove
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
* @return void
*/
- function updateShares($calendarId, array $add, array $remove);
+ function updateInvites($calendarId, array $sharees);
/**
* Returns the list of people whom this calendar is shared with.
*
- * Every element in this array should have the following properties:
- * * href - Often a mailto: address
- * * commonName - Optional, for example a first + last name
- * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
- * * readOnly - boolean
- * * summary - Optional, a description for the share
+ * Every item in the returned list must be a Sharee object with at
+ * least the following properties set:
+ * $href
+ * $shareAccess
+ * $inviteStatus
*
- * This method may be called by either the original instance of the
- * calendar, as well as the shared instances. In the case of the shared
- * instances, it is perfectly acceptable to return an empty array in case
- * there are privacy concerns.
+ * and optionally:
+ * $properties
*
* @param mixed $calendarId
- * @return array
- */
- function getShares($calendarId);
-
- /**
- * This method is called when a user replied to a request to share.
- *
- * If the user chose to accept the share, this method should return the
- * newly created calendar url.
- *
- * @param string href The sharee who is replying (often a mailto: address)
- * @param int status One of the SharingPlugin::STATUS_* constants
- * @param string $calendarUri The url to the calendar thats being shared
- * @param string $inReplyTo The unique id this message is a response to
- * @param string $summary A description of the reply
- * @return null|string
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
*/
- function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
+ function getInvites($calendarId);
/**
* Publishes a calendar
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php b/vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php
new file mode 100644
index 000000000..f8238ea9a
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php
@@ -0,0 +1,296 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+use Sabre\CalDAV;
+use Sabre\DAV;
+
+/**
+ * Simple PDO CalDAV backend.
+ *
+ * This class is basically the most minmum 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
+ *
+ * @param \PDO $pdo
+ */
+ 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
+ */
+ 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
+ * @param array $properties
+ * @return string
+ */
+ 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
+ * @return void
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Calendar.php b/vendor/sabre/dav/lib/CalDAV/Calendar.php
index ff8e19b15..90ace0d21 100644
--- a/vendor/sabre/dav/lib/CalDAV/Calendar.php
+++ b/vendor/sabre/dav/lib/CalDAV/Calendar.php
@@ -18,6 +18,8 @@ use Sabre\DAV\PropPatch;
*/
class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {
+ use DAVACL\ACLTrait;
+
/**
* This is an array with calendar information
*
@@ -86,7 +88,7 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
foreach ($this->calendarInfo as $propName => $propValue) {
- if ($propName[0] === '{')
+ if (!is_null($propValue) && $propName[0] === '{')
$response[$propName] = $this->calendarInfo[$propName];
}
@@ -227,7 +229,7 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
/**
* Returns the last modification date as a unix timestamp.
*
- * @return void
+ * @return null
*/
function getLastModified() {
@@ -249,19 +251,6 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -360,50 +349,6 @@ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection,
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
-
- // We need to inject 'read-free-busy' in the tree, aggregated under
- // {DAV:}read.
- foreach ($default['aggregates'] as &$agg) {
-
- if ($agg['privilege'] !== '{DAV:}read') continue;
-
- $agg['aggregates'][] = [
- 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
- ];
-
- }
- return $default;
-
- }
/**
* Performs a calendar-query on the contents of this calendar.
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
index a53f829e2..0a4bfb68f 100644
--- a/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
@@ -22,6 +22,8 @@ use Sabre\HTTP\URLUtil;
*/
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
+ use DAVACL\ACLTrait;
+
/**
* CalDAV backend
*
@@ -147,11 +149,7 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
if ($calendar['uri'] === $name) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
- if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
- return new SharedCalendar($this->caldavBackend, $calendar);
- } else {
- return new ShareableCalendar($this->caldavBackend, $calendar);
- }
+ return new SharedCalendar($this->caldavBackend, $calendar);
} else {
return new Calendar($this->caldavBackend, $calendar);
}
@@ -198,11 +196,7 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
$objs = [];
foreach ($calendars as $calendar) {
if ($this->caldavBackend instanceof Backend\SharingSupport) {
- if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
- $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
- } else {
- $objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
- }
+ $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
} else {
$objs[] = new Calendar($this->caldavBackend, $calendar);
}
@@ -278,11 +272,9 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
- * Returns the owner principal
+ * Returns the owner of the calendar home.
*
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
+ * @return string
*/
function getOwner() {
@@ -291,19 +283,6 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -348,37 +327,6 @@ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
/**
* This method is called when a user replied to a request to share.
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
index 393ca4cbd..001b35112 100644
--- a/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
@@ -11,6 +11,8 @@ namespace Sabre\CalDAV;
*/
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {
+ use \Sabre\DAVACL\ACLTrait;
+
/**
* Sabre\CalDAV\Backend\BackendInterface
*
@@ -192,19 +194,6 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -226,22 +215,12 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
// The default ACL
return [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
],
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
@@ -255,36 +234,4 @@ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
index 8c296d50f..a3a824c71 100644
--- a/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
+++ b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
@@ -170,13 +170,13 @@ class ICSExportPlugin extends DAV\ServerPlugin {
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.
- $calendarNode = $this->server->tree->getNodeForPath($path);
$queryResult = $calendarNode->calendarQuery([
'name' => 'VCALENDAR',
'comp-filters' => [
@@ -246,17 +246,29 @@ class ICSExportPlugin extends DAV\ServerPlugin {
$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
}
- $response->setHeader('Content-Type', $format);
+ $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);
@@ -272,11 +284,11 @@ class ICSExportPlugin extends DAV\ServerPlugin {
function mergeObjects(array $properties, array $inputObjects) {
$calendar = new VObject\Component\VCalendar();
- $calendar->version = '2.0';
+ $calendar->VERSION = '2.0';
if (DAV\Server::$exposeVersion) {
- $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
+ $calendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
} else {
- $calendar->prodid = '-//SabreDAV//SabreDAV//EN';
+ $calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
}
if (isset($properties['{DAV:}displayname'])) {
$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
diff --git a/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php b/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php
index cfda91a55..7420d94f7 100644
--- a/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php
+++ b/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php
@@ -5,7 +5,7 @@ namespace Sabre\CalDAV;
/**
* This interface represents a Calendar that can be shared with other users.
*
- * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
diff --git a/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php b/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
index 84442ac21..15f3b5335 100644
--- a/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
+++ b/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
@@ -2,6 +2,8 @@
namespace Sabre\CalDAV;
+use Sabre\DAV\Sharing\ISharedNode;
+
/**
* This interface represents a Calendar that is shared by a different user.
*
@@ -9,28 +11,16 @@ namespace Sabre\CalDAV;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-interface ISharedCalendar extends ICalendar {
-
- /**
- * This method should return the url of the owners' copy of the shared
- * calendar.
- *
- * @return string
- */
- function getSharedUrl();
+interface ISharedCalendar extends ISharedNode {
/**
- * Returns the list of people whom this calendar is shared with.
+ * Marks this calendar as published.
*
- * Every element in this array should have the following properties:
- * * href - Often a mailto: address
- * * commonName - Optional, for example a first + last name
- * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
- * * readOnly - boolean
- * * summary - Optional, a description for the share
+ * Publishing a calendar should automatically create a read-only, public,
+ * subscribable calendar.
*
- * @return array
+ * @param bool $value
+ * @return void
*/
- function getShares();
-
+ function setPublishStatus($value);
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
index 1fcc1171c..5fda61dfa 100644
--- a/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
@@ -22,6 +22,8 @@ use Sabre\DAVACL;
*/
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
+ use DAVACL\ACLTrait;
+
/**
* The notification backend
*
@@ -96,78 +98,4 @@ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
- * Returns a list of ACE's for this node.
- *
- * Each ACE has the following properties:
- * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
- * currently the only supported privileges
- * * 'principal', a url to the principal who owns the node
- * * 'protected' (optional), indicating that this ACE is not allowed to
- * be updated.
- *
- * @return array
- */
- function getACL() {
-
- return [
- [
- 'principal' => $this->getOwner(),
- 'privilege' => '{DAV:}read',
- 'protected' => true,
- ],
- [
- 'principal' => $this->getOwner(),
- 'privilege' => '{DAV:}write',
- 'protected' => true,
- ]
- ];
-
- }
-
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
index 47e78d5de..11df0c94b 100644
--- a/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
@@ -20,6 +20,8 @@ use Sabre\DAVACL;
*/
class Node extends DAV\File implements INode, DAVACL\IACL {
+ use DAVACL\ACLTrait;
+
/**
* The notification backend
*
@@ -116,78 +118,4 @@ class Node extends DAV\File implements INode, DAVACL\IACL {
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
- * Returns a list of ACE's for this node.
- *
- * Each ACE has the following properties:
- * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
- * currently the only supported privileges
- * * 'principal', a url to the principal who owns the node
- * * 'protected' (optional), indicating that this ACE is not allowed to
- * be updated.
- *
- * @return array
- */
- function getACL() {
-
- return [
- [
- 'principal' => $this->getOwner(),
- 'privilege' => '{DAV:}read',
- 'protected' => true,
- ],
- [
- 'principal' => $this->getOwner(),
- 'privilege' => '{DAV:}write',
- 'protected' => true,
- ]
- ];
-
- }
-
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Plugin.php
index 663490023..71ba75206 100644
--- a/vendor/sabre/dav/lib/CalDAV/Plugin.php
+++ b/vendor/sabre/dav/lib/CalDAV/Plugin.php
@@ -5,8 +5,9 @@ namespace Sabre\CalDAV;
use DateTimeZone;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
-use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\VObject;
use Sabre\HTTP;
@@ -186,6 +187,7 @@ class Plugin extends DAV\ServerPlugin {
$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
$server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
$server->on('afterMethod:GET', [$this, 'httpAfterGET']);
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
$server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
$server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
@@ -233,9 +235,10 @@ class Plugin extends DAV\ServerPlugin {
*
* @param string $reportName
* @param mixed $report
+ * @param mixed $path
* @return bool
*/
- function report($reportName, $report) {
+ function report($reportName, $report, $path) {
switch ($reportName) {
case '{' . self::NS_CALDAV . '}calendar-multiget' :
@@ -341,7 +344,7 @@ class Plugin extends DAV\ServerPlugin {
$calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
if (is_null($calendarHomePath)) return null;
- return new Href($calendarHomePath . '/');
+ return new LocalHref($calendarHomePath . '/');
});
// The calendar-user-address-set property is basically mapped to
@@ -349,7 +352,7 @@ class Plugin extends DAV\ServerPlugin {
$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
$addresses = $node->getAlternateUriSet();
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
- return new Href($addresses, false);
+ return new LocalHref($addresses);
});
// For some reason somebody thought it was a good idea to add
// another one of these properties. We're supporting it too.
@@ -394,8 +397,8 @@ class Plugin extends DAV\ServerPlugin {
}
- $propFind->set($propRead, new Href($readList));
- $propFind->set($propWrite, new Href($writeList));
+ $propFind->set($propRead, new LocalHref($readList));
+ $propFind->set($propWrite, new LocalHref($writeList));
}
@@ -821,11 +824,7 @@ class Plugin extends DAV\ServerPlugin {
$data = stream_get_contents($data);
}
- $before = md5($data);
- // Converting the data to unicode, if needed.
- $data = DAV\StringUtil::ensureUTF8($data);
-
- if ($before !== md5($data)) $modified = true;
+ $before = $data;
try {
@@ -865,7 +864,7 @@ class Plugin extends DAV\ServerPlugin {
}
$foundType = null;
- $foundUID = null;
+
foreach ($vobj->getComponents() as $component) {
switch ($component->name) {
case 'VTIMEZONE' :
@@ -873,31 +872,59 @@ class Plugin extends DAV\ServerPlugin {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
- if (is_null($foundType)) {
- $foundType = $component->name;
- if (!in_array($foundType, $supportedComponents)) {
- throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
- }
- if (!isset($component->UID)) {
- throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
- }
- $foundUID = (string)$component->UID;
- } else {
- if ($foundType !== $component->name) {
- throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
- }
- if ($foundUID !== (string)$component->UID) {
- throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
- }
- }
+ $foundType = $component->name;
break;
- default :
- throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
+ }
+
+ }
+
+ if (!$foundType || !in_array($foundType, $supportedComponents)) {
+ throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
+ }
+
+ $options = VObject\Node::PROFILE_CALDAV;
+ $prefer = $this->server->getHTTPPrefer();
+
+ if ($prefer['handling'] !== 'strict') {
+ $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 iCalendar: ' . $message['message']);
+
+ }
+
+ }
+ if ($warningMessage) {
+ $response->setHeader(
+ 'X-Sabre-Ew-Gross',
+ 'iCalendar validation warning: ' . $warningMessage
+ );
}
- if (!$foundType)
- throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
// We use an extra variable to allow event handles to tell us wether
// the object was modified or not.
@@ -917,12 +944,12 @@ class Plugin extends DAV\ServerPlugin {
]
);
- if ($subModified) {
+ if ($modified || $subModified) {
// An event handler told us that it modified the object.
$data = $vobj->serialize();
// Using md5 to figure out if there was an *actual* change.
- if (!$modified && $before !== md5($data)) {
+ if (!$modified && strcmp($data, $before) !== 0) {
$modified = true;
}
@@ -933,6 +960,22 @@ class Plugin extends DAV\ServerPlugin {
}
+ /**
+ * This method is triggered whenever a subsystem reqeuests the privileges
+ * that are supported on a particular node.
+ *
+ * @param INode $node
+ * @param array $supportedPrivilegeSet
+ */
+ function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
+
+ if ($node instanceof ICalendar) {
+ $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ }
/**
* This method is used to generate HTML output for the
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
index 13212565e..6b374ea3f 100644
--- a/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
@@ -17,6 +17,8 @@ use Sabre\VObject;
*/
class Inbox extends DAV\Collection implements IInbox {
+ use DAVACL\ACLTrait;
+
/**
* CalDAV backend
*
@@ -119,19 +121,6 @@ class Inbox extends DAV\Collection implements IInbox {
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -167,58 +156,11 @@ class Inbox extends DAV\Collection implements IInbox {
'protected' => true,
],
[
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite',
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
- [
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply',
- 'principal' => '{DAV:}authenticated',
- 'protected' => true,
- ],
- ];
-
- }
-
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- $ns = '{' . CalDAV\Plugin::NS_CALDAV . '}';
-
- $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
- $default['aggregates'][] = [
- 'privilege' => $ns . 'schedule-deliver',
- 'aggregates' => [
- ['privilege' => $ns . 'schedule-deliver-invite'],
- ['privilege' => $ns . 'schedule-deliver-reply'],
- ],
];
- return $default;
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
index dabaee2ca..29eefa744 100644
--- a/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
@@ -19,6 +19,8 @@ use Sabre\DAVACL;
*/
class Outbox extends DAV\Collection implements IOutbox {
+ use DAVACL\ACLTrait;
+
/**
* The principal Uri
*
@@ -75,19 +77,6 @@ class Outbox extends DAV\Collection implements IOutbox {
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -103,12 +92,7 @@ class Outbox extends DAV\Collection implements IOutbox {
return [
[
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
- 'principal' => $this->getOwner(),
- 'protected' => true,
- ],
- [
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
'principal' => $this->getOwner(),
'protected' => true,
],
@@ -118,12 +102,7 @@ class Outbox extends DAV\Collection implements IOutbox {
'protected' => true,
],
[
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
- 'principal' => $this->getOwner() . '/calendar-proxy-write',
- 'protected' => true,
- ],
- [
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
@@ -141,44 +120,4 @@ class Outbox extends DAV\Collection implements IOutbox {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
- $default['aggregates'][] = [
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
- ];
- $default['aggregates'][] = [
- 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
- ];
-
- return $default;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
index 827d6209b..47511140f 100644
--- a/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
@@ -5,10 +5,12 @@ namespace Sabre\CalDAV\Schedule;
use DateTimeZone;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Sharing;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\INode;
use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;
@@ -100,12 +102,13 @@ class Plugin extends ServerPlugin {
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('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 . '}';
@@ -215,7 +218,7 @@ class Plugin extends ServerPlugin {
}
$outboxPath = $calendarHomePath . '/outbox/';
- return new Href($outboxPath);
+ return new LocalHref($outboxPath);
});
// schedule-inbox-URL property
@@ -227,7 +230,7 @@ class Plugin extends ServerPlugin {
}
$inboxPath = $calendarHomePath . '/inbox/';
- return new Href($inboxPath);
+ return new LocalHref($inboxPath);
});
@@ -245,18 +248,28 @@ class Plugin extends ServerPlugin {
$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') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) {
- // Node is either not a calendar or a shared instance.
+ 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 ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) {
+ // 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 Href($child['href']);
+ return new LocalHref($child['href']);
}
}
@@ -492,7 +505,7 @@ class Plugin extends ServerPlugin {
}
if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
- $iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox';
+ $iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.';
return;
}
@@ -561,6 +574,65 @@ class Plugin extends ServerPlugin {
}
/**
+ * 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.
+ *
+ * @param INode $node
+ * @param array $supportedPrivilegeSet
+ */
+ 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.
*
@@ -647,7 +719,7 @@ class Plugin extends ServerPlugin {
/**
* This method handles POST requests to the schedule-outbox.
*
- * Currently, two types of requests are support:
+ * Currently, two types of requests are supported:
* * FREEBUSY requests from RFC 6638
* * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
*
@@ -699,7 +771,7 @@ class Plugin extends ServerPlugin {
if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
- $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
+ $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.
@@ -727,7 +799,7 @@ class Plugin extends ServerPlugin {
protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
$vFreeBusy = $vObject->VFREEBUSY;
- $organizer = $vFreeBusy->organizer;
+ $organizer = $vFreeBusy->ORGANIZER;
$organizer = (string)$organizer;
@@ -863,6 +935,9 @@ class Plugin extends ServerPlugin {
$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');
@@ -882,8 +957,6 @@ class Plugin extends ServerPlugin {
continue;
}
- $aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
-
if (isset($props[$ctz])) {
$vtimezoneObj = VObject\Reader::read($props[$ctz]);
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
index a36646e6c..6d9d3d5ec 100644
--- a/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
@@ -134,22 +134,12 @@ class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements IScheduli
// The default ACL
return [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->objectData['principaluri'],
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
- 'principal' => $this->objectData['principaluri'],
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}read',
- 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
+ 'privilege' => '{DAV:}all',
+ 'principal' => '{DAV:}owner',
'protected' => true,
],
[
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
'protected' => true,
],
diff --git a/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php
index c11695d5f..c81c96300 100644
--- a/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php
+++ b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php
@@ -6,7 +6,7 @@ namespace Sabre\CalDAV;
* This object represents a CalDAV calendar that can be shared with other
* users.
*
- * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
diff --git a/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
index 7973eff9c..7a77616e3 100644
--- a/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
+++ b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
@@ -2,6 +2,8 @@
namespace Sabre\CalDAV;
+use Sabre\DAV\Sharing\Plugin as SPlugin;
+
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
@@ -12,50 +14,84 @@ namespace Sabre\CalDAV;
class SharedCalendar extends Calendar implements ISharedCalendar {
/**
- * Constructor
+ * Returns the 'access level' for the instance of this shared resource.
+ *
+ * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
+ * constants.
*
- * @param Backend\BackendInterface $caldavBackend
- * @param array $calendarInfo
+ * @return int
*/
- function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
-
- $required = [
- '{http://calendarserver.org/ns/}shared-url',
- '{http://sabredav.org/ns}owner-principal',
- '{http://sabredav.org/ns}read-only',
- ];
- foreach ($required as $r) {
- if (!isset($calendarInfo[$r])) {
- throw new \InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)');
- }
- }
+ function getShareAccess() {
- parent::__construct($caldavBackend, $calendarInfo);
+ return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;
}
/**
- * This method should return the url of the owners' copy of the shared
- * calendar.
+ * 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
*/
- function getSharedUrl() {
+ 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
+ * @return void
+ */
+ function updateInvites(array $sharees) {
- return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
+ $this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees);
}
/**
- * Returns the owner principal
+ * 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
*
- * This must be a url to a principal, or null if there's no owner
+ * and optionally:
*
- * @return string|null
+ * * $properties
+ *
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
*/
- function getOwner() {
+ function getInvites() {
- return $this->calendarInfo['{http://sabredav.org/ns}owner-principal'];
+ 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
+ * @return void
+ */
+ function setPublishStatus($value) {
+
+ $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
}
@@ -73,32 +109,72 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*/
function getACL() {
- // The top-level ACL only contains access information for the true
- // owner of the calendar, so we need to add the information for the
- // sharee.
- $acl = parent::getACL();
- $acl[] = [
- 'privilege' => '{DAV:}read',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ];
- if ($this->calendarInfo['{http://sabredav.org/ns}read-only']) {
- $acl[] = [
- 'privilege' => '{DAV:}write-properties',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ];
- } else {
- $acl[] = [
- 'privilege' => '{DAV:}write',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ];
+ $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
@@ -108,40 +184,45 @@ class SharedCalendar extends Calendar implements ISharedCalendar {
*/
function getChildACL() {
- $acl = parent::getChildACL();
- $acl[] = [
- 'privilege' => '{DAV:}read',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ];
-
- if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
- $acl[] = [
- 'privilege' => '{DAV:}write',
- 'principal' => $this->calendarInfo['principaluri'],
- 'protected' => true,
- ];
+ $acl = [];
+
+ switch ($this->getShareAccess()) {
+ case SPlugin::ACCESS_NOTSHARED :
+ // No break intentional
+ case SPlugin::ACCESS_SHAREDOWNER :
+ // 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:}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;
-
- }
-
- /**
- * Returns the list of people whom this calendar is shared with.
- *
- * Every element in this array should have the following properties:
- * * href - Often a mailto: address
- * * commonName - Optional, for example a first + last name
- * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
- * * readOnly - boolean
- * * summary - Optional, a description for the share
- *
- * @return array
- */
- function getShares() {
-
- return $this->caldavBackend->getShares($this->calendarInfo['id']);
+ return $acl;
}
diff --git a/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
index 5154fb1de..6f7df02bc 100644
--- a/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
+++ b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
@@ -4,6 +4,7 @@ namespace Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
@@ -26,15 +27,6 @@ use Sabre\HTTP\ResponseInterface;
class SharingPlugin extends DAV\ServerPlugin {
/**
- * These are the various status constants used by sharing-messages.
- */
- const STATUS_ACCEPTED = 1;
- const STATUS_DECLINED = 2;
- const STATUS_DELETED = 3;
- const STATUS_NORESPONSE = 4;
- const STATUS_INVALID = 5;
-
- /**
* Reference to SabreDAV server object.
*
* @var Sabre\DAV\Server
@@ -83,7 +75,10 @@ class SharingPlugin extends DAV\ServerPlugin {
function initialize(DAV\Server $server) {
$this->server = $server;
- $server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
+
+ 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,
@@ -114,24 +109,8 @@ class SharingPlugin extends DAV\ServerPlugin {
*/
function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
- if ($node instanceof IShareableCalendar) {
-
- $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
- return new Xml\Property\Invite(
- $node->getShares()
- );
- });
-
- }
-
if ($node instanceof ISharedCalendar) {
- $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function() use ($node) {
- return new Href(
- $node->getSharedUrl()
- );
- });
-
$propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
// Fetching owner information
@@ -158,7 +137,7 @@ class SharingPlugin extends DAV\ServerPlugin {
}
return new Xml\Property\Invite(
- $node->getShares(),
+ $node->getInvites(),
$ownerInfo
);
@@ -179,10 +158,18 @@ class SharingPlugin extends DAV\ServerPlugin {
*/
function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
- if ($node instanceof IShareableCalendar) {
+ if ($node instanceof ISharedCalendar) {
+ $shareAccess = $node->getShareAccess();
if ($rt = $propFind->get('{DAV:}resourcetype')) {
- if (count($node->getShares()) > 0) {
- $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
+ 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() {
@@ -211,21 +198,24 @@ class SharingPlugin extends DAV\ServerPlugin {
function propPatch($path, DAV\PropPatch $propPatch) {
$node = $this->server->tree->getNodeForPath($path);
- if (!$node instanceof IShareableCalendar)
+ if (!$node instanceof ISharedCalendar)
return;
- $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
- if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
- $shares = $node->getShares();
- $remove = [];
- foreach ($shares as $share) {
- $remove[] = $share['href'];
- }
- $node->updateShares([], $remove);
+ if ($node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER || $node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED) {
- return true;
+ $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;
+
+ });
+
+ }
}
@@ -267,26 +257,12 @@ class SharingPlugin extends DAV\ServerPlugin {
switch ($documentType) {
- // Dealing with the 'share' document, which modified invitees on a
- // calendar.
+ // Both the DAV:share-resource and CALENDARSERVER:share requests
+ // behave identically.
case '{' . Plugin::NS_CALENDARSERVER . '}share' :
- // We can only deal with IShareableCalendar objects
- if (!$node instanceof IShareableCalendar) {
- return;
- }
-
- $this->server->transactionType = 'post-calendar-share';
-
- // Getting ACL info
- $acl = $this->server->getPlugin('acl');
-
- // If there's no ACL support, we allow everything
- if ($acl) {
- $acl->checkPrivileges($path, '{DAV:}write');
- }
-
- $node->updateShares($message->set, $message->remove);
+ $sharingPlugin = $this->server->getPlugin('sharing');
+ $sharingPlugin->shareResource($path, $message->sharees);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
@@ -328,11 +304,11 @@ class SharingPlugin extends DAV\ServerPlugin {
$response->setHeader('X-Sabre-Status', 'everything-went-well');
if ($url) {
- $writer = $this->server->xml->getWriter($this->server->getBaseUri());
+ $writer = $this->server->xml->getWriter();
$writer->openMemory();
$writer->startDocument();
$writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
- $writer->write(new Href($url));
+ $writer->write(new LocalHref($url));
$writer->endElement();
$response->setHeader('Content-Type', 'application/xml');
$response->setBody($writer->outputMemory());
@@ -345,7 +321,7 @@ class SharingPlugin extends DAV\ServerPlugin {
case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
// We can only deal with IShareableCalendar objects
- if (!$node instanceof IShareableCalendar) {
+ if (!$node instanceof ISharedCalendar) {
return;
}
$this->server->transactionType = 'post-publish-calendar';
@@ -355,7 +331,7 @@ class SharingPlugin extends DAV\ServerPlugin {
// If there's no ACL support, we allow everything
if ($acl) {
- $acl->checkPrivileges($path, '{DAV:}write');
+ $acl->checkPrivileges($path, '{DAV:}share');
}
$node->setPublishStatus(true);
@@ -373,7 +349,7 @@ class SharingPlugin extends DAV\ServerPlugin {
case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
- if (!$node instanceof IShareableCalendar) {
+ if (!$node instanceof ISharedCalendar) {
return;
}
$this->server->transactionType = 'post-unpublish-calendar';
@@ -383,7 +359,7 @@ class SharingPlugin extends DAV\ServerPlugin {
// If there's no ACL support, we allow everything
if ($acl) {
- $acl->checkPrivileges($path, '{DAV:}write');
+ $acl->checkPrivileges($path, '{DAV:}share');
}
$node->setPublishStatus(false);
diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
index ee53da2c6..3bb3451f3 100644
--- a/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
+++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
@@ -5,8 +5,8 @@ namespace Sabre\CalDAV\Subscriptions;
use Sabre\DAV\Collection;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\PropPatch;
-use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAVACL\IACL;
+use Sabre\DAVACL\ACLTrait;
use Sabre\CalDAV\Backend\SubscriptionSupport;
/**
@@ -20,6 +20,8 @@ use Sabre\CalDAV\Backend\SubscriptionSupport;
*/
class Subscription extends Collection implements ISubscription, IACL {
+ use ACLTrait;
+
/**
* caldavBackend
*
@@ -144,7 +146,7 @@ class Subscription extends Collection implements ISubscription, IACL {
* The Server class will filter out the extra.
*
* @param array $properties
- * @return void
+ * @return array
*/
function getProperties($properties) {
@@ -154,7 +156,7 @@ class Subscription extends Collection implements ISubscription, IACL {
switch ($prop) {
case '{http://calendarserver.org/ns/}source' :
- $r[$prop] = new Href($this->subscriptionInfo['source'], false);
+ $r[$prop] = new Href($this->subscriptionInfo['source']);
break;
default :
if (array_key_exists($prop, $this->subscriptionInfo)) {
@@ -183,19 +185,6 @@ class Subscription extends Collection implements ISubscription, IACL {
}
/**
- * Returns a group principal.
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -211,22 +200,12 @@ class Subscription extends Collection implements ISubscription, IACL {
return [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->getOwner(),
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->getOwner(),
'protected' => true,
],
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->getOwner() . '/calendar-proxy-write',
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
],
@@ -239,36 +218,4 @@ class Subscription extends Collection implements ISubscription, IACL {
}
- /**
- * Updates the ACL.
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
index 7fb022e33..1ca64f3e8 100644
--- a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
@@ -5,6 +5,7 @@ namespace Sabre\CalDAV\Xml\Notification;
use Sabre\Xml\Writer;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\CalDAV;
+use Sabre\DAV;
/**
* This class represents the cs:invite-notification notification element.
@@ -210,16 +211,10 @@ class Invite implements NotificationInterface {
switch ($this->type) {
- case SharingPlugin::STATUS_ACCEPTED :
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
- case SharingPlugin::STATUS_DECLINED :
- $writer->writeElement($cs . 'invite-declined');
- break;
- case SharingPlugin::STATUS_DELETED :
- $writer->writeElement($cs . 'invite-deleted');
- break;
- case SharingPlugin::STATUS_NORESPONSE :
+ case DAV\Sharing\Plugin::INVITE_NORESPONSE :
$writer->writeElement($cs . 'invite-noresponse');
break;
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
index 945323fed..51bfc178a 100644
--- a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
@@ -5,6 +5,7 @@ namespace Sabre\CalDAV\Xml\Notification;
use Sabre\Xml\Writer;
use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin;
+use Sabre\DAV;
/**
* This class represents the cs:invite-reply notification element.
@@ -162,10 +163,10 @@ class InviteReply implements NotificationInterface {
switch ($this->type) {
- case SharingPlugin::STATUS_ACCEPTED :
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
$writer->writeElement($cs . 'invite-accepted');
break;
- case SharingPlugin::STATUS_DECLINED :
+ case DAV\Sharing\Plugin::INVITE_DECLINED :
$writer->writeElement($cs . 'invite-declined');
break;
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
index 3ee053214..40ff6b936 100644
--- a/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
@@ -2,11 +2,10 @@
namespace Sabre\CalDAV\Xml\Property;
-use Sabre\Xml\Element;
-use Sabre\Xml\Reader;
+use Sabre\Xml\XmlSerializable;
use Sabre\Xml\Writer;
use Sabre\CalDAV\Plugin;
-use Sabre\CalDAV\SharingPlugin;
+use Sabre\DAV;
/**
* Invite property
@@ -20,53 +19,23 @@ use Sabre\CalDAV\SharingPlugin;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-class Invite implements Element {
+class Invite implements XmlSerializable {
/**
* The list of users a calendar has been shared to.
*
- * @var array
+ * @var Sharee[]
*/
- protected $users;
-
- /**
- * The organizer contains information about the person who shared the
- * object.
- *
- * @var array
- */
- protected $organizer;
+ protected $sharees;
/**
* Creates the property.
*
- * Users is an array. Each element of the array has the following
- * properties:
- *
- * * href - Often a mailto: address
- * * commonName - Optional, for example a first and lastname for a user.
- * * status - One of the SharingPlugin::STATUS_* constants.
- * * readOnly - true or false
- * * summary - Optional, description of the share
- *
- * The organizer key is optional to specify. It's only useful when a
- * 'sharee' requests the sharing information.
- *
- * The organizer may have the following properties:
- * * href - Often a mailto: address.
- * * commonName - Optional human-readable name.
- * * firstName - Optional first name.
- * * lastName - Optional last name.
- *
- * If you wonder why these two structures are so different, I guess a
- * valid answer is that the current spec is still a draft.
- *
- * @param array $users
+ * @param Sharee[] $sharees
*/
- function __construct(array $users, array $organizer = null) {
+ function __construct(array $sharees) {
- $this->users = $users;
- $this->organizer = $organizer;
+ $this->sharees = $sharees;
}
@@ -77,7 +46,7 @@ class Invite implements Element {
*/
function getValue() {
- return $this->users;
+ return $this->sharees;
}
@@ -104,149 +73,55 @@ class Invite implements Element {
$cs = '{' . Plugin::NS_CALENDARSERVER . '}';
- if (!is_null($this->organizer)) {
-
- $writer->startElement($cs . 'organizer');
- $writer->writeElement('{DAV:}href', $this->organizer['href']);
-
- if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
- $writer->writeElement($cs . 'common-name', $this->organizer['commonName']);
- }
- if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
- $writer->writeElement($cs . 'first-name', $this->organizer['firstName']);
- }
- if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
- $writer->writeElement($cs . 'last-name', $this->organizer['lastName']);
- }
- $writer->endElement(); // organizer
+ foreach ($this->sharees as $sharee) {
- }
-
- foreach ($this->users as $user) {
-
- $writer->startElement($cs . 'user');
- $writer->writeElement('{DAV:}href', $user['href']);
- if (isset($user['commonName']) && $user['commonName']) {
- $writer->writeElement($cs . 'common-name', $user['commonName']);
- }
- switch ($user['status']) {
-
- case SharingPlugin::STATUS_ACCEPTED :
- $writer->writeElement($cs . 'invite-accepted');
- break;
- case SharingPlugin::STATUS_DECLINED :
- $writer->writeElement($cs . 'invite-declined');
- break;
- case SharingPlugin::STATUS_NORESPONSE :
- $writer->writeElement($cs . 'invite-noresponse');
- break;
- case SharingPlugin::STATUS_INVALID :
- $writer->writeElement($cs . 'invite-invalid');
- break;
- }
-
- $writer->startElement($cs . 'access');
- if ($user['readOnly']) {
- $writer->writeElement($cs . 'read');
+ if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
+ $writer->startElement($cs . 'organizer');
} else {
- $writer->writeElement($cs . 'read-write');
- }
- $writer->endElement(); // access
-
- if (isset($user['summary']) && $user['summary']) {
- $writer->writeElement($cs . 'summary', $user['summary']);
- }
-
- $writer->endElement(); //user
-
- }
-
- }
-
- /**
- * The deserialize method is called during xml parsing.
- *
- * This method is called statictly, 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.
- *
- * @param Reader $reader
- * @return mixed
- */
- static function xmlDeserialize(Reader $reader) {
-
- $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
-
- $users = [];
-
- foreach ($reader->parseInnerTree() as $elem) {
+ $writer->startElement($cs . 'user');
- if ($elem['name'] !== $cs . 'user')
- continue;
-
- $user = [
- 'href' => null,
- 'commonName' => null,
- 'readOnly' => null,
- 'summary' => null,
- 'status' => null,
- ];
-
- foreach ($elem['value'] as $userElem) {
-
- switch ($userElem['name']) {
- case $cs . 'invite-accepted' :
- $user['status'] = SharingPlugin::STATUS_ACCEPTED;
- break;
- case $cs . 'invite-declined' :
- $user['status'] = SharingPlugin::STATUS_DECLINED;
+ switch ($sharee->inviteStatus) {
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
+ $writer->writeElement($cs . 'invite-accepted');
break;
- case $cs . 'invite-noresponse' :
- $user['status'] = SharingPlugin::STATUS_NORESPONSE;
+ case DAV\Sharing\Plugin::INVITE_DECLINED :
+ $writer->writeElement($cs . 'invite-declined');
break;
- case $cs . 'invite-invalid' :
- $user['status'] = SharingPlugin::STATUS_INVALID;
+ case DAV\Sharing\Plugin::INVITE_NORESPONSE :
+ $writer->writeElement($cs . 'invite-noresponse');
break;
- case '{DAV:}href' :
- $user['href'] = $userElem['value'];
+ case DAV\Sharing\Plugin::INVITE_INVALID :
+ $writer->writeElement($cs . 'invite-invalid');
break;
- case $cs . 'common-name' :
- $user['commonName'] = $userElem['value'];
- break;
- case $cs . 'access' :
- foreach ($userElem['value'] as $accessHref) {
- if ($accessHref['name'] === $cs . 'read') {
- $user['readOnly'] = true;
- }
- }
+ }
+
+ $writer->startElement($cs . 'access');
+ switch ($sharee->access) {
+ case DAV\Sharing\Plugin::ACCESS_READWRITE :
+ $writer->writeElement($cs . 'read-write');
break;
- case $cs . 'summary' :
- $user['summary'] = $userElem['value'];
+ case DAV\Sharing\Plugin::ACCESS_READ :
+ $writer->writeElement($cs . 'read');
break;
}
+ $writer->endElement(); // access
}
- if (!$user['status']) {
- throw new \InvalidArgumentException('Every user must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid');
- }
- $users[] = $user;
+ $href = new \Sabre\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
- return new self($users);
+ }
}
+
}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
index ec627156f..2ecf6c2bb 100644
--- a/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
@@ -2,12 +2,13 @@
namespace Sabre\CalDAV\Xml\Request;
+use Sabre\CalDAV\Plugin;
+use Sabre\CalDAV\SharingPlugin;
+use Sabre\DAV;
+use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use Sabre\Xml\Element\KeyValue;
-use Sabre\DAV\Exception\BadRequest;
-use Sabre\CalDAV\Plugin;
-use Sabre\CalDAV\SharingPlugin;
/**
* Invite-reply POST request parser
@@ -121,10 +122,10 @@ class InviteReply implements XmlDeserializable {
}
break;
case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' :
- $status = SharingPlugin::STATUS_ACCEPTED;
+ $status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
break;
case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' :
- $status = SharingPlugin::STATUS_DECLINED;
+ $status = DAV\Sharing\Plugin::INVITE_DECLINED;
break;
case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' :
$inReplyTo = $value;
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
index dacc5dc94..b5d9a133c 100644
--- a/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
@@ -2,9 +2,10 @@
namespace Sabre\CalDAV\Xml\Request;
+use Sabre\CalDAV\Plugin;
+use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
-use Sabre\CalDAV\Plugin;
/**
* Share POST request parser
@@ -20,37 +21,20 @@ use Sabre\CalDAV\Plugin;
class Share implements XmlDeserializable {
/**
- * The list of new people added or updated.
- *
- * Every element has the following keys:
- * 1. href - An email address
- * 2. commonName - Some name
- * 3. summary - An optional description of the share
- * 4. readOnly - true or false
- *
- * @var array
- */
- public $set = [];
-
- /**
- * List of people removed from the share list.
- *
- * The list is a flat list of email addresses (including mailto:).
+ * The list of new people added or updated or removed from the share.
*
- * @var array
+ * @var Sharee[]
*/
- public $remove = [];
+ public $sharees = [];
/**
* Constructor
*
- * @param array $set
- * @param array $remove
+ * @param Sharee[] $sharees
*/
- function __construct(array $set, array $remove) {
+ function __construct(array $sharees) {
- $this->set = $set;
- $this->remove = $remove;
+ $this->sharees = $sharees;
}
@@ -77,13 +61,12 @@ class Share implements XmlDeserializable {
*/
static function xmlDeserialize(Reader $reader) {
- $elems = $reader->parseInnerTree([
+ $elems = $reader->parseGetElements([
'{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue',
'{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue',
]);
- $set = [];
- $remove = [];
+ $sharees = [];
foreach ($elems as $elem) {
switch ($elem['name']) {
@@ -94,22 +77,34 @@ class Share implements XmlDeserializable {
$sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary';
$commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name';
- $set[] = [
+ $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'],
- 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null,
- 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null,
- 'readOnly' => !array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee),
- ];
+ 'properties' => $properties,
+ 'access' => $access,
+ 'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null
+ ]);
break;
case '{' . Plugin::NS_CALENDARSERVER . '}remove' :
- $remove[] = $elem['value']['{DAV:}href'];
+ $sharees[] = new Sharee([
+ 'href' => $elem['value']['{DAV:}href'],
+ 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS
+ ]);
break;
}
}
- return new self($set, $remove);
+ return new self($sharees);
}
diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/vendor/sabre/dav/lib/CardDAV/AddressBook.php
index 70bec8760..6dd098618 100644
--- a/vendor/sabre/dav/lib/CardDAV/AddressBook.php
+++ b/vendor/sabre/dav/lib/CardDAV/AddressBook.php
@@ -16,6 +16,8 @@ use Sabre\DAVACL;
*/
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
*
@@ -236,48 +238,6 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
- * Returns a list of ACE's for this node.
- *
- * Each ACE has the following properties:
- * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
- * currently the only supported privileges
- * * 'principal', a url to the principal who owns the node
- * * 'protected' (optional), indicating that this ACE is not allowed to
- * be updated.
- *
- * @return array
- */
- function getACL() {
-
- return [
- [
- 'privilege' => '{DAV:}read',
- 'principal' => $this->getOwner(),
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
- 'principal' => $this->getOwner(),
- 'protected' => true,
- ],
-
- ];
-
- }
/**
* This method returns the ACL's for card nodes in this address book.
@@ -290,12 +250,7 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
return [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->getOwner(),
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->getOwner(),
'protected' => true,
],
@@ -303,37 +258,6 @@ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IPropertie
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
/**
* This method returns the current sync-token for this collection.
diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
index ebc251832..888a44a40 100644
--- a/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
+++ b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php
@@ -18,6 +18,8 @@ use Sabre\Uri;
*/
class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {
+ use DAVACL\ACLTrait;
+
/**
* Principal uri
*
@@ -186,78 +188,4 @@ class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection,
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
- * Returns a list of ACE's for this node.
- *
- * Each ACE has the following properties:
- * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
- * currently the only supported privileges
- * * 'principal', a url to the principal who owns the node
- * * 'protected' (optional), indicating that this ACE is not allowed to
- * be updated.
- *
- * @return array
- */
- function getACL() {
-
- return [
- [
- 'privilege' => '{DAV:}read',
- 'principal' => $this->principalUri,
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
- 'principal' => $this->principalUri,
- 'protected' => true,
- ],
- ];
-
- }
-
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
index b9691b906..54e42b899 100644
--- a/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php
@@ -55,12 +55,15 @@ interface BackendInterface {
function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
/**
- * Creates a new address book
+ * 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.
* @param array $properties
- * @return void
+ * @return mixed
*/
function createAddressBook($principalUri, $url, array $properties);
diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php b/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
index 5509ddc02..7c3feff93 100644
--- a/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
+++ b/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php
@@ -128,7 +128,7 @@ class PDO extends AbstractBackend implements SyncSupport {
} else {
$query .= ', ';
}
- $query .= ' `' . $key . '` = :' . $key . ' ';
+ $query .= ' ' . $key . ' = :' . $key . ' ';
}
$query .= ' WHERE id = :addressbookid';
@@ -180,7 +180,9 @@ class PDO extends AbstractBackend implements SyncSupport {
$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();
+ return $this->pdo->lastInsertId(
+ $this->addressBooksTableName . '_id_seq'
+ );
}
@@ -230,6 +232,7 @@ class PDO extends AbstractBackend implements SyncSupport {
$result = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$row['etag'] = '"' . $row['etag'] . '"';
+ $row['lastmodified'] = (int)$row['lastmodified'];
$result[] = $row;
}
return $result;
@@ -258,6 +261,7 @@ class PDO extends AbstractBackend implements SyncSupport {
if (!$result) return false;
$result['etag'] = '"' . $result['etag'] . '"';
+ $result['lastmodified'] = (int)$result['lastmodified'];
return $result;
}
@@ -286,6 +290,7 @@ class PDO extends AbstractBackend implements SyncSupport {
$result = [];
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$row['etag'] = '"' . $row['etag'] . '"';
+ $row['lastmodified'] = (int)$row['lastmodified'];
$result[] = $row;
}
return $result;
diff --git a/vendor/sabre/dav/lib/CardDAV/Card.php b/vendor/sabre/dav/lib/CardDAV/Card.php
index 8da672502..0a040be6b 100644
--- a/vendor/sabre/dav/lib/CardDAV/Card.php
+++ b/vendor/sabre/dav/lib/CardDAV/Card.php
@@ -14,6 +14,8 @@ use Sabre\DAV;
*/
class Card extends DAV\File implements ICard, DAVACL\IACL {
+ use DAVACL\ACLTrait;
+
/**
* CardDAV backend
*
@@ -181,18 +183,6 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
/**
* Returns a list of ACE's for this node.
@@ -215,12 +205,7 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
return [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $this->addressBookInfo['principaluri'],
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
+ 'privilege' => '{DAV:}all',
'principal' => $this->addressBookInfo['principaluri'],
'protected' => true,
],
@@ -228,36 +213,4 @@ class Card extends DAV\File implements ICard, DAVACL\IACL {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/CardDAV/Plugin.php b/vendor/sabre/dav/lib/CardDAV/Plugin.php
index b8bded098..0507df100 100644
--- a/vendor/sabre/dav/lib/CardDAV/Plugin.php
+++ b/vendor/sabre/dav/lib/CardDAV/Plugin.php
@@ -4,7 +4,7 @@ namespace Sabre\CardDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\ReportNotSupported;
-use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
@@ -156,11 +156,11 @@ class Plugin extends DAV\ServerPlugin {
$path = $propFind->getPath();
$propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
- return new Href($this->getAddressBookHomeForPrincipal($path) . '/');
+ return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/');
});
if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
- return new Href($this->directories);
+ return new LocalHref($this->directories);
});
}
@@ -334,12 +334,7 @@ class Plugin extends DAV\ServerPlugin {
$data = stream_get_contents($data);
}
- $before = md5($data);
-
- // Converting the data to unicode, if needed.
- $data = DAV\StringUtil::ensureUTF8($data);
-
- if (md5($data) !== $before) $modified = true;
+ $before = $data;
try {
@@ -366,11 +361,56 @@ class Plugin extends DAV\ServerPlugin {
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
}
- if (!isset($vobj->UID)) {
- // No UID in vcards is invalid, but we'll just add it in anyway.
- $vobj->add('UID', DAV\UUIDUtil::getUUID());
+ $options = VObject\Node::PROFILE_CARDDAV;
+ $prefer = $this->server->getHTTPPrefer();
+
+ if ($prefer['handling'] !== 'strict') {
+ $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();
- $modified = true;
+ if (!$modified && strcmp($data, $before) !== 0) {
+ // This ensures that the system does not send an ETag back.
+ $modified = true;
+ }
}
// Destroy circular references to PHP will GC the object.
@@ -803,33 +843,49 @@ class Plugin extends DAV\ServerPlugin {
/**
* Converts a vcard blob to a different version, or jcard.
*
- * @param string $data
+ * @param string|resource $data
* @param string $target
* @return string
*/
protected function convertVCard($data, $target) {
- $data = VObject\Reader::read($data);
- switch ($target) {
- default :
- case 'vcard3' :
- $data = $data->convert(VObject\Document::VCARD30);
- $newResult = $data->serialize();
- break;
- case 'vcard4' :
- $data = $data->convert(VObject\Document::VCARD40);
- $newResult = $data->serialize();
- break;
- case 'jcard' :
- $data = $data->convert(VObject\Document::VCARD40);
- $newResult = json_encode($data->jsonSerialize());
- break;
-
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
}
- // Destroy circular references to PHP will GC the object.
- $data->destroy();
+ $input = VObject\Reader::read($data);
+ $output = null;
+ try {
- return $newResult;
+ switch ($target) {
+ default :
+ case 'vcard3' :
+ if ($input->getDocumentType() === VObject\Document::VCARD30) {
+ // Do nothing
+ return $data;
+ }
+ $output = $input->convert(VObject\Document::VCARD30);
+ return $output->serialize();
+ case 'vcard4' :
+ if ($input->getDocumentType() === VObject\Document::VCARD40) {
+ // 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();
+ }
+ }
}
diff --git a/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
index de8b3bb84..d015589ad 100644
--- a/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
+++ b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php
@@ -70,14 +70,34 @@ class VCFExportPlugin extends DAV\ServerPlugin {
$aclPlugin->checkPrivileges($path, '{DAV:}read');
}
- $response->setHeader('Content-Type', 'text/directory');
- $response->setStatus(200);
-
$nodes = $this->server->getPropertiesForPath($path, [
'{' . Plugin::NS_CARDDAV . '}address-data',
], 1);
- $response->setBody($this->generateVCF($nodes));
+ $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;
diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
index 0251decc1..85c5f30d5 100644
--- a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
+++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php
@@ -155,8 +155,14 @@ abstract class AbstractDigest implements BackendInterface {
$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);
+
}
}
diff --git a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php
index 818d8a4ad..4b5f35ac3 100644
--- a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php
+++ b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php
@@ -4,7 +4,6 @@ namespace Sabre\DAV\Auth;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
-use Sabre\HTTP\URLUtil;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
@@ -26,6 +25,20 @@ use Sabre\DAV\ServerPlugin;
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;
@@ -108,27 +121,6 @@ class Plugin extends ServerPlugin {
}
/**
- * Returns the current username.
- *
- * This method is deprecated and is only kept for backwards compatibility
- * purposes. Please switch to getCurrentPrincipal().
- *
- * @deprecated Will be removed in a future version!
- * @return string|null
- */
- function getCurrentUser() {
-
- // We just do a 'basename' on the principal to give back a sane value
- // here.
- list(, $userName) = URLUtil::splitPath(
- $this->getCurrentPrincipal()
- );
-
- return $userName;
-
- }
-
- /**
* This method is called before any HTTP method and forces users to be authenticated
*
* @param RequestInterface $request
@@ -154,6 +146,50 @@ class Plugin extends ServerPlugin {
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
+ * unsuccesful. For every auth backend there will be one reason, so usually
+ * there's just one.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return array
+ */
+ function check(RequestInterface $request, ResponseInterface $response) {
+
if (!$this->backends) {
throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
}
@@ -172,20 +208,56 @@ class Plugin extends ServerPlugin {
if ($result[0]) {
$this->currentPrincipal = $result[1];
// Exit early
- return;
+ return [true, $result[1]];
}
$reasons[] = $result[1];
}
- // If we got here, it means that no authentication backend was
- // successful in authenticating the user.
- $this->currentPrincipal = null;
+ 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.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return array
+ */
+ function challenge(RequestInterface $request, ResponseInterface $response) {
foreach ($this->backends as $backend) {
$backend->challenge($request, $response);
}
- throw new NotAuthenticated(implode(', ', $reasons));
+
+ }
+
+ /**
+ * 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
+ */
+ function getLoginFailedReasons() {
+
+ return $this->loginFailedReasons;
}
diff --git a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php
index 07ca6c3e5..49359a045 100644
--- a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php
+++ b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php
@@ -48,7 +48,7 @@ class Plugin extends DAV\ServerPlugin {
public $uninterestingProperties = [
'{DAV:}supportedlock',
'{DAV:}acl-restrictions',
- '{DAV:}supported-privilege-set',
+// '{DAV:}supported-privilege-set',
'{DAV:}supported-method-set',
];
@@ -112,7 +112,7 @@ class Plugin extends DAV\ServerPlugin {
$getVars = $request->getQueryParameters();
// CSP headers
- $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
+ $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
$sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
@@ -354,7 +354,7 @@ class Plugin extends DAV\ServerPlugin {
$output = '';
if ($this->enablePost) {
- $this->server->emit('onHTMLActionsPanel', [$node, &$output]);
+ $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
}
if ($output) {
@@ -368,7 +368,7 @@ class Plugin extends DAV\ServerPlugin {
$html .= $this->generateFooter();
- $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';");
+ $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
return $html;
@@ -477,7 +477,7 @@ HTML;
$version = DAV\Version::VERSION;
return <<<HTML
-<footer>Generated by SabreDAV $version (c)2007-2015 <a href="http://sabre.io/">http://sabre.io/</a></footer>
+<footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer>
</body>
</html>
HTML;
@@ -493,9 +493,10 @@ HTML;
*
* @param DAV\INode $node
* @param mixed $output
+ * @param string $path
* @return void
*/
- function htmlActionsPanel(DAV\INode $node, &$output) {
+ function htmlActionsPanel(DAV\INode $node, &$output, $path) {
if (!$node instanceof DAV\ICollection)
return;
diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css
index c9ab2c74f..8869597f0 100644
--- a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css
+++ b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css
@@ -96,12 +96,12 @@ header a {
vertical-align: middle;
border: 0;
}
-input, button {
+input, button, select {
font: inherit;
color: inherit;
}
-input[type=text] {
+input[type=text], select {
border: 1px solid #bbbbbb;
line-height: 22px;
padding: 5px 10px;
@@ -200,7 +200,7 @@ section table {
line-height: 40px;
}
-.actions input[type=text] {
+.actions input[type=text], select {
width: 450px;
}
diff --git a/vendor/sabre/dav/lib/DAV/Client.php b/vendor/sabre/dav/lib/DAV/Client.php
index d46b397b6..08d5d4702 100644
--- a/vendor/sabre/dav/lib/DAV/Client.php
+++ b/vendor/sabre/dav/lib/DAV/Client.php
@@ -3,6 +3,7 @@
namespace Sabre\DAV;
use Sabre\HTTP;
+use Sabre\Uri;
/**
* SabreDAV DAV client
@@ -387,20 +388,10 @@ class Client extends HTTP\Client {
*/
function getAbsoluteUrl($url) {
- // If the url starts with http:// or https://, the url is already absolute.
- if (preg_match('/^http(s?):\/\//', $url)) {
- return $url;
- }
-
- // If the url starts with a slash, we must calculate the url based off
- // the root of the base url.
- if (strpos($url, '/') === 0) {
- $parts = parse_url($this->baseUri);
- return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url;
- }
-
- // Otherwise...
- return $this->baseUri . $url;
+ return Uri\resolve(
+ $this->baseUri,
+ $url
+ );
}
diff --git a/vendor/sabre/dav/lib/DAV/CorePlugin.php b/vendor/sabre/dav/lib/DAV/CorePlugin.php
index 3a70b2a7e..a1b052915 100644
--- a/vendor/sabre/dav/lib/DAV/CorePlugin.php
+++ b/vendor/sabre/dav/lib/DAV/CorePlugin.php
@@ -50,6 +50,8 @@ class CorePlugin extends ServerPlugin {
$server->on('propFind', [$this, 'propFindNode'], 120);
$server->on('propFind', [$this, 'propFindLate'], 200);
+ $server->on('exception', [$this, 'exception']);
+
}
/**
@@ -844,10 +846,8 @@ class CorePlugin extends ServerPlugin {
if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
$nodeProperties = $node->getProperties($propertyNames);
- foreach ($propertyNames as $propertyName) {
- if (array_key_exists($propertyName, $nodeProperties)) {
- $propFind->set($propertyName, $nodeProperties[$propertyName], 200);
- }
+ foreach ($nodeProperties as $propertyName => $propertyValue) {
+ $propFind->set($propertyName, $propertyValue, 200);
}
}
@@ -905,6 +905,38 @@ class CorePlugin extends ServerPlugin {
}
/**
+ * Listens for exception events, and automatically logs them.
+ *
+ * @param Exception $e
+ */
+ function exception($e) {
+
+ $logLevel = \Psr\Log\LogLevel::CRITICAL;
+ if ($e instanceof \Sabre\DAV\Exception) {
+ // If it's a standard sabre/dav exception, it means we have a http
+ // status code available.
+ $code = $e->getHTTPCode();
+
+ if ($code >= 400 && $code < 500) {
+ // user error
+ $logLevel = \Psr\Log\LogLevel::INFO;
+ } else {
+ // Server-side error. We mark it's as an error, but it's not
+ // critical.
+ $logLevel = \Psr\Log\LogLevel::ERROR;
+ }
+ }
+
+ $this->server->getLogger()->log(
+ $logLevel,
+ 'Uncaught exception',
+ [
+ 'exception' => $e,
+ ]
+ );
+ }
+
+ /**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
diff --git a/vendor/sabre/dav/lib/DAV/FS/Directory.php b/vendor/sabre/dav/lib/DAV/FS/Directory.php
index 963e5554c..362f7a411 100644
--- a/vendor/sabre/dav/lib/DAV/FS/Directory.php
+++ b/vendor/sabre/dav/lib/DAV/FS/Directory.php
@@ -140,10 +140,10 @@ class Directory extends Node implements DAV\ICollection, DAV\IQuota {
* @return array
*/
function getQuotaInfo() {
-
+ $absolute = realpath($this->path);
return [
- disk_total_space($this->path) - disk_free_space($this->path),
- disk_free_space($this->path)
+ disk_total_space($absolute) - disk_free_space($absolute),
+ disk_free_space($absolute)
];
}
diff --git a/vendor/sabre/dav/lib/DAV/File.php b/vendor/sabre/dav/lib/DAV/File.php
index e0a0391db..675956b22 100644
--- a/vendor/sabre/dav/lib/DAV/File.php
+++ b/vendor/sabre/dav/lib/DAV/File.php
@@ -15,12 +15,24 @@ namespace Sabre\DAV;
abstract class File extends Node implements IFile {
/**
- * Updates the data
+ * Replaces the contents of the file.
*
- * data is a readable stream resource.
+ * The data argument is a readable stream resource.
*
- * @param resource $data
- * @return void
+ * After a succesful put operation, you may choose to return an ETag. The
+ * etag must always be surrounded by double-quotes. These quotes must
+ * appear in the actual string you're returning.
+ *
+ * Clients may use the ETag from a PUT request to later on make sure that
+ * when they update the file, the contents haven't changed in the mean
+ * time.
+ *
+ * If you don't plan to store the file byte-by-byte, and you return a
+ * different object on a subsequent GET you are strongly recommended to not
+ * return an ETag, and just return null.
+ *
+ * @param string|resource $data
+ * @return string|null
*/
function put($data) {
diff --git a/vendor/sabre/dav/lib/DAV/ICollection.php b/vendor/sabre/dav/lib/DAV/ICollection.php
index 390d9b741..7793070d3 100644
--- a/vendor/sabre/dav/lib/DAV/ICollection.php
+++ b/vendor/sabre/dav/lib/DAV/ICollection.php
@@ -54,14 +54,14 @@ interface ICollection extends INode {
* exist.
*
* @param string $name
- * @return DAV\INode
+ * @return INode
*/
function getChild($name);
/**
* Returns an array with all the child nodes
*
- * @return DAV\INode[]
+ * @return INode[]
*/
function getChildren();
diff --git a/vendor/sabre/dav/lib/DAV/IFile.php b/vendor/sabre/dav/lib/DAV/IFile.php
index e16a3a58a..37e7cd33c 100644
--- a/vendor/sabre/dav/lib/DAV/IFile.php
+++ b/vendor/sabre/dav/lib/DAV/IFile.php
@@ -32,7 +32,7 @@ interface IFile extends INode {
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
- * @param resource $data
+ * @param resource|data $data
* @return string|null
*/
function put($data);
diff --git a/vendor/sabre/dav/lib/DAV/INode.php b/vendor/sabre/dav/lib/DAV/INode.php
index b5e6cb9ef..bb884934d 100644
--- a/vendor/sabre/dav/lib/DAV/INode.php
+++ b/vendor/sabre/dav/lib/DAV/INode.php
@@ -36,9 +36,10 @@ interface INode {
function setName($name);
/**
- * Returns the last modification time, as a unix timestamp
+ * Returns the last modification time, as a unix timestamp. Return null
+ * if the information is not available.
*
- * @return int
+ * @return int|null
*/
function getLastModified();
diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php
index 910e4979d..2fe843884 100644
--- a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php
+++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php
@@ -88,6 +88,9 @@ class PDO implements BackendInterface {
$stmt->execute([$path]);
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if (gettype($row['value']) === 'resource') {
+ $row['value'] = stream_get_contents($row['value']);
+ }
switch ($row['valuetype']) {
case null :
case self::VT_STRING :
@@ -121,7 +124,26 @@ class PDO implements BackendInterface {
$propPatch->handleRemaining(function($properties) use ($path) {
- $updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)");
+
+ if ($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') {
+
+ $updateSql = <<<SQL
+INSERT INTO {$this->tableName} (path, name, valuetype, value)
+VALUES (:path, :name, :valuetype, :value)
+ON CONFLICT (path, name)
+DO UPDATE SET valuetype = :valuetype, value = :value
+SQL;
+
+
+ } else {
+ $updateSql = <<<SQL
+REPLACE INTO {$this->tableName} (path, name, valuetype, value)
+VALUES (:path, :name, :valuetype, :value)
+SQL;
+
+ }
+
+ $updateStmt = $this->pdo->prepare($updateSql);
$deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?");
foreach ($properties as $name => $value) {
@@ -136,7 +158,14 @@ class PDO implements BackendInterface {
$valueType = self::VT_OBJECT;
$value = serialize($value);
}
- $updateStmt->execute([$path, $name, $valueType, $value]);
+
+ $updateStmt->bindParam('path', $path, \PDO::PARAM_STR);
+ $updateStmt->bindParam('name', $name, \PDO::PARAM_STR);
+ $updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT);
+ $updateStmt->bindParam('value', $value, \PDO::PARAM_LOB);
+
+ $updateStmt->execute();
+
} else {
$deleteStmt->execute([$path, $name]);
}
diff --git a/vendor/sabre/dav/lib/DAV/Server.php b/vendor/sabre/dav/lib/DAV/Server.php
index b37652812..024b7a557 100644
--- a/vendor/sabre/dav/lib/DAV/Server.php
+++ b/vendor/sabre/dav/lib/DAV/Server.php
@@ -8,6 +8,10 @@ use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;
use Sabre\Uri;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
/**
* Main DAV server class
@@ -16,7 +20,9 @@ use Sabre\Uri;
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
-class Server extends EventEmitter {
+class Server extends EventEmitter implements LoggerAwareInterface {
+
+ use LoggerAwareTrait;
/**
* Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree
@@ -431,6 +437,20 @@ class Server extends EventEmitter {
}
/**
+ * Returns the PSR-3 logger objcet.
+ *
+ * @return LoggerInterface
+ */
+ function getLogger() {
+
+ if (!$this->logger) {
+ $this->logger = new NullLogger();
+ }
+ return $this->logger;
+
+ }
+
+ /**
* Handles a http request, and execute a method based on its name
*
* @param RequestInterface $request
@@ -1177,9 +1197,20 @@ class Server extends EventEmitter {
if (!$success) {
$result = $mkCol->getResult();
- // generateMkCol needs the href key to exist.
- $result['href'] = $uri;
- return $result;
+
+ $formattedResult = [
+ 'href' => $uri,
+ ];
+
+ foreach ($result as $propertyName => $status) {
+
+ if (!isset($formattedResult[$status])) {
+ $formattedResult[$status] = [];
+ }
+ $formattedResult[$status][$propertyName] = null;
+
+ }
+ return $formattedResult;
}
$this->tree->markDirty($parentUri);
diff --git a/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php b/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php
new file mode 100644
index 000000000..034aefbdc
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Sabre\DAV\Sharing;
+
+use Sabre\DAV\INode;
+
+/**
+ * This interface represents a resource that has sharing capabilities, either
+ * because it's possible for an owner to share the resource, or because this is
+ * an instance of a shared resource.
+ *
+ * @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ISharedNode extends INode {
+
+ /**
+ * 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
+ */
+ function getShareAccess();
+
+ /**
+ * 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
+ */
+ function getShareResourceUri();
+
+ /**
+ * Updates the list of sharees.
+ *
+ * Every item must be a Sharee object.
+ *
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
+ * @return void
+ */
+ function updateInvites(array $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[]
+ */
+ function getInvites();
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php b/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php
new file mode 100644
index 000000000..354d06a56
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php
@@ -0,0 +1,342 @@
+<?php
+
+namespace Sabre\DAV\Sharing;
+
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\INode;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Xml\Property;
+use Sabre\DAV\Xml\Element\Sharee;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * This plugin implements HTTP requests and properties related to:
+ *
+ * draft-pot-webdav-resource-sharing
+ *
+ * This specification allows people to share webdav resources with others.
+ *
+ * @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 Plugin extends ServerPlugin {
+
+ const ACCESS_NOTSHARED = 0;
+ const ACCESS_SHAREDOWNER = 1;
+ const ACCESS_READ = 2;
+ const ACCESS_READWRITE = 3;
+ const ACCESS_NOACCESS = 4;
+
+ const INVITE_NORESPONSE = 1;
+ const INVITE_ACCEPTED = 2;
+ const INVITE_DECLINED = 3;
+ const INVITE_INVALID = 4;
+
+ /**
+ * Reference to SabreDAV server object.
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * This method should return a list of server-features.
+ *
+ * This is for example 'versioning' and is added to the DAV: header
+ * in an OPTIONS response.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return ['resource-sharing'];
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'sharing';
+
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param Server $server
+ * @return void
+ */
+ function initialize(Server $server) {
+
+ $this->server = $server;
+
+ $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';
+
+ array_push(
+ $server->protectedProperties,
+ '{DAV:}share-mode'
+ );
+
+ $server->on('method:POST', [$this, 'httpPost']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('onBrowserPostAction', [$this, 'browserPostAction']);
+
+ }
+
+ /**
+ * Updates the list of sharees on a shared resource.
+ *
+ * The sharees array is a list of people that are to be added modified
+ * or removed in the list of shares.
+ *
+ * @param string $path
+ * @param Sharee[] $sharees
+ * @return void
+ */
+ function shareResource($path, array $sharees) {
+
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof ISharedNode) {
+
+ throw new Forbidden('Sharing is not allowed on this node');
+
+ }
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}share');
+ }
+
+ foreach ($sharees as $sharee) {
+ // We're going to attempt to get a local principal uri for a share
+ // href by emitting the getPrincipalByUri event.
+ $principal = null;
+ $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
+ $sharee->principal = $principal;
+ }
+ $node->updateInvites($sharees);
+
+ }
+
+ /**
+ * This event is triggered when properties are requested for nodes.
+ *
+ * This allows us to inject any sharings-specific properties.
+ *
+ * @param PropFind $propFind
+ * @param INode $node
+ * @return void
+ */
+ function propFind(PropFind $propFind, INode $node) {
+
+ if ($node instanceof ISharedNode) {
+
+ $propFind->handle('{DAV:}share-access', function() use ($node) {
+
+ return new Property\ShareAccess($node->getShareAccess());
+
+ });
+ $propFind->handle('{DAV:}invite', function() use ($node) {
+
+ return new Property\Invite($node->getInvites());
+
+ });
+ $propFind->handle('{DAV:}share-resource-uri', function() use ($node) {
+
+ return new Property\Href($node->getShareResourceUri());
+
+ });
+
+ }
+
+ }
+
+ /**
+ * We intercept this to handle POST requests on shared resources
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return null|bool
+ */
+ function httpPost(RequestInterface $request, ResponseInterface $response) {
+
+ $path = $request->getPath();
+ $contentType = $request->getHeader('Content-Type');
+
+ // We're only interested in the davsharing content type.
+ if (strpos($contentType, 'application/davsharing+xml') === false) {
+ return;
+ }
+
+ $message = $this->server->xml->parse(
+ $request->getBody(),
+ $request->getUrl(),
+ $documentType
+ );
+
+ switch ($documentType) {
+
+ case '{DAV:}share-resource':
+
+ $this->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;
+
+ default :
+ throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type');
+
+ }
+
+ }
+
+ /**
+ * This method is triggered whenever a subsystem reqeuests the privileges
+ * hat are supported on a particular node.
+ *
+ * We need to add a number of privileges for scheduling purposes.
+ *
+ * @param INode $node
+ * @param array $supportedPrivilegeSet
+ */
+ function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
+
+ if ($node instanceof ISharedNode) {
+ $supportedPrivilegeSet['{DAV:}share'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ }
+
+ /**
+ * 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
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'This plugin implements WebDAV resource sharing',
+ 'link' => 'https://github.com/evert/webdav-sharing'
+ ];
+
+ }
+
+ /**
+ * This method is used to generate HTML output for the
+ * DAV\Browser\Plugin.
+ *
+ * @param INode $node
+ * @param string $output
+ * @param string $path
+ * @return bool|null
+ */
+ function htmlActionsPanel(INode $node, &$output, $path) {
+
+ if (!$node instanceof ISharedNode) {
+ return;
+ }
+
+ $aclPlugin = $this->server->getPlugin('acl');
+ if ($aclPlugin) {
+ if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
+ // Sharing is not permitted, we will not draw this interface.
+ return;
+ }
+ }
+
+ $output .= '<tr><td colspan="2"><form method="post" action="">
+ <h3>Share this resource</h3>
+ <input type="hidden" name="sabreAction" value="share" />
+ <label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br />
+ <label>Access</label>
+ <select name="access">
+ <option value="readwrite">Read-write</option>
+ <option value="read">Read-only</option>
+ <option value="no-access">Revoke access</option>
+ </select><br />
+ <input type="submit" value="share" />
+ </form>
+ </td></tr>';
+
+ }
+
+ /**
+ * This method is triggered for POST actions generated by the browser
+ * plugin.
+ *
+ * @param string $path
+ * @param string $action
+ * @param array $postVars
+ */
+ function browserPostAction($path, $action, $postVars) {
+
+ if ($action !== 'share') {
+ return;
+ }
+
+ if (empty($postVars['href'])) {
+ throw new BadRequest('The "href" POST parameter is required');
+ }
+ if (empty($postVars['access'])) {
+ throw new BadRequest('The "access" POST parameter is required');
+ }
+
+ $accessMap = [
+ 'readwrite' => self::ACCESS_READWRITE,
+ 'read' => self::ACCESS_READ,
+ 'no-access' => self::ACCESS_NOACCESS,
+ ];
+
+ if (!isset($accessMap[$postVars['access']])) {
+ throw new BadRequest('The "access" POST must be readwrite, read or no-access');
+ }
+ $sharee = new Sharee([
+ 'href' => $postVars['href'],
+ 'access' => $accessMap[$postVars['access']],
+ ]);
+
+ $this->shareResource(
+ $path,
+ [$sharee]
+ );
+ return false;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Tree.php b/vendor/sabre/dav/lib/DAV/Tree.php
index 4563f7c72..5d2792503 100644
--- a/vendor/sabre/dav/lib/DAV/Tree.php
+++ b/vendor/sabre/dav/lib/DAV/Tree.php
@@ -229,7 +229,7 @@ class Tree {
// flushing the entire cache
$path = trim($path, '/');
foreach ($this->cache as $nodePath => $node) {
- if ($nodePath == $path || strpos($nodePath, $path . '/') === 0)
+ if ($path === '' || $nodePath == $path || strpos($nodePath, $path . '/') === 0)
unset($this->cache[$nodePath]);
}
diff --git a/vendor/sabre/dav/lib/DAV/Version.php b/vendor/sabre/dav/lib/DAV/Version.php
index f9331943a..5430b967c 100644
--- a/vendor/sabre/dav/lib/DAV/Version.php
+++ b/vendor/sabre/dav/lib/DAV/Version.php
@@ -14,6 +14,6 @@ class Version {
/**
* Full version number
*/
- const VERSION = '3.1.3';
+ const VERSION = '3.2.0-beta1';
}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php b/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php
new file mode 100644
index 000000000..dcfd7bd2e
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Sabre\DAV\Xml\Element;
+
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Sharing\Plugin;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\ShareAccess;
+use Sabre\Xml\Deserializer;
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+
+/**
+ * This class represents the {DAV:}sharee element.
+ *
+ * @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Sharee implements Element {
+
+ /**
+ * A URL. Usually a mailto: address, could also be a principal url.
+ * This uniquely identifies the sharee.
+ *
+ * @var string
+ */
+ public $href;
+
+ /**
+ * A local principal path. The server will do its best to locate the
+ * principal uri based on the given uri. If we could find a local matching
+ * principal uri, this property will contain the value.
+ *
+ * @var string|null
+ */
+ public $principal;
+
+ /**
+ * A list of WebDAV properties that describe the sharee. This might for
+ * example contain a {DAV:}displayname with the real name of the user.
+ *
+ * @var array
+ */
+ public $properties = [];
+
+ /**
+ * Share access level. One of the Sabre\DAV\Sharing\Plugin::ACCESS
+ * constants.
+ *
+ * Can be one of:
+ *
+ * ACCESS_READ
+ * ACCESS_READWRITE
+ * ACCESS_SHAREDOWNER
+ * ACCESS_NOACCESS
+ *
+ * depending on context.
+ *
+ * @var int
+ */
+ public $access;
+
+ /**
+ * When a sharee is originally invited to a share, the sharer may add
+ * a comment. This will be placed in this property.
+ *
+ * @var string
+ */
+ public $comment;
+
+ /**
+ * The status of the invite, should be one of the
+ * Sabre\DAV\Sharing\Plugin::INVITE constants.
+ *
+ * @var int
+ */
+ public $inviteStatus;
+
+ /**
+ * Creates the object
+ *
+ * $properties will be used to populate all internal properties.
+ *
+ * @param array $properties
+ */
+ function __construct(array $properties = []) {
+
+ foreach ($properties as $k => $v) {
+
+ if (property_exists($this, $k)) {
+ $this->$k = $v;
+ } else {
+ throw new \InvalidArgumentException('Unknown property: ' . $k);
+ }
+
+ }
+
+ }
+
+ /**
+ * 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 XmlSerializble 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.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+
+ $writer->write([
+ new Href($this->href),
+ '{DAV:}prop' => $this->properties,
+ '{DAV:}share-access' => new ShareAccess($this->access),
+ ]);
+ switch ($this->inviteStatus) {
+ case Plugin::INVITE_NORESPONSE :
+ $writer->writeElement('{DAV:}invite-noresponse');
+ break;
+ case Plugin::INVITE_ACCEPTED :
+ $writer->writeElement('{DAV:}invite-accepted');
+ break;
+ case Plugin::INVITE_DECLINED :
+ $writer->writeElement('{DAV:}invite-declined');
+ break;
+ case Plugin::INVITE_INVALID :
+ $writer->writeElement('{DAV:}invite-invalid');
+ break;
+ }
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, 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.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ // Temporarily override configuration
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess';
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue';
+
+ $elems = Deserializer\keyValue($reader, 'DAV:');
+
+ // Restore previous configuration
+ $reader->popContext();
+
+ $sharee = new self();
+ if (!isset($elems['href'])) {
+ throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element');
+ }
+ $sharee->href = $elems['href'];
+
+ if (isset($elems['prop'])) {
+ $sharee->properties = $elems['prop'];
+ }
+ if (isset($elems['comment'])) {
+ $sharee->comment = $elems['comment'];
+ }
+ if (!isset($elems['share-access'])) {
+ throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element');
+ }
+ $sharee->access = $elems['share-access']->getValue();
+ return $sharee;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php
index 538e98d0f..0027f72e1 100644
--- a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php
+++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php
@@ -7,6 +7,7 @@ use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
+use Sabre\Uri;
/**
* Href property
@@ -32,13 +33,6 @@ class Href implements Element, HtmlOutput {
protected $hrefs;
/**
- * Automatically prefix the url with the server base directory
- *
- * @var bool
- */
- protected $autoPrefix = true;
-
- /**
* Constructor
*
* You must either pass a string for a single href, or an array of hrefs.
@@ -47,16 +41,13 @@ class Href implements Element, HtmlOutput {
* and not relative to the servers base uri.
*
* @param string|string[] $href
- * @param bool $autoPrefix
*/
- function __construct($hrefs, $autoPrefix = true) {
+ function __construct($hrefs) {
if (is_string($hrefs)) {
$hrefs = [$hrefs];
}
$this->hrefs = $hrefs;
- $this->autoPrefix = $autoPrefix;
-
}
@@ -104,9 +95,7 @@ class Href implements Element, HtmlOutput {
function xmlSerialize(Writer $writer) {
foreach ($this->getHrefs() as $href) {
- if ($this->autoPrefix) {
- $href = $writer->contextUri . \Sabre\HTTP\encodePath($href);
- }
+ $href = Uri\resolve($writer->contextUri, $href);
$writer->writeElement('{DAV:}href', $href);
}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php
new file mode 100644
index 000000000..0616ff113
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Sabre\DAV\Xml\Property;
+
+use Sabre\DAV\Sharing\Sharee;
+use Sabre\Xml\XmlSerializable;
+use Sabre\Xml\Writer;
+
+/**
+ * This class represents the {DAV:}invite property.
+ *
+ * This property is defined here:
+ * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.2
+ *
+ * This property is used by clients to determine who currently has access to
+ * a shared resource, what their access level is and what their invite status
+ * is.
+ *
+ * @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 {
+
+ /**
+ * A list of sharees
+ *
+ * @var Sharee[]
+ */
+ public $sharees = [];
+
+ /**
+ * Creates the property.
+ *
+ * @param Sharee[] $sharees
+ */
+ function __construct(array $sharees) {
+
+ $this->sharees = $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 XmlSerializble 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.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ foreach ($this->sharees as $sharee) {
+ $writer->writeElement('{DAV:}sharee', $sharee);
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php b/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php
new file mode 100644
index 000000000..76a27b95d
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Sabre\DAV\Xml\Property;
+
+use Sabre\HTTP;
+
+/**
+ * LocalHref property
+ *
+ * Like the Href property, this element represents {DAV:}href. The difference
+ * is that this is used stricly for paths on the server. The LocalHref property
+ * will prepare the path so it's a valid URI.
+ *
+ * These two objects behave identically:
+ * new LocalHref($path)
+ * new Href(\Sabre\HTTP\encodePath($path))
+ *
+ * LocalPath basically ensures that your spaces are %20, and everything that
+ * needs to be is uri encoded.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class LocalHref extends Href {
+
+ /**
+ * Constructor
+ *
+ * You must either pass a string for a single href, or an array of hrefs.
+ *
+ * If auto-prefix is set to false, the hrefs will be treated as absolute
+ * and not relative to the servers base uri.
+ *
+ * @param string|string[] $href
+ */
+ function __construct($hrefs) {
+
+ parent::__construct(array_map(
+ function($href) {
+ return \Sabre\HTTP\encodePath($href);
+ },
+ (array)$hrefs
+ ));
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php b/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php
new file mode 100644
index 000000000..f27af5415
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Sabre\DAV\Xml\Property;
+
+use Sabre\DAV\Sharing\Plugin as SharingPlugin;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+
+/**
+ * This class represents the {DAV:}share-access property.
+ *
+ * This property is defined here:
+ * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.1
+ *
+ * This property is used to indicate if a resource is a shared resource, and
+ * whether the instance of the shared resource is the original instance, or
+ * an instance belonging to a sharee.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ShareAccess implements Element {
+
+ /**
+ * Either SHARED or SHAREDOWNER
+ *
+ * @var int
+ */
+ protected $value;
+
+ /**
+ * Creates the property.
+ *
+ * The constructor value must be one of the
+ * \Sabre\DAV\Sharing\Plugin::ACCESS_ constants.
+ *
+ * @param int $shareAccess
+ */
+ function __construct($shareAccess) {
+
+ $this->value = $shareAccess;
+
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * @return int
+ */
+ 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 XmlSerializble 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.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ switch ($this->value) {
+
+ case SharingPlugin::ACCESS_NOTSHARED :
+ $writer->writeElement('{DAV:}not-shared');
+ break;
+ case SharingPlugin::ACCESS_SHAREDOWNER :
+ $writer->writeElement('{DAV:}shared-owner');
+ break;
+ case SharingPlugin::ACCESS_READ :
+ $writer->writeElement('{DAV:}read');
+ break;
+ case SharingPlugin::ACCESS_READWRITE :
+ $writer->writeElement('{DAV:}read-write');
+ break;
+ case SharingPlugin::ACCESS_NOACCESS :
+ $writer->writeElement('{DAV:}no-access');
+ break;
+
+ }
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, 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.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree();
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+ case '{DAV:}not-shared' :
+ return new self(SharingPlugin::ACCESS_NOTSHARED);
+ case '{DAV:}sharedowner' :
+ return new self(SharingPlugin::ACCESS_SHAREDOWNER);
+ case '{DAV:}read' :
+ return new self(SharingPlugin::ACCESS_READ);
+ case '{DAV:}read-write' :
+ return new self(SharingPlugin::ACCESS_READWRITE);
+ case '{DAV:}no-access' :
+ return new self(SharingPlugin::ACCESS_NOACCESS);
+ }
+ }
+ throw new BadRequest('Invalid value for {DAV:}share-access element');
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php b/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php
new file mode 100644
index 000000000..965e5857c
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Sabre\DAV\Xml\Request;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Xml\Element\Sharee;
+
+/**
+ * ShareResource request parser.
+ *
+ * This class parses the {DAV:}share-resource POST request as defined in:
+ *
+ * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-01#section-5.3.2.1
+ *
+ * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ShareResource implements XmlDeserializable {
+
+ /**
+ * The list of new people added or updated or removed from the share.
+ *
+ * @var Sharee[]
+ */
+ public $sharees = [];
+
+ /**
+ * Constructor
+ *
+ * @param Sharee[] $sharees
+ */
+ function __construct(array $sharees) {
+
+ $this->sharees = $sharees;
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, 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.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree([
+ '{DAV:}sharee' => 'Sabre\DAV\Xml\Element\Sharee',
+ '{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess',
+ '{DAV:}prop' => 'Sabre\Xml\Deserializer\keyValue',
+ ]);
+
+ $sharees = [];
+
+ foreach ($elems as $elem) {
+ if ($elem['name'] !== '{DAV:}sharee') continue;
+ $sharees[] = $elem['value'];
+
+ }
+
+ return new self($sharees);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAVACL/ACLTrait.php b/vendor/sabre/dav/lib/DAVACL/ACLTrait.php
new file mode 100644
index 000000000..602654a2e
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAVACL/ACLTrait.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Sabre\DAVACL;
+
+/**
+ * This trait is a default implementation of the IACL interface.
+ *
+ * In many cases you only want to implement 1 or to of the IACL functions,
+ * this trait allows you to be a bit lazier.
+ *
+ * By default this trait grants all privileges to the owner of the resource.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (https://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+trait ACLTrait {
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}all',
+ 'principal' => '{DAV:}owner',
+ 'protected' => true,
+ ]
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's as an array argument.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node');
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php
index 460f78981..9d2026380 100644
--- a/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php
+++ b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php
@@ -110,7 +110,7 @@ abstract class AbstractPrincipalCollection extends DAV\Collection implements IPr
*
* @param string $name
* @throws DAV\Exception\NotFound
- * @return IPrincipal
+ * @return DAV\INode
*/
function getChild($name) {
diff --git a/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php
index 5fab4768c..1c08b43b1 100644
--- a/vendor/sabre/dav/lib/DAVACL/FS/Collection.php
+++ b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php
@@ -3,6 +3,7 @@
namespace Sabre\DAVACL\FS;
use Sabre\DAV\FSExt\Directory as BaseCollection;
+use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
@@ -16,6 +17,8 @@ use Sabre\DAV\Exception\NotFound;
*/
class Collection extends BaseCollection implements IACL {
+ use ACLTrait;
+
/**
* A list of ACL rules.
*
@@ -52,8 +55,8 @@ class Collection extends BaseCollection implements IACL {
* exist.
*
* @param string $name
- * @throws DAV\Exception\NotFound
- * @return DAV\INode
+ * @throws NotFound
+ * @return \Sabre\DAV\INode
*/
function getChild($name) {
@@ -88,19 +91,6 @@ class Collection extends BaseCollection implements IACL {
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -118,36 +108,4 @@ class Collection extends BaseCollection implements IACL {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new Forbidden('Setting ACL is not allowed here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/FS/File.php b/vendor/sabre/dav/lib/DAVACL/FS/File.php
index 0d549528b..387597bf7 100644
--- a/vendor/sabre/dav/lib/DAVACL/FS/File.php
+++ b/vendor/sabre/dav/lib/DAVACL/FS/File.php
@@ -4,7 +4,7 @@ namespace Sabre\DAVACL\FS;
use Sabre\DAV\FSExt\File as BaseFile;
use Sabre\DAVACL\IACL;
-use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAVACL\ACLTrait;
/**
* This is an ACL-enabled file node.
@@ -15,6 +15,8 @@ use Sabre\DAV\Exception\Forbidden;
*/
class File extends BaseFile implements IACL {
+ use ACLTrait;
+
/**
* A list of ACL rules.
*
@@ -58,19 +60,6 @@ class File extends BaseFile implements IACL {
}
/**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -88,36 +77,4 @@ class File extends BaseFile implements IACL {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new Forbidden('Setting ACL is not allowed here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php
index c27616770..9e21353ea 100644
--- a/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php
+++ b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php
@@ -2,8 +2,8 @@
namespace Sabre\DAVACL\FS;
-use Sabre\DAV\Exception\Forbidden;
use Sabre\DAVACL\AbstractPrincipalCollection;
+use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
use Sabre\Uri;
@@ -21,6 +21,8 @@ use Sabre\Uri;
*/
class HomeCollection extends AbstractPrincipalCollection implements IACL {
+ use ACLTrait;
+
/**
* Name of this collection.
*
@@ -70,20 +72,15 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL {
* supplied by the authentication backend.
*
* @param array $principalInfo
- * @return void
+ * @return \Sabre\DAVACL\INode
*/
function getChildForPrincipal(array $principalInfo) {
$owner = $principalInfo['uri'];
$acl = [
[
- 'privilege' => '{DAV:}read',
- 'principal' => $owner,
- 'protected' => true,
- ],
- [
- 'privilege' => '{DAV:}write',
- 'principal' => $owner,
+ 'privilege' => '{DAV:}all',
+ 'principal' => '{DAV:}owner',
'protected' => true,
],
];
@@ -103,31 +100,6 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL {
}
- /**
- * Returns the owner principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getOwner() {
-
- return null;
-
- }
-
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
/**
* Returns a list of ACE's for this node.
@@ -153,36 +125,4 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL {
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new Forbidden('Setting ACL is not allowed here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/IACL.php b/vendor/sabre/dav/lib/DAVACL/IACL.php
index 81908d08f..f7a138665 100644
--- a/vendor/sabre/dav/lib/DAVACL/IACL.php
+++ b/vendor/sabre/dav/lib/DAVACL/IACL.php
@@ -71,5 +71,4 @@ interface IACL extends DAV\INode {
*/
function getSupportedPrivilegeSet();
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/Plugin.php b/vendor/sabre/dav/lib/DAVACL/Plugin.php
index 601dffecc..59a7b0922 100644
--- a/vendor/sabre/dav/lib/DAVACL/Plugin.php
+++ b/vendor/sabre/dav/lib/DAVACL/Plugin.php
@@ -4,7 +4,11 @@ namespace Sabre\DAVACL;
use Sabre\DAV;
use Sabre\DAV\INode;
+use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\NotAuthenticated;
+use Sabre\DAVACL\Exception\NeedPrivileges;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Uri;
@@ -64,18 +68,6 @@ class Plugin extends DAV\ServerPlugin {
];
/**
- * By default ACL is only enforced for nodes that have ACL support (the
- * ones that implement IACL). For any other node, access is
- * always granted.
- *
- * To override this behaviour you can turn this setting off. This is useful
- * if you plan to fully support ACL in the entire tree.
- *
- * @var bool
- */
- public $allowAccessToNodesWithoutACL = true;
-
- /**
* By default nodes that are inaccessible by the user, can still be seen
* in directory listings (PROPFIND on parent with Depth: 1)
*
@@ -109,6 +101,18 @@ class Plugin extends DAV\ServerPlugin {
public $adminPrincipals = [];
/**
+ * The ACL plugin allows privileges to be assigned to users that are not
+ * logged in. To facilitate that, it modifies the auth plugin's behavior
+ * to only require login when a privileged operation was denied.
+ *
+ * Unauthenticated access can be considered a security concern, so it's
+ * possible to turn this feature off to harden the server's security.
+ *
+ * @var bool
+ */
+ public $allowUnauthenticatedAccess = true;
+
+ /**
* Returns a list of features added by this plugin.
*
* This list is used in the response of a HTTP OPTIONS request.
@@ -161,6 +165,7 @@ class Plugin extends DAV\ServerPlugin {
return [
'{DAV:}expand-property',
+ '{DAV:}principal-match',
'{DAV:}principal-property-search',
'{DAV:}principal-search-property-set',
];
@@ -179,7 +184,8 @@ class Plugin extends DAV\ServerPlugin {
* @param array|string $privileges
* @param int $recursion
* @param bool $throwExceptions if set to false, this method won't throw exceptions.
- * @throws Sabre\DAVACL\Exception\NeedPrivileges
+ * @throws NeedPrivileges
+ * @throws NotAuthenticated
* @return bool
*/
function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {
@@ -188,18 +194,6 @@ class Plugin extends DAV\ServerPlugin {
$acl = $this->getCurrentUserPrivilegeSet($uri);
- if (is_null($acl)) {
- if ($this->allowAccessToNodesWithoutACL) {
- return true;
- } else {
- if ($throwExceptions)
- throw new Exception\NeedPrivileges($uri, $privileges);
- else
- return false;
-
- }
- }
-
$failed = [];
foreach ($privileges as $priv) {
@@ -210,10 +204,22 @@ class Plugin extends DAV\ServerPlugin {
}
if ($failed) {
- if ($throwExceptions)
- throw new Exception\NeedPrivileges($uri, $failed);
- else
+ if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
+ // We are not authenticated. Kicking in the Auth plugin.
+ $authPlugin = $this->server->getPlugin('auth');
+ $reasons = $authPlugin->getLoginFailedReasons();
+ $authPlugin->challenge(
+ $this->server->httpRequest,
+ $this->server->httpResponse
+ );
+ throw new notAuthenticated(implode(', ', $reasons) . '. Login was needed for privilege: ' . implode(', ', $failed) . ' on ' . $uri);
+ }
+ if ($throwExceptions) {
+
+ throw new NeedPrivileges($uri, $failed);
+ } else {
return false;
+ }
}
return true;
@@ -229,10 +235,11 @@ class Plugin extends DAV\ServerPlugin {
*/
function getCurrentUserPrincipal() {
- $authPlugin = $this->server->getPlugin('auth');
- if (is_null($authPlugin)) return null;
/** @var $authPlugin Sabre\DAV\Auth\Plugin */
-
+ $authPlugin = $this->server->getPlugin('auth');
+ if (!$authPlugin) {
+ return null;
+ }
return $authPlugin->getCurrentPrincipal();
}
@@ -258,6 +265,56 @@ class Plugin extends DAV\ServerPlugin {
}
/**
+ * Sets the default ACL rules.
+ *
+ * These rules are used for all nodes that don't implement the IACL interface.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setDefaultAcl(array $acl) {
+
+ $this->defaultAcl = $acl;
+
+ }
+
+ /**
+ * Returns the default ACL rules.
+ *
+ * These rules are used for all nodes that don't implement the IACL interface.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function getDefaultAcl() {
+
+ return $this->defaultAcl;
+
+ }
+
+ /**
+ * The default ACL rules.
+ *
+ * These rules are used for nodes that don't implement IACL. These default
+ * set of rules allow anyone to do anything, as long as they are
+ * authenticated.
+ *
+ * var array
+ */
+ protected $defaultAcl = [
+ [
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ 'privilege' => '{DAV:}all',
+ ],
+ [
+ 'principal' => '{DAV:}unauthenticated',
+ 'protected' => true,
+ 'privilege' => '{DAV:}read',
+ ],
+ ];
+
+ /**
* This array holds a cache for all the principals that are associated with
* a single principal.
*
@@ -311,13 +368,77 @@ class Plugin extends DAV\ServerPlugin {
}
/**
- * Returns the supported privilege structure for this ACL plugin.
+ * Find out of a principal equals another principal.
+ *
+ * This is a quick way to find out wether a principal URI is part of a
+ * group, or any subgroups.
*
- * See RFC3744 for more details. Currently we default on a simple,
- * standard structure.
+ * The first argument is the principal URI you want to check against. For
+ * example the principal group, and the second argument is the principal of
+ * which you want to find out of it is the same as the first principal, or
+ * in a member of the first principal's group or subgroups.
*
- * You can either get the list of privileges by a uri (path) or by
- * specifying a Node.
+ * So the arguments are not interchangable. If principal A is in group B,
+ * passing 'B', 'A' will yield true, but 'A', 'B' is false.
+ *
+ * If the sceond argument is not passed, we will use the current user
+ * principal.
+ *
+ * @param string $checkPrincipal
+ * @param string $currentPrincipal
+ * @return bool
+ */
+ function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null) {
+
+ if (is_null($currentPrincipal)) {
+ $currentPrincipal = $this->getCurrentUserPrincipal();
+ }
+ if ($currentPrincipal === $checkPrincipal) {
+ return true;
+ }
+ return in_array(
+ $checkPrincipal,
+ $this->getPrincipalMembership($currentPrincipal)
+ );
+
+ }
+
+
+ /**
+ * Returns a tree of supported privileges for a resource.
+ *
+ * The returned array structure should be in this form:
+ *
+ * [
+ * [
+ * 'privilege' => '{DAV:}read',
+ * 'abstract' => false,
+ * 'aggregates' => []
+ * ]
+ * ]
+ *
+ * Privileges can be nested using "aggregrates". Doing so means that
+ * if you assign someone the aggregrating privilege, all the
+ * sub-privileges will automatically be granted.
+ *
+ * Marking a privilege as abstract means that the privilege cannot be
+ * directly assigned, but must be assigned via the parent privilege.
+ *
+ * So a more complex version might look like this:
+ *
+ * [
+ * [
+ * 'privilege' => '{DAV:}read',
+ * 'abstract' => false,
+ * 'aggregates' => [
+ * [
+ * 'privilege' => '{DAV:}read-acl',
+ * 'abstract' => false,
+ * 'aggregates' => [],
+ * ]
+ * ]
+ * ]
+ * ]
*
* @param string|INode $node
* @return array
@@ -328,73 +449,71 @@ class Plugin extends DAV\ServerPlugin {
$node = $this->server->tree->getNodeForPath($node);
}
+ $supportedPrivileges = null;
if ($node instanceof IACL) {
- $result = $node->getSupportedPrivilegeSet();
-
- if ($result)
- return $result;
+ $supportedPrivileges = $node->getSupportedPrivilegeSet();
}
- return self::getDefaultSupportedPrivilegeSet();
+ if (is_null($supportedPrivileges)) {
- }
-
- /**
- * Returns a fairly standard set of privileges, which may be useful for
- * other systems to use as a basis.
- *
- * @return array
- */
- static function getDefaultSupportedPrivilegeSet() {
-
- return [
- 'privilege' => '{DAV:}all',
- 'abstract' => true,
- 'aggregates' => [
- [
- 'privilege' => '{DAV:}read',
+ // Default
+ $supportedPrivileges = [
+ '{DAV:}read' => [
+ 'abstract' => false,
'aggregates' => [
- [
- 'privilege' => '{DAV:}read-acl',
- 'abstract' => false,
+ '{DAV:}read-acl' => [
+ 'abstract' => false,
+ 'aggregates' => [],
],
- [
- 'privilege' => '{DAV:}read-current-user-privilege-set',
- 'abstract' => false,
+ '{DAV:}read-current-user-privilege-set' => [
+ 'abstract' => false,
+ 'aggregates' => [],
],
],
- ], // {DAV:}read
- [
- 'privilege' => '{DAV:}write',
+ ],
+ '{DAV:}write' => [
+ 'abstract' => false,
'aggregates' => [
- [
- 'privilege' => '{DAV:}write-acl',
- 'abstract' => false,
- ],
- [
- 'privilege' => '{DAV:}write-properties',
- 'abstract' => false,
- ],
- [
- 'privilege' => '{DAV:}write-content',
- 'abstract' => false,
+ '{DAV:}write-properties' => [
+ 'abstract' => false,
+ 'aggregates' => [],
],
- [
- 'privilege' => '{DAV:}bind',
- 'abstract' => false,
+ '{DAV:}write-content' => [
+ 'abstract' => false,
+ 'aggregates' => [],
],
- [
- 'privilege' => '{DAV:}unbind',
- 'abstract' => false,
- ],
- [
- 'privilege' => '{DAV:}unlock',
- 'abstract' => false,
+ '{DAV:}unlock' => [
+ 'abstract' => false,
+ 'aggregates' => [],
],
],
- ], // {DAV:}write
- ],
- ]; // {DAV:}all
+ ],
+ ];
+ if ($node instanceof \Sabre\DAV\ICollection) {
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+ if ($node instanceof \Sabre\DAVACL\IACL) {
+ $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
+ 'abstract' => false,
+ 'aggregates' => [],
+ ];
+ }
+
+ }
+
+ $this->server->emit(
+ 'getSupportedPrivilegeSet',
+ [$node, &$supportedPrivileges]
+ );
+
+ return $supportedPrivileges;
}
@@ -414,35 +533,38 @@ class Plugin extends DAV\ServerPlugin {
*/
final function getFlatPrivilegeSet($node) {
- $privs = $this->getSupportedPrivilegeSet($node);
+ $privs = [
+ 'abstract' => false,
+ 'aggregates' => $this->getSupportedPrivilegeSet($node)
+ ];
$fpsTraverse = null;
- $fpsTraverse = function($priv, $concrete, &$flat) use (&$fpsTraverse) {
+ $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
$myPriv = [
- 'privilege' => $priv['privilege'],
- 'abstract' => isset($priv['abstract']) && $priv['abstract'],
+ 'privilege' => $privName,
+ 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
'aggregates' => [],
- 'concrete' => isset($priv['abstract']) && $priv['abstract'] ? $concrete : $priv['privilege'],
+ 'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
];
- if (isset($priv['aggregates'])) {
+ if (isset($privInfo['aggregates'])) {
- foreach ($priv['aggregates'] as $subPriv) {
+ foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
- $myPriv['aggregates'][] = $subPriv['privilege'];
+ $myPriv['aggregates'][] = $subPrivName;
}
}
- $flat[$priv['privilege']] = $myPriv;
+ $flat[$privName] = $myPriv;
- if (isset($priv['aggregates'])) {
+ if (isset($privInfo['aggregates'])) {
- foreach ($priv['aggregates'] as $subPriv) {
+ foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
- $fpsTraverse($subPriv, $myPriv['concrete'], $flat);
+ $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);
}
@@ -451,7 +573,7 @@ class Plugin extends DAV\ServerPlugin {
};
$flat = [];
- $fpsTraverse($privs, null, $flat);
+ $fpsTraverse('{DAV:}all', $privs, null, $flat);
return $flat;
@@ -467,13 +589,13 @@ class Plugin extends DAV\ServerPlugin {
* @param string|DAV\INode $node
* @return array
*/
- function getACL($node) {
+ function getAcl($node) {
if (is_string($node)) {
$node = $this->server->tree->getNodeForPath($node);
}
if (!$node instanceof IACL) {
- return null;
+ return $this->getDefaultAcl();
}
$acl = $node->getACL();
foreach ($this->adminPrincipals as $adminPrincipal) {
@@ -506,12 +628,10 @@ class Plugin extends DAV\ServerPlugin {
$acl = $this->getACL($node);
- if (is_null($acl)) return null;
-
- $principals = $this->getCurrentUserPrincipals();
-
$collected = [];
+ $isAuthenticated = $this->getCurrentUserPrincipal() !== null;
+
foreach ($acl as $ace) {
$principal = $ace['principal'];
@@ -520,7 +640,7 @@ class Plugin extends DAV\ServerPlugin {
case '{DAV:}owner' :
$owner = $node->getOwner();
- if ($owner && in_array($owner, $principals)) {
+ if ($owner && $this->principalMatchesPrincipal($owner)) {
$collected[] = $ace;
}
break;
@@ -528,21 +648,25 @@ class Plugin extends DAV\ServerPlugin {
// 'all' matches for every user
case '{DAV:}all' :
+ $collected[] = $ace;
+ break;
- // 'authenticated' matched for every user that's logged in.
- // Since it's not possible to use ACL while not being logged
- // in, this is also always true.
case '{DAV:}authenticated' :
- $collected[] = $ace;
+ // Authenticated users only
+ if ($isAuthenticated) {
+ $collected[] = $ace;
+ }
break;
- // 'unauthenticated' can never occur either, so we simply
- // ignore these.
case '{DAV:}unauthenticated' :
+ // Unauthenticated users only
+ if (!$isAuthenticated) {
+ $collected[] = $ace;
+ }
break;
default :
- if (in_array($ace['principal'], $principals)) {
+ if ($this->principalMatchesPrincipal($ace['principal'])) {
$collected[] = $ace;
}
break;
@@ -561,6 +685,11 @@ class Plugin extends DAV\ServerPlugin {
$current = array_pop($collected);
$collected2[] = $current['privilege'];
+ if (!isset($flat[$current['privilege']])) {
+ // Ignoring privileges that are not in the supported-privileges list.
+ $this->server->getLogger()->debug('A node has the "' . $current['privilege'] . '" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
+ continue;
+ }
foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
$collected2[] = $subPriv;
$collected[] = $flat[$subPriv];
@@ -587,7 +716,13 @@ class Plugin extends DAV\ServerPlugin {
$collections = $this->principalCollectionSet;
foreach ($collections as $collection) {
- $principalCollection = $this->server->tree->getNodeForPath($collection);
+ try {
+ $principalCollection = $this->server->tree->getNodeForPath($collection);
+ } catch (NotFound $e) {
+ // Ignore and move on
+ continue;
+ }
+
if (!$principalCollection instanceof IPrincipalCollection) {
// Not a principal collection, we're simply going to ignore
// this.
@@ -673,6 +808,14 @@ class Plugin extends DAV\ServerPlugin {
*/
function initialize(DAV\Server $server) {
+ if ($this->allowUnauthenticatedAccess) {
+ $authPlugin = $server->getPlugin('auth');
+ if (!$authPlugin) {
+ throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
+ }
+ $authPlugin->autoRequireLogin = false;
+ }
+
$this->server = $server;
$server->on('propFind', [$this, 'propFind'], 20);
$server->on('beforeMethod', [$this, 'beforeMethod'], 20);
@@ -683,6 +826,14 @@ class Plugin extends DAV\ServerPlugin {
$server->on('report', [$this, 'report']);
$server->on('method:ACL', [$this, 'httpAcl']);
$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('getPrincipalByUri', function($principal, &$uri) {
+
+ $uri = $this->getPrincipalByUri($principal);
+
+ // Break event chain
+ if ($uri) return false;
+
+ });
array_push($server->protectedProperties,
'{DAV:}alternate-URI-set',
@@ -707,9 +858,11 @@ class Plugin extends DAV\ServerPlugin {
// class.
$server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
$server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
+ $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
$server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
$server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
$server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
+ $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';
}
@@ -743,7 +896,6 @@ class Plugin extends DAV\ServerPlugin {
case 'PUT' :
case 'LOCK' :
- case 'UNLOCK' :
// This method requires the write-content priv if the node
// already exists, and bind on the parent if the node is being
// created.
@@ -751,6 +903,9 @@ class Plugin extends DAV\ServerPlugin {
$this->checkPrivileges($path, '{DAV:}write-content');
break;
+ case 'UNLOCK' :
+ // Unlock is always allowed at the moment.
+ break;
case 'PROPPATCH' :
$this->checkPrivileges($path, '{DAV:}write-properties');
@@ -774,7 +929,6 @@ class Plugin extends DAV\ServerPlugin {
// If MOVE is used beforeUnbind will also be used to check if
// the sourcenode can be deleted.
$this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
-
break;
}
@@ -864,24 +1018,24 @@ class Plugin extends DAV\ServerPlugin {
if ($node instanceof IPrincipal) {
$propFind->handle('{DAV:}alternate-URI-set', function() use ($node) {
- return new DAV\Xml\Property\Href($node->getAlternateUriSet());
+ return new Href($node->getAlternateUriSet());
});
$propFind->handle('{DAV:}principal-URL', function() use ($node) {
- return new DAV\Xml\Property\Href($node->getPrincipalUrl() . '/');
+ return new Href($node->getPrincipalUrl() . '/');
});
$propFind->handle('{DAV:}group-member-set', function() use ($node) {
$members = $node->getGroupMemberSet();
foreach ($members as $k => $member) {
$members[$k] = rtrim($member, '/') . '/';
}
- return new DAV\Xml\Property\Href($members);
+ return new Href($members);
});
$propFind->handle('{DAV:}group-membership', function() use ($node) {
$members = $node->getGroupMembership();
foreach ($members as $k => $member) {
$members[$k] = rtrim($member, '/') . '/';
}
- return new DAV\Xml\Property\Href($members);
+ return new Href($members);
});
$propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
@@ -892,7 +1046,7 @@ class Plugin extends DAV\ServerPlugin {
$val = $this->principalCollectionSet;
// Ensuring all collections end with a slash
foreach ($val as $k => $v) $val[$k] = $v . '/';
- return new DAV\Xml\Property\Href($val);
+ return new Href($val);
});
$propFind->handle('{DAV:}current-user-principal', function() {
@@ -910,9 +1064,7 @@ class Plugin extends DAV\ServerPlugin {
$propFind->set('{DAV:}current-user-privilege-set', null, 403);
} else {
$val = $this->getCurrentUserPrivilegeSet($node);
- if (!is_null($val)) {
- return new Xml\Property\CurrentUserPrivilegeSet($val);
- }
+ return new Xml\Property\CurrentUserPrivilegeSet($val);
}
});
$propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) {
@@ -921,9 +1073,7 @@ class Plugin extends DAV\ServerPlugin {
$propFind->set('{DAV:}acl', null, 403);
} else {
$acl = $this->getACL($node);
- if (!is_null($acl)) {
- return new Xml\Property\Acl($this->getACL($node));
- }
+ return new Xml\Property\Acl($this->getACL($node));
}
});
$propFind->handle('{DAV:}acl-restrictions', function() {
@@ -933,7 +1083,7 @@ class Plugin extends DAV\ServerPlugin {
/* Adding ACL properties */
if ($node instanceof IACL) {
$propFind->handle('{DAV:}owner', function() use ($node) {
- return new DAV\Xml\Property\Href($node->getOwner() . '/');
+ return new Href($node->getOwner() . '/');
});
}
@@ -952,7 +1102,7 @@ class Plugin extends DAV\ServerPlugin {
$propPatch->handle('{DAV:}group-member-set', function($value) use ($path) {
if (is_null($value)) {
$memberSet = [];
- } elseif ($value instanceof DAV\Xml\Property\Href) {
+ } elseif ($value instanceof Href) {
$memberSet = array_map(
[$this->server, 'calculateUri'],
$value->getHrefs()
@@ -990,15 +1140,23 @@ class Plugin extends DAV\ServerPlugin {
case '{DAV:}principal-property-search' :
$this->server->transactionType = 'report-principal-property-search';
- $this->principalPropertySearchReport($report);
+ $this->principalPropertySearchReport($path, $report);
return false;
case '{DAV:}principal-search-property-set' :
$this->server->transactionType = 'report-principal-search-property-set';
- $this->principalSearchPropertySetReport($report);
+ $this->principalSearchPropertySetReport($path, $report);
return false;
case '{DAV:}expand-property' :
$this->server->transactionType = 'report-expand-property';
- $this->expandPropertyReport($report);
+ $this->expandPropertyReport($path, $report);
+ return false;
+ case '{DAV:}principal-match' :
+ $this->server->transactionType = 'report-principal-match';
+ $this->principalMatchReport($path, $report);
+ return false;
+ case '{DAV:}acl-principal-prop-set' :
+ $this->server->transactionType = 'acl-principal-prop-set';
+ $this->aclPrincipalPropSetReport($path, $report);
return false;
}
@@ -1073,7 +1231,7 @@ class Plugin extends DAV\ServerPlugin {
// Looking up the principal
try {
$principal = $this->server->tree->getNodeForPath($newAce['principal']);
- } catch (DAV\Exception\NotFound $e) {
+ } catch (NotFound $e) {
throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist');
}
if (!($principal instanceof IPrincipal)) {
@@ -1095,7 +1253,110 @@ class Plugin extends DAV\ServerPlugin {
/* Reports {{{ */
/**
- * The expand-property report is defined in RFC3253 section 3-8.
+ * The principal-match report is defined in RFC3744, section 9.3.
+ *
+ * This report allows a client to figure out based on the current user,
+ * or a principal URL, the principal URL and principal URLs of groups that
+ * principal belongs to.
+ *
+ * @param string $path
+ * @param Xml\Request\PrincipalMatchReport $report
+ * @return void
+ */
+ protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report) {
+
+ $depth = $this->server->getHTTPDepth(0);
+ if ($depth !== 0) {
+ throw new BadRequest('The principal-match report is only defined on Depth: 0');
+ }
+
+ $currentPrincipals = $this->getCurrentUserPrincipals();
+
+ $result = [];
+
+ if ($report->type === Xml\Request\PrincipalMatchReport::SELF) {
+
+ // Finding all principals under the request uri that match the
+ // current principal.
+ foreach ($currentPrincipals as $currentPrincipal) {
+
+ if ($currentPrincipal === $path || strpos($currentPrincipal, $path . '/') === 0) {
+ $result[] = $currentPrincipal;
+ }
+
+ }
+
+ } else {
+
+ // We need to find all resources that have a property that matches
+ // one of the current principals.
+ $candidates = $this->server->getPropertiesForPath(
+ $path,
+ [$report->principalProperty],
+ 1
+ );
+
+ foreach ($candidates as $candidate) {
+
+ if (!isset($candidate[200][$report->principalProperty])) {
+ continue;
+ }
+
+ $hrefs = $candidate[200][$report->principalProperty];
+
+ if (!$hrefs instanceof Href) {
+ continue;
+ }
+
+ foreach ($hrefs->getHrefs() as $href) {
+ if (in_array(trim($href, '/'), $currentPrincipals)) {
+ $result[] = $candidate['href'];
+ continue 2;
+ }
+ }
+ }
+
+ }
+
+ $responses = [];
+
+ foreach ($result as $item) {
+
+ $properties = [];
+
+ if ($report->properties) {
+
+ $foo = $this->server->getPropertiesForPath($item, $report->properties);
+ $foo = $foo[0];
+ $item = $foo['href'];
+ unset($foo['href']);
+ $properties = $foo;
+
+ }
+
+ $responses[] = new DAV\Xml\Element\Response(
+ $item,
+ $properties,
+ '200'
+ );
+
+ }
+
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setBody(
+ $this->server->xml->write(
+ '{DAV:}multistatus',
+ $responses,
+ $this->server->getBaseUri()
+ )
+ );
+
+
+ }
+
+ /**
+ * The expand-property report is defined in RFC3253 section 3.8.
*
* This report is very similar to a standard PROPFIND. The difference is
* that it has the additional ability to look at properties containing a
@@ -1105,15 +1366,15 @@ class Plugin extends DAV\ServerPlugin {
* Other rfc's, such as ACL rely on this report, so it made sense to put
* it in this plugin.
*
+ * @param string $path
* @param Xml\Request\ExpandPropertyReport $report
* @return void
*/
- protected function expandPropertyReport($report) {
+ protected function expandPropertyReport($path, $report) {
$depth = $this->server->getHTTPDepth(0);
- $requestUri = $this->server->getRequestUri();
- $result = $this->expandProperties($requestUri, $report->properties, $depth);
+ $result = $this->expandProperties($path, $report->properties, $depth);
$xml = $this->server->xml->write(
'{DAV:}multistatus',
@@ -1187,10 +1448,11 @@ class Plugin extends DAV\ServerPlugin {
* of properties the client may search on, using the
* {DAV:}principal-property-search report.
*
+ * @param string $path
* @param Xml\Request\PrincipalSearchPropertySetReport $report
* @return void
*/
- protected function principalSearchPropertySetReport($report) {
+ protected function principalSearchPropertySetReport($path, $report) {
$httpDepth = $this->server->getHTTPDepth(0);
if ($httpDepth !== 0) {
@@ -1241,14 +1503,14 @@ class Plugin extends DAV\ServerPlugin {
* clients to search for groups of principals, based on the value of one
* or more properties.
*
+ * @param string $path
* @param Xml\Request\PrincipalPropertySearchReport $report
* @return void
*/
- protected function principalPropertySearchReport($report) {
+ protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report) {
- $uri = null;
- if (!$report->applyToPrincipalCollectionSet) {
- $uri = $this->server->httpRequest->getPath();
+ if ($report->applyToPrincipalCollectionSet) {
+ $path = null;
}
if ($this->server->getHttpDepth('0') !== 0) {
throw new BadRequest('Depth must be 0');
@@ -1256,7 +1518,7 @@ class Plugin extends DAV\ServerPlugin {
$result = $this->principalSearch(
$report->searchProperties,
$report->properties,
- $uri,
+ $path,
$report->test
);
@@ -1269,6 +1531,64 @@ class Plugin extends DAV\ServerPlugin {
}
+ /**
+ * aclPrincipalPropSet REPORT
+ *
+ * This method is responsible for handling the {DAV:}acl-principal-prop-set
+ * REPORT, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc3744#section-9.2
+ *
+ * This REPORT allows a user to quickly fetch information about all
+ * principals specified in the access control list. Most commonly this
+ * is used to for example generate a UI with ACL rules, allowing you
+ * to show names for principals for every entry.
+ *
+ * @param string $path
+ * @param Xml\Request\AclPrincipalPropSetReport $report
+ * @return void
+ */
+ protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report) {
+
+ if ($this->server->getHTTPDepth(0) !== 0) {
+ throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
+ }
+
+ // Fetching ACL rules for the given path. We're using the property
+ // API and not the local getACL, because it will ensure that all
+ // business rules and restrictions are applied.
+ $acl = $this->server->getProperties($path, '{DAV:}acl');
+
+ if (!$acl || !isset($acl['{DAV:}acl'])) {
+ throw new Forbidden('Could not fetch ACL rules for this path');
+ }
+
+ $principals = [];
+ foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
+
+ if ($ace['principal'][0] === '{') {
+ // It's not a principal, it's one of the special rules such as {DAV:}authenticated
+ continue;
+ }
+
+ $principals[] = $ace['principal'];
+
+ }
+
+ $properties = $this->server->getPropertiesForMultiplePaths(
+ $principals,
+ $report->properties
+ );
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setBody(
+ $this->server->generateMultiStatus($properties)
+ );
+
+ }
+
+
/* }}} */
/**
diff --git a/vendor/sabre/dav/lib/DAVACL/Principal.php b/vendor/sabre/dav/lib/DAVACL/Principal.php
index 16375d3fc..6ebb30907 100644
--- a/vendor/sabre/dav/lib/DAVACL/Principal.php
+++ b/vendor/sabre/dav/lib/DAVACL/Principal.php
@@ -22,6 +22,8 @@ use Sabre\HTTP\URLUtil;
*/
class Principal extends DAV\Node implements IPrincipal, DAV\IProperties, IACL {
+ use ACLTrait;
+
/**
* Struct with principal information.
*
@@ -216,73 +218,4 @@ class Principal extends DAV\Node implements IPrincipal, DAV\IProperties, IACL {
}
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
-
- return null;
-
- }
-
- /**
- * Returns a list of ACE's for this node.
- *
- * Each ACE has the following properties:
- * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
- * currently the only supported privileges
- * * 'principal', a url to the principal who owns the node
- * * 'protected' (optional), indicating that this ACE is not allowed to
- * be updated.
- *
- * @return array
- */
- function getACL() {
-
- return [
- [
- 'privilege' => '{DAV:}read',
- 'principal' => '{DAV:}authenticated',
- 'protected' => true,
- ],
- ];
-
- }
-
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new DAV\Exception\MethodNotAllowed('Updating ACLs is not allowed here');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php
index 01b7a175c..a491dc88f 100644
--- a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php
+++ b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php
@@ -300,7 +300,7 @@ class PDO extends AbstractBackend implements CreatePrincipalSupport {
$value = null;
$scheme = null;
list($scheme, $value) = explode(":", $uri, 2);
- if ($value == null) return null;
+ if (empty($value)) return null;
$uri = null;
switch ($scheme){
diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php
index 54911e7b5..d8a90153a 100644
--- a/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php
+++ b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php
@@ -3,7 +3,6 @@
namespace Sabre\DAVACL;
use Sabre\DAV\Exception\InvalidResourceType;
-use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IExtendedCollection;
use Sabre\DAV\MkCol;
@@ -19,6 +18,8 @@ use Sabre\DAV\MkCol;
*/
class PrincipalCollection extends AbstractPrincipalCollection implements IExtendedCollection, IACL {
+ use ACLTrait;
+
/**
* This method returns a node for a principal.
*
@@ -73,28 +74,6 @@ class PrincipalCollection extends AbstractPrincipalCollection implements IExtend
}
/**
- * Returns the owner principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getOwner() {
- return null;
- }
-
- /**
- * Returns a group principal
- *
- * This must be a url to a principal, or null if there's no owner
- *
- * @return string|null
- */
- function getGroup() {
- return null;
- }
-
- /**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
@@ -116,36 +95,4 @@ class PrincipalCollection extends AbstractPrincipalCollection implements IExtend
];
}
- /**
- * Updates the ACL
- *
- * This method will receive a list of new ACE's as an array argument.
- *
- * @param array $acl
- * @return void
- */
- function setACL(array $acl) {
-
- throw new Forbidden('Updating ACLs is not allowed on this node');
-
- }
-
- /**
- * Returns the list of supported privileges for this node.
- *
- * The returned data structure is a list of nested privileges.
- * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
- * standard structure.
- *
- * If null is returned from this method, the default privilege set is used,
- * which is fine for most common usecases.
- *
- * @return array|null
- */
- function getSupportedPrivilegeSet() {
-
- return null;
-
- }
-
}
diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php
index 572bed4dd..55e7783ae 100644
--- a/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php
+++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php
@@ -73,7 +73,7 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
*/
function xmlSerialize(Writer $writer) {
- $this->serializePriv($writer, $this->privileges);
+ $this->serializePriv($writer, '{DAV:}all', [ 'aggregates' => $this->privileges]);
}
@@ -93,9 +93,9 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
*/
function toHtml(HtmlOutputHelper $html) {
- $traverse = function($priv) use (&$traverse, $html) {
+ $traverse = function($privName, $priv) use (&$traverse, $html) {
echo "<li>";
- echo $html->xmlName($priv['privilege']);
+ echo $html->xmlName($privName);
if (isset($priv['abstract']) && $priv['abstract']) {
echo " <i>(abstract)</i>";
}
@@ -104,8 +104,8 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
}
if (isset($priv['aggregates'])) {
echo "\n<ul>\n";
- foreach ($priv['aggregates'] as $subPriv) {
- $traverse($subPriv);
+ foreach ($priv['aggregates'] as $subPrivName => $subPriv) {
+ $traverse($subPrivName, $subPriv);
}
echo "</ul>";
}
@@ -114,7 +114,7 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
ob_start();
echo "<ul class=\"tree\">";
- $traverse($this->getValue());
+ $traverse('{DAV:}all', [ 'aggregates' => $this->getValue() ]);
echo "</ul>\n";
return ob_get_clean();
@@ -132,12 +132,12 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
* @param array $privilege
* @return void
*/
- private function serializePriv(Writer $writer, $privilege) {
+ private function serializePriv(Writer $writer, $privName, $privilege) {
$writer->startElement('{DAV:}supported-privilege');
$writer->startElement('{DAV:}privilege');
- $writer->writeElement($privilege['privilege']);
+ $writer->writeElement($privName);
$writer->endElement(); // privilege
if (!empty($privilege['abstract'])) {
@@ -147,8 +147,8 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {
$writer->writeElement('{DAV:}description', $privilege['description']);
}
if (isset($privilege['aggregates'])) {
- foreach ($privilege['aggregates'] as $subPrivilege) {
- $this->serializePriv($writer, $subPrivilege);
+ foreach ($privilege['aggregates'] as $subPrivName => $subPrivilege) {
+ $this->serializePriv($writer, $subPrivName, $subPrivilege);
}
}
diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php
new file mode 100644
index 000000000..f01c1e6ab
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Sabre\DAVACL\Xml\Request;
+
+use Sabre\Xml\XmlDeserializable;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Deserializer;
+
+/**
+ * AclPrincipalPropSet request parser.
+ *
+ * This class parses the {DAV:}acl-principal-prop-set REPORT, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc3744#section-9.2
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (https://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AclPrincipalPropSetReport implements XmlDeserializable {
+
+ public $properties = [];
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, 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.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
+
+ $elems = Deserializer\keyValue(
+ $reader,
+ 'DAV:'
+ );
+
+ $reader->popContext();
+
+ $report = new self();
+
+ if (!empty($elems['prop'])) {
+ $report->properties = $elems['prop'];
+ }
+
+ return $report;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php
new file mode 100644
index 000000000..5c4e88189
--- /dev/null
+++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Sabre\DAVACL\Xml\Request;
+
+use Sabre\Xml\XmlDeserializable;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Deserializer;
+
+/**
+ * PrincipalMatchReport request parser.
+ *
+ * This class parses the {DAV:}principal-match REPORT, as defined
+ * in:
+ *
+ * https://tools.ietf.org/html/rfc3744#section-9.3
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PrincipalMatchReport implements XmlDeserializable {
+
+ /**
+ * Report on a list of principals that match the current principal.
+ */
+ const SELF = 1;
+
+ /**
+ * Report on a property on resources, such as {DAV:}owner, that match the current principal.
+ */
+ const PRINCIPAL_PROPERTY = 2;
+
+ /**
+ * Must be SELF or PRINCIPAL_PROPERTY
+ *
+ * @var int
+ */
+ public $type;
+
+ /**
+ * List of properties that are being requested for matching resources.
+ *
+ * @var string[]
+ */
+ public $properties = [];
+
+ /**
+ * If $type = PRINCIPAL_PROPERTY, which WebDAV property we should compare
+ * to the current principal.
+ *
+ * @var string
+ */
+ public $principalProperty;
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, 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.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $reader->pushContext();
+ $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
+
+ $elems = Deserializer\keyValue(
+ $reader,
+ 'DAV:'
+ );
+
+ $reader->popContext();
+
+ $principalMatch = new self();
+
+ if (array_key_exists('self', $elems)) {
+ $principalMatch->type = self::SELF;
+ }
+
+ if (array_key_exists('principal-property', $elems)) {
+ $principalMatch->type = self::PRINCIPAL_PROPERTY;
+ $principalMatch->principalProperty = $elems['principal-property'][0]['name'];
+ }
+
+ if (!empty($elems['prop'])) {
+ $principalMatch->properties = $elems['prop'];
+ }
+
+ return $principalMatch;
+
+ }
+
+}