mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
Merge pull request #60968 from nextcloud/backport/60648/stable34
[stable34] fix: access shared and delegated trashbin objects
This commit is contained in:
commit
a1bc63779f
4 changed files with 336 additions and 31 deletions
|
|
@ -1190,44 +1190,202 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
}
|
||||
|
||||
/**
|
||||
* Return all deleted calendar objects by the given principal that are not
|
||||
* in deleted calendars.
|
||||
* Return all deleted calendar objects accessible to the given principal:
|
||||
* - Calendars owned by the principal.
|
||||
* - Calendars shared with the principal.
|
||||
* - Calendars owned by users who delegated the principal (calendar-proxy-*),
|
||||
* plus calendars shared with those delegators (transitively).
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDeletedCalendarObjectsByPrincipal(string $principalUri): array {
|
||||
$result = [];
|
||||
$this->collectDeletedCalendarObjectsForPrincipal($principalUri, $result, null);
|
||||
foreach ($this->getProxyDelegators($principalUri) as $delegator => $hasProxyWrite) {
|
||||
$overlay = $hasProxyWrite ? Backend::ACCESS_READ_WRITE : Backend::ACCESS_READ;
|
||||
$this->collectDeletedCalendarObjectsForPrincipal($delegator, $result, $overlay);
|
||||
}
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the owned + shared trashbin queries for $principalUri and merge the
|
||||
* results into $result, keyed by calendar object id.
|
||||
*
|
||||
* @param string $principalUri principal whose calendars to scan.
|
||||
* @param array $result accumulator keyed by calendar object id; merged in-place.
|
||||
* @param int|null $proxyOverlay if non-null, the entries are being collected on
|
||||
* behalf of a different accessor via calendar-proxy; the value caps the
|
||||
* effective share access for that accessor (READ_WRITE for proxy-write,
|
||||
* READ for proxy-read). null means $principalUri is the accessor itself.
|
||||
*/
|
||||
private function collectDeletedCalendarObjectsForPrincipal(string $principalUri, array &$result, ?int $proxyOverlay): void {
|
||||
[$principalUri, $principals] = $this->resolvePrincipal($principalUri);
|
||||
|
||||
// Owned calendars
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.componenttype', 'co.classification', 'co.deleted_at'])
|
||||
->selectAlias('c.uri', 'calendaruri')
|
||||
->selectAlias('c.principaluri', 'calendarprincipaluri')
|
||||
->from('calendarobjects', 'co')
|
||||
->join('co', 'calendars', 'c', $query->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
|
||||
->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
|
||||
->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
|
||||
->andWhere($query->expr()->isNotNull('co.deleted_at'))
|
||||
->andWhere($query->expr()->isNull('c.deleted_at'));
|
||||
$stmt = $query->executeQuery();
|
||||
|
||||
$result = [];
|
||||
while ($row = $stmt->fetchAssociative()) {
|
||||
$result[] = [
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'lastmodified' => $row['lastmodified'],
|
||||
'etag' => '"' . $row['etag'] . '"',
|
||||
'calendarid' => $row['calendarid'],
|
||||
'calendaruri' => $row['calendaruri'],
|
||||
'size' => (int)$row['size'],
|
||||
'component' => strtolower($row['componenttype']),
|
||||
'classification' => (int)$row['classification'],
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'],
|
||||
];
|
||||
if ($this->resultHasMorePermissiveEntry($result, $row['id'], $proxyOverlay)) {
|
||||
continue;
|
||||
}
|
||||
[, $ownerName] = Uri\split($row['calendarprincipaluri']);
|
||||
$isDelegated = $proxyOverlay !== null;
|
||||
$calendarUri = $isDelegated ? $row['calendaruri'] . '_delegated_by_' . $ownerName : $row['calendaruri'];
|
||||
$result[$row['id']] = $this->rowToDeletedCalendarObject($row, $calendarUri, false, $proxyOverlay, $isDelegated ? $principalUri : null);
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
|
||||
// Shared calendars — multiple share rows may match (user + group, etc.),
|
||||
// so we dedupe in PHP keeping the most permissive effective access.
|
||||
$select = $this->db->getQueryBuilder();
|
||||
$select->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.componenttype', 'co.classification', 'co.deleted_at'])
|
||||
->selectAlias('c.uri', 'calendaruri')
|
||||
->selectAlias('c.principaluri', 'calendarprincipaluri')
|
||||
->selectAlias('s.access', 'shareaccess')
|
||||
->from('calendarobjects', 'co')
|
||||
->join('co', 'calendars', 'c', $select->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
|
||||
->andWhere($select->expr()->isNotNull('co.deleted_at'))
|
||||
->andWhere($select->expr()->isNull('c.deleted_at'));
|
||||
$this->applySharedCalendarFilters($select, $principals, $principalUri);
|
||||
|
||||
$stmt = $select->executeQuery();
|
||||
while ($row = $stmt->fetchAssociative()) {
|
||||
$effective = $this->effectiveAccess((int)$row['shareaccess'], $proxyOverlay);
|
||||
if ($this->resultHasMorePermissiveEntry($result, $row['id'], $effective)) {
|
||||
continue;
|
||||
}
|
||||
[, $ownerName] = Uri\split($row['calendarprincipaluri']);
|
||||
$result[$row['id']] = $this->rowToDeletedCalendarObject($row, $row['calendaruri'] . '_shared_by_' . $ownerName, false, $effective, null);
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective access for an entry surfaced via a proxy delegator.
|
||||
* Lower int = more permissive (READ_WRITE=2, READ=3); the more restrictive of
|
||||
* the share access and the proxy overlay wins (max of the two ints).
|
||||
*/
|
||||
private function effectiveAccess(int $shareAccess, ?int $proxyOverlay): int {
|
||||
if ($proxyOverlay === null) {
|
||||
return $shareAccess;
|
||||
}
|
||||
return max($shareAccess, $proxyOverlay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,array<string,mixed>> $result keyed by object id.
|
||||
* @param int|string $id the candidate row id.
|
||||
* @param int|null $candidateAccess effective access of the candidate row.
|
||||
* Owned/no-overlay rows pass null and always win over null entries.
|
||||
*/
|
||||
private function resultHasMorePermissiveEntry(array $result, int|string $id, ?int $candidateAccess): bool {
|
||||
$existing = $result[$id] ?? null;
|
||||
if ($existing === null) {
|
||||
return false;
|
||||
}
|
||||
$existingAccess = $existing['shared_access'] ?? null;
|
||||
if ($existingAccess === null) {
|
||||
// Owned-by-accessor (no overlay) is the most permissive; keep it.
|
||||
return true;
|
||||
}
|
||||
if ($candidateAccess === null) {
|
||||
// Candidate is owned-by-accessor; replace.
|
||||
return false;
|
||||
}
|
||||
return $existingAccess <= $candidateAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the principals (users) for whom $principalUri acts as a calendar
|
||||
* proxy. The value is true for proxy-write, false for proxy-read.
|
||||
*
|
||||
* @return array<string,bool> map of delegator-principal => has-write-proxy
|
||||
*/
|
||||
private function getProxyDelegators(string $principalUri): array {
|
||||
$memberships = $this->principalBackend->getGroupMembership($principalUri, true);
|
||||
$delegators = [];
|
||||
foreach ($memberships as $membership) {
|
||||
if (str_ends_with($membership, '/calendar-proxy-write')) {
|
||||
$delegator = substr($membership, 0, -strlen('/calendar-proxy-write'));
|
||||
$delegators[$delegator] = true;
|
||||
} elseif (str_ends_with($membership, '/calendar-proxy-read')) {
|
||||
$delegator = substr($membership, 0, -strlen('/calendar-proxy-read'));
|
||||
$delegators[$delegator] ??= false;
|
||||
}
|
||||
}
|
||||
return $delegators;
|
||||
}
|
||||
|
||||
private function rowToDeletedCalendarObject(array $row, string $calendarUri, bool $includeData = false, ?int $sharedAccess = null, ?string $delegator = null): array {
|
||||
$deletedAt = isset($row['deleted_at']) ? (int)$row['deleted_at'] : null;
|
||||
$result = [
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'lastmodified' => $row['lastmodified'],
|
||||
'etag' => '"' . $row['etag'] . '"',
|
||||
'calendarid' => $row['calendarid'],
|
||||
'calendaruri' => $calendarUri,
|
||||
'sourcecalendaruri' => $row['calendaruri'],
|
||||
'calendarprincipaluri' => $row['calendarprincipaluri'],
|
||||
'size' => (int)$row['size'],
|
||||
'component' => strtolower($row['componenttype']),
|
||||
'classification' => (int)$row['classification'],
|
||||
'deleted_at' => $deletedAt,
|
||||
'delegator' => $delegator,
|
||||
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $deletedAt,
|
||||
];
|
||||
if ($sharedAccess !== null) {
|
||||
$result['shared_access'] = $sharedAccess;
|
||||
}
|
||||
if ($includeData) {
|
||||
$result['calendardata'] = $this->readBlob($row['calendardata']);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a principal URI into its converted form and all group/circle memberships.
|
||||
*
|
||||
* @return array{string, string[]} [$convertedUri, $allPrincipals]
|
||||
*/
|
||||
private function resolvePrincipal(string $principalUri): array {
|
||||
$principals = $this->principalBackend->getGroupMembership($principalUri, true);
|
||||
$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUri));
|
||||
$converted = $this->convertPrincipal($principalUri, true);
|
||||
$principals[] = $converted;
|
||||
return [$converted, $principals];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add joins and WHERE conditions to $query to restrict results to calendars
|
||||
* shared with any of $principals, excluding calendars explicitly unshared and
|
||||
* calendars owned by $principalUri (already covered by the owned query).
|
||||
*/
|
||||
private function applySharedCalendarFilters(IQueryBuilder $query, array $principals, string $principalUri): void {
|
||||
$subSelect = $this->db->getQueryBuilder();
|
||||
$subSelect->select('resourceid')
|
||||
->from('dav_shares', 'd')
|
||||
->where($subSelect->expr()->eq('d.access', $query->createNamedParameter(Backend::ACCESS_UNSHARED, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
|
||||
->andWhere($subSelect->expr()->in('d.principaluri', $query->createNamedParameter($principals, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY));
|
||||
|
||||
$query->join('c', 'dav_shares', 's', $query->expr()->eq('s.resourceid', 'c.id', IQueryBuilder::PARAM_INT))
|
||||
->andWhere($query->expr()->in('s.principaluri', $query->createNamedParameter($principals, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY))
|
||||
->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar', IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR))
|
||||
->andWhere($query->expr()->neq('c.principaluri', $query->createNamedParameter($principalUri, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($query->expr()->notIn('c.id', $query->createFunction($subSelect->getSQL()), IQueryBuilder::PARAM_INT_ARRAY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information from a single calendar object, based on it's object
|
||||
* uri.
|
||||
|
|
@ -2529,6 +2687,97 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a deleted calendar object by its ID, accessible to $principalUri
|
||||
* via ownership, sharing, or proxy delegation. Returns the sharee-facing URI
|
||||
* for shared/delegated entries.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $principalUri
|
||||
* @return array|null
|
||||
*/
|
||||
public function getDeletedCalendarObjectByIdForPrincipal(int $id, string $principalUri): ?array {
|
||||
// Visit every accessible path (self + delegators) and keep the most
|
||||
// permissive row, so canModify() doesn't get a read-only view when a
|
||||
// write path also exists.
|
||||
$candidates = [];
|
||||
$row = $this->findDeletedCalendarObjectForPrincipal($id, $principalUri, null);
|
||||
if ($row !== null) {
|
||||
$candidates[$id] = $row;
|
||||
}
|
||||
foreach ($this->getProxyDelegators($principalUri) as $delegator => $hasProxyWrite) {
|
||||
$overlay = $hasProxyWrite ? Backend::ACCESS_READ_WRITE : Backend::ACCESS_READ;
|
||||
$row = $this->findDeletedCalendarObjectForPrincipal($id, $delegator, $overlay);
|
||||
if ($row === null) {
|
||||
continue;
|
||||
}
|
||||
if ($this->resultHasMorePermissiveEntry($candidates, $id, $row['shared_access'] ?? null)) {
|
||||
continue;
|
||||
}
|
||||
$candidates[$id] = $row;
|
||||
}
|
||||
return $candidates[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single deleted calendar object by id for $principalUri.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $principalUri
|
||||
* @param int|null $proxyOverlay see collectDeletedCalendarObjectsForPrincipal.
|
||||
* @return array|null
|
||||
*/
|
||||
private function findDeletedCalendarObjectForPrincipal(int $id, string $principalUri, ?int $proxyOverlay): ?array {
|
||||
[$principalUri, $principals] = $this->resolvePrincipal($principalUri);
|
||||
|
||||
// Check owned calendars first
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.calendardata', 'co.componenttype', 'co.classification', 'co.deleted_at'])
|
||||
->selectAlias('c.uri', 'calendaruri')
|
||||
->selectAlias('c.principaluri', 'calendarprincipaluri')
|
||||
->from('calendarobjects', 'co')
|
||||
->join('co', 'calendars', 'c', $query->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
|
||||
->where($query->expr()->eq('co.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
|
||||
->andWhere($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
|
||||
->andWhere($query->expr()->isNotNull('co.deleted_at'));
|
||||
$stmt = $query->executeQuery();
|
||||
$row = $stmt->fetchAssociative();
|
||||
$stmt->closeCursor();
|
||||
|
||||
if ($row) {
|
||||
[, $ownerName] = Uri\split($row['calendarprincipaluri']);
|
||||
$isDelegated = $proxyOverlay !== null ;
|
||||
$calendarUri = $isDelegated ? $row['calendaruri'] . '_delegated_by_' . $ownerName : $row['calendaruri'];
|
||||
return $this->rowToDeletedCalendarObject($row, $calendarUri, true, $proxyOverlay, $isDelegated ? $principalUri : null);
|
||||
}
|
||||
|
||||
// Check shared calendars; order by access ASC so the most permissive
|
||||
// row wins when the principal matches multiple share entries.
|
||||
$select = $this->db->getQueryBuilder();
|
||||
$select->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.calendardata', 'co.componenttype', 'co.classification', 'co.deleted_at'])
|
||||
->selectAlias('c.uri', 'calendaruri')
|
||||
->selectAlias('c.principaluri', 'calendarprincipaluri')
|
||||
->selectAlias('s.access', 'shareaccess')
|
||||
->from('calendarobjects', 'co')
|
||||
->join('co', 'calendars', 'c', $select->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
|
||||
->andWhere($select->expr()->eq('co.id', $select->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
|
||||
->andWhere($select->expr()->isNotNull('co.deleted_at'))
|
||||
->orderBy('s.access', 'ASC');
|
||||
$this->applySharedCalendarFilters($select, $principals, $principalUri);
|
||||
|
||||
$stmt = $select->executeQuery();
|
||||
$row = $stmt->fetchAssociative();
|
||||
$stmt->closeCursor();
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$effective = $this->effectiveAccess((int)$row['shareaccess'], $proxyOverlay);
|
||||
[, $ownerName] = Uri\split($row['calendarprincipaluri']);
|
||||
return $this->rowToDeletedCalendarObject($row, $row['calendaruri'] . '_shared_by_' . $ownerName, true, $effective, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getChanges method returns all the changes that have happened, since
|
||||
* the specified syncToken in the specified calendar.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace OCA\DAV\CalDAV\Trashbin;
|
|||
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\IRestorable;
|
||||
use OCA\DAV\DAV\Sharing\Backend;
|
||||
use Sabre\CalDAV\ICalendarObject;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAVACL\ACLTrait;
|
||||
|
|
@ -29,6 +30,9 @@ class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
|
|||
|
||||
#[\Override]
|
||||
public function delete() {
|
||||
if (!$this->canModify()) {
|
||||
throw new Forbidden('Read-only sharees cannot permanently delete trashbin entries');
|
||||
}
|
||||
$this->calDavBackend->deleteCalendarObject(
|
||||
$this->objectData['calendarid'],
|
||||
$this->objectData['uri'],
|
||||
|
|
@ -37,6 +41,19 @@ class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
|
|||
);
|
||||
}
|
||||
|
||||
private function isShared(): bool {
|
||||
$calendarOwner = $this->objectData['calendarprincipaluri'] ?? null;
|
||||
return $calendarOwner !== null && $calendarOwner !== $this->principalUri;
|
||||
}
|
||||
|
||||
private function canModify(): bool {
|
||||
if (!$this->isShared()) {
|
||||
return true;
|
||||
}
|
||||
// For shared entries, only write sharees may delete/restore.
|
||||
return ($this->objectData['shared_access'] ?? null) === Backend::ACCESS_READ_WRITE;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
|
|
@ -84,6 +101,9 @@ class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
|
|||
|
||||
#[\Override]
|
||||
public function restore(): void {
|
||||
if (!$this->canModify()) {
|
||||
throw new Forbidden('Read-only sharees cannot restore trashbin entries');
|
||||
}
|
||||
$this->calDavBackend->restoreCalendarObject($this->objectData);
|
||||
}
|
||||
|
||||
|
|
@ -95,21 +115,28 @@ class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
|
|||
return $this->objectData['calendaruri'];
|
||||
}
|
||||
|
||||
public function getSourceCalendarUri(): string {
|
||||
return $this->objectData['sourcecalendaruri'] ?? $this->objectData['calendaruri'];
|
||||
}
|
||||
|
||||
public function getCalendarPrincipalUri(): ?string {
|
||||
return $this->objectData['calendarprincipaluri'] ?? null;
|
||||
}
|
||||
|
||||
public function getDelegator(): ?string {
|
||||
return $this->objectData['delegator'] ?? null;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getACL(): array {
|
||||
return [
|
||||
$acl = [
|
||||
[
|
||||
'privilege' => '{DAV:}read', // For queries
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}unbind', // For moving and deletion
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
'privilege' => '{DAV:}all',
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
],
|
||||
|
|
@ -119,6 +146,23 @@ class DeletedCalendarObject implements IACL, ICalendarObject, IRestorable {
|
|||
'protected' => true,
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->canModify()) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}unbind',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
];
|
||||
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}unbind',
|
||||
'principal' => $this->getOwner() . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ use Sabre\DAV\Exception\NotFound;
|
|||
use Sabre\DAV\Exception\NotImplemented;
|
||||
use Sabre\DAVACL\ACLTrait;
|
||||
use Sabre\DAVACL\IACL;
|
||||
use function array_map;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
|
||||
|
|
@ -46,12 +45,11 @@ class DeletedCalendarObjectsCollection implements ICalendarObjectContainer, IACL
|
|||
throw new NotFound();
|
||||
}
|
||||
|
||||
$data = $this->caldavBackend->getCalendarObjectById(
|
||||
$this->principalInfo['uri'],
|
||||
$data = $this->caldavBackend->getDeletedCalendarObjectByIdForPrincipal(
|
||||
(int)$matches[1],
|
||||
$this->principalInfo['uri'],
|
||||
);
|
||||
|
||||
// If the object hasn't been deleted yet then we don't want to find it here
|
||||
if ($data === null) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
|
@ -110,9 +108,10 @@ class DeletedCalendarObjectsCollection implements ICalendarObjectContainer, IACL
|
|||
|
||||
#[\Override]
|
||||
public function calendarQuery(array $filters) {
|
||||
return array_map(function (array $calendarObjectInfo) {
|
||||
return $this->getRelativeObjectPath($calendarObjectInfo);
|
||||
}, $this->caldavBackend->getDeletedCalendarObjectsByPrincipal($this->principalInfo['uri']));
|
||||
return array_map(
|
||||
fn (array $obj) => $this->getRelativeObjectPath($obj),
|
||||
$this->caldavBackend->getDeletedCalendarObjectsByPrincipal($this->principalInfo['uri']),
|
||||
);
|
||||
}
|
||||
|
||||
private function getRelativeObjectPath(array $calendarInfo): string {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ use function implode;
|
|||
class Plugin extends ServerPlugin {
|
||||
public const PROPERTY_DELETED_AT = '{http://nextcloud.com/ns}deleted-at';
|
||||
public const PROPERTY_CALENDAR_URI = '{http://nextcloud.com/ns}calendar-uri';
|
||||
public const PROPERTY_SOURCE_CALENDAR_URI = '{http://nextcloud.com/ns}source-calendar-uri';
|
||||
public const PROPERTY_CALENDAR_OWNER_PRINCIPAL_URI = '{http://nextcloud.com/ns}calendar-owner-principal-uri';
|
||||
public const PROPERTY_RETENTION_DURATION = '{http://nextcloud.com/ns}trash-bin-retention-duration';
|
||||
public const PROPERTY_DELEGATOR = '{http://nextcloud.com/ns}delegator';
|
||||
|
||||
/** @var bool */
|
||||
private $disableTrashbin;
|
||||
|
|
@ -97,6 +100,16 @@ class Plugin extends ServerPlugin {
|
|||
$propFind->handle(self::PROPERTY_CALENDAR_URI, function () use ($node) {
|
||||
return $node->getCalendarUri();
|
||||
});
|
||||
// needed in case of delegated or shared calendars
|
||||
$propFind->handle(self::PROPERTY_SOURCE_CALENDAR_URI, function () use ($node) {
|
||||
return $node->getSourceCalendarUri();
|
||||
});
|
||||
$propFind->handle(self::PROPERTY_CALENDAR_OWNER_PRINCIPAL_URI, function () use ($node) {
|
||||
return $node->getCalendarPrincipalUri();
|
||||
});
|
||||
$propFind->handle(self::PROPERTY_DELEGATOR, function () use ($node) {
|
||||
return $node->getDelegator();
|
||||
});
|
||||
}
|
||||
if ($node instanceof TrashbinHome) {
|
||||
$propFind->handle(self::PROPERTY_RETENTION_DURATION, function () use ($node) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue