aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/CalDAV/CalDavBackend.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/CalDAV/CalDavBackend.php')
-rw-r--r--apps/dav/lib/CalDAV/CalDavBackend.php726
1 files changed, 479 insertions, 247 deletions
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
index cc6c4344c3c..d5b0d875ede 100644
--- a/apps/dav/lib/CalDAV/CalDavBackend.php
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -9,6 +10,7 @@ namespace OCA\DAV\CalDAV;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
+use Generator;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\Sharing\Backend;
use OCA\DAV\Connector\Sabre\Principal;
@@ -19,12 +21,6 @@ use OCA\DAV\Events\CachedCalendarObjectUpdatedEvent;
use OCA\DAV\Events\CalendarCreatedEvent;
use OCA\DAV\Events\CalendarDeletedEvent;
use OCA\DAV\Events\CalendarMovedToTrashEvent;
-use OCA\DAV\Events\CalendarObjectCreatedEvent;
-use OCA\DAV\Events\CalendarObjectDeletedEvent;
-use OCA\DAV\Events\CalendarObjectMovedEvent;
-use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
-use OCA\DAV\Events\CalendarObjectRestoredEvent;
-use OCA\DAV\Events\CalendarObjectUpdatedEvent;
use OCA\DAV\Events\CalendarPublishedEvent;
use OCA\DAV\Events\CalendarRestoredEvent;
use OCA\DAV\Events\CalendarShareUpdatedEvent;
@@ -34,6 +30,13 @@ use OCA\DAV\Events\SubscriptionCreatedEvent;
use OCA\DAV\Events\SubscriptionDeletedEvent;
use OCA\DAV\Events\SubscriptionUpdatedEvent;
use OCP\AppFramework\Db\TTransactional;
+use OCP\Calendar\CalendarExportOptions;
+use OCP\Calendar\Events\CalendarObjectCreatedEvent;
+use OCP\Calendar\Events\CalendarObjectDeletedEvent;
+use OCP\Calendar\Events\CalendarObjectMovedEvent;
+use OCP\Calendar\Events\CalendarObjectMovedToTrashEvent;
+use OCP\Calendar\Events\CalendarObjectRestoredEvent;
+use OCP\Calendar\Events\CalendarObjectUpdatedEvent;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -65,6 +68,8 @@ use Sabre\VObject\ParseException;
use Sabre\VObject\Property;
use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;
+use Sabre\VObject\Recur\MaxInstancesExceededException;
+use Sabre\VObject\Recur\NoInstancesException;
use function array_column;
use function array_map;
use function array_merge;
@@ -86,6 +91,19 @@ use function time;
* Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
*
* @package OCA\DAV\CalDAV
+ *
+ * @psalm-type CalendarInfo = array{
+ * id: int,
+ * uri: string,
+ * principaluri: string,
+ * '{http://calendarserver.org/ns/}getctag': string,
+ * '{http://sabredav.org/ns}sync-token': int,
+ * '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet,
+ * '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': \Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp,
+ * '{DAV:}displayname': string,
+ * '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string,
+ * '{http://nextcloud.com/ns}owner-displayname': string,
+ * }
*/
class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
use TTransactional;
@@ -176,7 +194,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*/
protected array $userDisplayNames;
+ private string $dbObjectsTable = 'calendarobjects';
private string $dbObjectPropertiesTable = 'calendarobjects_props';
+ private string $dbObjectInvitationsTable = 'calendar_invitations';
private array $cachedObjects = [];
public function __construct(
@@ -193,15 +213,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
- * Return the number of calendars for a principal
+ * Return the number of calendars owned by the given principal.
*
- * By default this excludes the automatically generated birthday calendar
+ * Calendars shared with the given principal are not counted!
*
- * @param $principalUri
- * @param bool $excludeBirthday
- * @return int
+ * By default, this excludes the automatically generated birthday calendar.
*/
- public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
+ public function getCalendarsForUserCount(string $principalUri, bool $excludeBirthday = true): int {
$principalUri = $this->convertPrincipal($principalUri, true);
$query = $this->db->getQueryBuilder();
$query->select($query->func()->count('*'))
@@ -257,8 +275,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendars = [];
while (($row = $result->fetch()) !== false) {
$calendars[] = [
- 'id' => (int) $row['id'],
- 'deleted_at' => (int) $row['deleted_at'],
+ 'id' => (int)$row['id'],
+ 'deleted_at' => (int)$row['deleted_at'],
];
}
$result->closeCursor();
@@ -318,7 +336,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendars = [];
while ($row = $result->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
$components = [];
if ($row['components']) {
$components = explode(',', $row['components']);
@@ -328,8 +346,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
@@ -352,7 +370,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$fields = array_column($this->propertyMap, 0);
$fields = array_map(function (string $field) {
- return 'a.'.$field;
+ return 'a.' . $field;
}, $fields);
$fields[] = 'a.id';
$fields[] = 'a.uri';
@@ -381,19 +399,19 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
while ($row = $results->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
if ($row['principaluri'] === $principalUri) {
continue;
}
- $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
+ $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
if (isset($calendars[$row['id']])) {
if ($readOnly) {
// New share can not have more permissions than the old one.
continue;
}
- if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
- $calendars[$row['id']][$readOnlyPropertyName] === 0) {
+ if (isset($calendars[$row['id']][$readOnlyPropertyName])
+ && $calendars[$row['id']][$readOnlyPropertyName] === 0) {
// Old share is already read-write, no more permissions can be gained
continue;
}
@@ -410,8 +428,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $uri,
'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
@@ -451,7 +469,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$stmt = $query->executeQuery();
$calendars = [];
while ($row = $stmt->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
$components = [];
if ($row['components']) {
$components = explode(',', $row['components']);
@@ -460,8 +478,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
];
@@ -501,7 +519,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->executeQuery();
while ($row = $result->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
[, $name] = Uri\split($row['principaluri']);
$row['displayname'] = $row['displayname'] . "($name)";
$components = [];
@@ -512,8 +530,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['publicuri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
@@ -566,7 +584,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
throw new NotFound('Node with name \'' . $uri . '\' could not be found');
}
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
[, $name] = Uri\split($row['principaluri']);
$row['displayname'] = $row['displayname'] . ' ' . "($name)";
$components = [];
@@ -577,8 +595,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['publicuri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
@@ -621,7 +639,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return null;
}
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
$components = [];
if ($row['components']) {
$components = explode(',', $row['components']);
@@ -631,8 +649,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . 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',
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
];
@@ -645,7 +663,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
- * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string }|null
+ * @psalm-return CalendarInfo|null
+ * @return array|null
*/
public function getCalendarById(int $calendarId): ?array {
$fields = array_column($this->propertyMap, 0);
@@ -669,7 +688,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return null;
}
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
$components = [];
if ($row['components']) {
$components = explode(',', $row['components']);
@@ -679,7 +698,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
- '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
+ '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'),
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?? 0,
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
@@ -717,7 +736,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return null;
}
- $row['principaluri'] = (string) $row['principaluri'];
+ $row['principaluri'] = (string)$row['principaluri'];
$subscription = [
'id' => $row['id'],
'uri' => $row['uri'],
@@ -725,7 +744,44 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'source' => $row['source'],
'lastmodified' => $row['lastmodified'],
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
- '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
+ ];
+
+ return $this->rowToSubscription($row, $subscription);
+ }
+
+ public function getSubscriptionByUri(string $principal, string $uri): ?array {
+ $fields = array_column($this->subscriptionPropertyMap, 0);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'source';
+ $fields[] = 'synctoken';
+ $fields[] = 'principaluri';
+ $fields[] = 'lastmodified';
+
+ $query = $this->db->getQueryBuilder();
+ $query->select($fields)
+ ->from('calendarsubscriptions')
+ ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
+ ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
+ ->setMaxResults(1);
+ $stmt = $query->executeQuery();
+
+ $row = $stmt->fetch();
+ $stmt->closeCursor();
+ if ($row === false) {
+ return null;
+ }
+
+ $row['principaluri'] = (string)$row['principaluri'];
+ $subscription = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ 'source' => $row['source'],
+ 'lastmodified' => $row['lastmodified'],
+ '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
];
return $this->rowToSubscription($row, $subscription);
@@ -754,7 +810,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'uri' => $calendarUri,
'synctoken' => 1,
'transparent' => 0,
- 'components' => 'VEVENT,VTODO',
+ 'components' => 'VEVENT,VTODO,VJOURNAL',
'displayname' => $calendarUri
];
@@ -773,7 +829,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
if (isset($properties[$transp])) {
- $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
+ $values['transparent'] = (int)($properties[$transp]->getValue() === 'transparent');
}
foreach ($this->propertyMap as $xmlName => [$dbName, $type]) {
@@ -826,7 +882,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
switch ($propertyName) {
case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
$fieldName = 'transparent';
- $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
+ $newValues[$fieldName] = (int)($propertyValue->getValue() === 'transparent');
break;
default:
$fieldName = $this->propertyMap[$propertyName][0];
@@ -843,7 +899,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
$query->executeStatement();
- $this->addChanges($calendarId, [""], 2);
+ $this->addChanges($calendarId, [''], 2);
$calendarData = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
@@ -863,7 +919,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function deleteCalendar($calendarId, bool $forceDeletePermanently = false) {
- $this->atomic(function () use ($calendarId, $forceDeletePermanently) {
+ $this->atomic(function () use ($calendarId, $forceDeletePermanently): void {
// The calendar is deleted right away if this is either enforced by the caller
// or the special contacts birthday calendar or when the preference of an empty
// retention (0 seconds) is set, which signals a disabled trashbin.
@@ -874,6 +930,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendarData = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
+ $this->purgeCalendarInvitations($calendarId);
+
$qbDeleteCalendarObjectProps = $this->db->getQueryBuilder();
$qbDeleteCalendarObjectProps->delete($this->dbObjectPropertiesTable)
->where($qbDeleteCalendarObjectProps->expr()->eq('calendarid', $qbDeleteCalendarObjectProps->createNamedParameter($calendarId)))
@@ -924,7 +982,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
public function restoreCalendar(int $id): void {
- $this->atomic(function () use ($id) {
+ $this->atomic(function () use ($id): void {
$qb = $this->db->getQueryBuilder();
$update = $qb->update('calendars')
->set('deleted_at', $qb->createNamedParameter(null))
@@ -945,6 +1003,81 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
+ * Returns all calendar entries as a stream of data
+ *
+ * @since 32.0.0
+ *
+ * @return Generator<array>
+ */
+ public function exportCalendar(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR, ?CalendarExportOptions $options = null): Generator {
+ // extract options
+ $rangeStart = $options?->getRangeStart();
+ $rangeCount = $options?->getRangeCount();
+ // construct query
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from('calendarobjects')
+ ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
+ ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
+ ->andWhere($qb->expr()->isNull('deleted_at'));
+ if ($rangeStart !== null) {
+ $qb->andWhere($qb->expr()->gt('uid', $qb->createNamedParameter($rangeStart)));
+ }
+ if ($rangeCount !== null) {
+ $qb->setMaxResults($rangeCount);
+ }
+ if ($rangeStart !== null || $rangeCount !== null) {
+ $qb->orderBy('uid', 'ASC');
+ }
+ $rs = $qb->executeQuery();
+ // iterate through results
+ try {
+ while (($row = $rs->fetch()) !== false) {
+ yield $row;
+ }
+ } finally {
+ $rs->closeCursor();
+ }
+ }
+
+ /**
+ * Returns all calendar objects with limited metadata for a calendar
+ *
+ * Every item contains an array with the following keys:
+ * * id - the table row id
+ * * etag - An arbitrary string
+ * * uri - a unique key which will be used to construct the uri. This can
+ * be any arbitrary string.
+ * * calendardata - The iCalendar-compatible calendar data
+ *
+ * @param mixed $calendarId
+ * @param int $calendarType
+ * @return array
+ */
+ public function getLimitedCalendarObjects(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['id','uid', 'etag', 'uri', 'calendardata'])
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
+ ->andWhere($query->expr()->isNull('deleted_at'));
+ $stmt = $query->executeQuery();
+
+ $result = [];
+ while (($row = $stmt->fetch()) !== false) {
+ $result[$row['uid']] = [
+ 'id' => $row['id'],
+ 'etag' => $row['etag'],
+ 'uri' => $row['uri'],
+ 'calendardata' => $row['calendardata'],
+ ];
+ }
+ $stmt->closeCursor();
+
+ return $result;
+ }
+
+ /**
* Delete all of an user's shares
*
* @param string $principaluri
@@ -1029,12 +1162,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
- 'calendarid' => (int) $row['calendarid'],
- 'calendartype' => (int) $row['calendartype'],
- 'size' => (int) $row['size'],
+ 'calendarid' => (int)$row['calendarid'],
+ 'calendartype' => (int)$row['calendartype'],
+ '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'],
+ 'classification' => (int)$row['classification'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'],
];
}
$stmt->closeCursor();
@@ -1073,7 +1206,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'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'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'],
];
}
$stmt->closeCursor();
@@ -1104,7 +1237,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $this->cachedObjects[$key];
}
$query = $this->db->getQueryBuilder();
- $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
+ $query->select(['id', 'uri', 'uid', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
@@ -1126,6 +1259,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return [
'id' => $row['id'],
'uri' => $row['uri'],
+ 'uid' => $row['uid'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
@@ -1133,7 +1267,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'calendardata' => $this->readBlob($row['calendardata']),
'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'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int)$row['deleted_at'],
];
}
@@ -1222,7 +1356,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
->andWhere($qb->expr()->isNull('deleted_at'));
$result = $qb->executeQuery();
- $count = (int) $result->fetchOne();
+ $count = (int)$result->fetchOne();
$result->closeCursor();
if ($count !== 0) {
@@ -1310,15 +1444,15 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $extraData, $calendarType) {
$query = $this->db->getQueryBuilder();
$query->update('calendarobjects')
- ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
- ->set('lastmodified', $query->createNamedParameter(time()))
- ->set('etag', $query->createNamedParameter($extraData['etag']))
- ->set('size', $query->createNamedParameter($extraData['size']))
- ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
- ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
- ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
- ->set('classification', $query->createNamedParameter($extraData['classification']))
- ->set('uid', $query->createNamedParameter($extraData['uid']))
+ ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
+ ->set('lastmodified', $query->createNamedParameter(time()))
+ ->set('etag', $query->createNamedParameter($extraData['etag']))
+ ->set('size', $query->createNamedParameter($extraData['size']))
+ ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
+ ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
+ ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
+ ->set('classification', $query->createNamedParameter($extraData['classification']))
+ ->set('uid', $query->createNamedParameter($extraData['uid']))
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
@@ -1348,37 +1482,40 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/**
* Moves a calendar object from calendar to calendar.
*
- * @param int $sourceCalendarId
+ * @param string $sourcePrincipalUri
+ * @param int $sourceObjectId
+ * @param string $targetPrincipalUri
* @param int $targetCalendarId
- * @param int $objectId
- * @param string $oldPrincipalUri
- * @param string $newPrincipalUri
+ * @param string $tragetObjectUri
* @param int $calendarType
* @return bool
* @throws Exception
*/
- public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, int $objectId, string $oldPrincipalUri, string $newPrincipalUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
+ public function moveCalendarObject(string $sourcePrincipalUri, int $sourceObjectId, string $targetPrincipalUri, int $targetCalendarId, string $tragetObjectUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
$this->cachedObjects = [];
- return $this->atomic(function () use ($sourceCalendarId, $targetCalendarId, $objectId, $oldPrincipalUri, $newPrincipalUri, $calendarType) {
- $object = $this->getCalendarObjectById($oldPrincipalUri, $objectId);
+ return $this->atomic(function () use ($sourcePrincipalUri, $sourceObjectId, $targetPrincipalUri, $targetCalendarId, $tragetObjectUri, $calendarType) {
+ $object = $this->getCalendarObjectById($sourcePrincipalUri, $sourceObjectId);
if (empty($object)) {
return false;
}
+ $sourceCalendarId = $object['calendarid'];
+ $sourceObjectUri = $object['uri'];
+
$query = $this->db->getQueryBuilder();
$query->update('calendarobjects')
->set('calendarid', $query->createNamedParameter($targetCalendarId, IQueryBuilder::PARAM_INT))
- ->where($query->expr()->eq('id', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->set('uri', $query->createNamedParameter($tragetObjectUri, IQueryBuilder::PARAM_STR))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($sourceObjectId, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
->executeStatement();
- $this->purgeProperties($sourceCalendarId, $objectId);
- $this->updateProperties($targetCalendarId, $object['uri'], $object['calendardata'], $calendarType);
+ $this->purgeProperties($sourceCalendarId, $sourceObjectId);
+ $this->updateProperties($targetCalendarId, $tragetObjectUri, $object['calendardata'], $calendarType);
- $this->addChanges($sourceCalendarId, [$object['uri']], 3, $calendarType);
- $this->addChanges($targetCalendarId, [$object['uri']], 1, $calendarType);
+ $this->addChanges($sourceCalendarId, [$sourceObjectUri], 3, $calendarType);
+ $this->addChanges($targetCalendarId, [$tragetObjectUri], 1, $calendarType);
- $object = $this->getCalendarObjectById($newPrincipalUri, $objectId);
+ $object = $this->getCalendarObjectById($targetPrincipalUri, $sourceObjectId);
// Calendar Object wasn't found - possibly because it was deleted in the meantime by a different client
if (empty($object)) {
return false;
@@ -1400,25 +1537,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}, $this->db);
}
-
- /**
- * @param int $calendarObjectId
- * @param int $classification
- */
- public function setClassification($calendarObjectId, $classification) {
- $this->cachedObjects = [];
- if (!in_array($classification, [
- self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
- ])) {
- throw new \InvalidArgumentException();
- }
- $query = $this->db->getQueryBuilder();
- $query->update('calendarobjects')
- ->set('classification', $query->createNamedParameter($classification))
- ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
- ->executeStatement();
- }
-
/**
* Deletes an existing calendar object.
*
@@ -1432,7 +1550,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*/
public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR, bool $forceDeletePermanently = false) {
$this->cachedObjects = [];
- $this->atomic(function () use ($calendarId, $objectUri, $calendarType, $forceDeletePermanently) {
+ $this->atomic(function () use ($calendarId, $objectUri, $calendarType, $forceDeletePermanently): void {
$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
if ($data === null) {
@@ -1446,6 +1564,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->purgeProperties($calendarId, $data['id']);
+ $this->purgeObjectInvitations($data['uid']);
+
if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
$calendarRow = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
@@ -1461,13 +1581,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
if (!empty($pathInfo['extension'])) {
// Append a suffix to "free" the old URI for recreation
$newUri = sprintf(
- "%s-deleted.%s",
+ '%s-deleted.%s',
$pathInfo['filename'],
$pathInfo['extension']
);
} else {
$newUri = sprintf(
- "%s-deleted",
+ '%s-deleted',
$pathInfo['filename']
);
}
@@ -1514,9 +1634,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*/
public function restoreCalendarObject(array $objectData): void {
$this->cachedObjects = [];
- $this->atomic(function () use ($objectData) {
- $id = (int) $objectData['id'];
- $restoreUri = str_replace("-deleted.ics", ".ics", $objectData['uri']);
+ $this->atomic(function () use ($objectData): void {
+ $id = (int)$objectData['id'];
+ $restoreUri = str_replace('-deleted.ics', '.ics', $objectData['uri']);
$targetObject = $this->getCalendarObject(
$objectData['calendarid'],
$restoreUri
@@ -1545,17 +1665,17 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
// Welp, this should possibly not have happened, but let's ignore
return;
}
- $this->addChanges($row['calendarid'], [$row['uri']], 1, (int) $row['calendartype']);
+ $this->addChanges($row['calendarid'], [$row['uri']], 1, (int)$row['calendartype']);
- $calendarRow = $this->getCalendarById((int) $row['calendarid']);
+ $calendarRow = $this->getCalendarById((int)$row['calendarid']);
if ($calendarRow === null) {
throw new RuntimeException('Calendar object data that was just written can\'t be read back. Check your database configuration.');
}
$this->dispatcher->dispatchTyped(
new CalendarObjectRestoredEvent(
- (int) $objectData['calendarid'],
+ (int)$objectData['calendarid'],
$calendarRow,
- $this->getShares((int) $row['calendarid']),
+ $this->getShares((int)$row['calendarid']),
$row
)
);
@@ -1642,7 +1762,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
}
$query = $this->db->getQueryBuilder();
- $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
+ $query->select(['id', 'uri', 'uid', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification', 'deleted_at'])
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
@@ -1674,13 +1794,19 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
try {
$matches = $this->validateFilterForObject($row, $filters);
} catch (ParseException $ex) {
- $this->logger->error('Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri'], [
+ $this->logger->error('Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:' . $calendarId . ' uri:' . $row['uri'], [
'app' => 'dav',
'exception' => $ex,
]);
continue;
} catch (InvalidDataException $ex) {
- $this->logger->error('Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri'], [
+ $this->logger->error('Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:' . $calendarId . ' uri:' . $row['uri'], [
+ 'app' => 'dav',
+ 'exception' => $ex,
+ ]);
+ continue;
+ } catch (MaxInstancesExceededException $ex) {
+ $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: ' . $row['uri'], [
'app' => 'dav',
'exception' => $ex,
]);
@@ -1803,7 +1929,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->andWhere($compExpr)
->andWhere($propParamExpr)
->andWhere($query->expr()->iLike('i.value',
- $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')))
+ $query->createNamedParameter('%' . $this->db->escapeLikeParameter($filters['search-term']) . '%')))
->andWhere($query->expr()->isNull('deleted_at'));
if ($offset) {
@@ -1845,7 +1971,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
array $searchProperties,
array $options,
$limit,
- $offset
+ $offset,
) {
$outerQuery = $this->db->getQueryBuilder();
$innerQuery = $this->db->getQueryBuilder();
@@ -1874,18 +2000,18 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
if (!empty($searchProperties)) {
- $or = $innerQuery->expr()->orX();
+ $or = [];
foreach ($searchProperties as $searchProperty) {
- $or->add($innerQuery->expr()->eq('op.name',
- $outerQuery->createNamedParameter($searchProperty)));
+ $or[] = $innerQuery->expr()->eq('op.name',
+ $outerQuery->createNamedParameter($searchProperty));
}
- $innerQuery->andWhere($or);
+ $innerQuery->andWhere($innerQuery->expr()->orX(...$or));
}
if ($pattern !== '') {
$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
- $outerQuery->createNamedParameter('%' .
- $this->db->escapeLikeParameter($pattern) . '%')));
+ $outerQuery->createNamedParameter('%'
+ . $this->db->escapeLikeParameter($pattern) . '%')));
}
$start = null;
@@ -1923,12 +2049,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
if (!empty($options['types'])) {
- $or = $outerQuery->expr()->orX();
+ $or = [];
foreach ($options['types'] as $type) {
- $or->add($outerQuery->expr()->eq('componenttype',
- $outerQuery->createNamedParameter($type)));
+ $or[] = $outerQuery->expr()->eq('componenttype',
+ $outerQuery->createNamedParameter($type));
}
- $outerQuery->andWhere($or);
+ $outerQuery->andWhere($outerQuery->expr()->orX(...$or));
}
$outerQuery->andWhere($outerQuery->expr()->in('c.id', $outerQuery->createFunction($innerQuery->getSQL())));
@@ -2021,7 +2147,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $calendarObjects;
}
- private function searchCalendarObjects(IQueryBuilder $query, DateTimeInterface|null $start, DateTimeInterface|null $end): array {
+ private function searchCalendarObjects(IQueryBuilder $query, ?DateTimeInterface $start, ?DateTimeInterface $end): array {
$calendarObjects = [];
$filterByTimeRange = ($start instanceof DateTimeInterface) || ($end instanceof DateTimeInterface);
@@ -2034,24 +2160,32 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
continue;
}
- $isValid = $this->validateFilterForObject($row, [
- 'name' => 'VCALENDAR',
- 'comp-filters' => [
- [
- 'name' => 'VEVENT',
- 'comp-filters' => [],
- 'prop-filters' => [],
- 'is-not-defined' => false,
- 'time-range' => [
- 'start' => $start,
- 'end' => $end,
+ try {
+ $isValid = $this->validateFilterForObject($row, [
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $start,
+ 'end' => $end,
+ ],
],
],
- ],
- 'prop-filters' => [],
- 'is-not-defined' => false,
- 'time-range' => null,
- ]);
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+ } catch (MaxInstancesExceededException $ex) {
+ $this->logger->warning('Caught max instances exceeded exception for calendar data. This usually indicates too much recurring (more than 3500) event in calendar data. Object uri: ' . $row['uri'], [
+ 'app' => 'dav',
+ 'exception' => $ex,
+ ]);
+ continue;
+ }
if (is_resource($row['calendardata'])) {
// Put the stream back to the beginning so it can be read another time
@@ -2143,22 +2277,23 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
array $componentTypes,
array $searchProperties,
array $searchParameters,
- array $options = []
+ array $options = [],
): array {
return $this->atomic(function () use ($principalUri, $pattern, $componentTypes, $searchProperties, $searchParameters, $options) {
$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
$calendarObjectIdQuery = $this->db->getQueryBuilder();
- $calendarOr = $calendarObjectIdQuery->expr()->orX();
- $searchOr = $calendarObjectIdQuery->expr()->orX();
+ $calendarOr = [];
+ $searchOr = [];
// Fetch calendars and subscription
$calendars = $this->getCalendarsForUser($principalUri);
$subscriptions = $this->getSubscriptionsForUser($principalUri);
foreach ($calendars as $calendar) {
- $calendarAnd = $calendarObjectIdQuery->expr()->andX();
- $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
- $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
+ $calendarAnd = $calendarObjectIdQuery->expr()->andX(
+ $calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])),
+ $calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)),
+ );
// If it's shared, limit search to public events
if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
@@ -2166,12 +2301,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
}
- $calendarOr->add($calendarAnd);
+ $calendarOr[] = $calendarAnd;
}
foreach ($subscriptions as $subscription) {
- $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
- $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
- $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
+ $subscriptionAnd = $calendarObjectIdQuery->expr()->andX(
+ $calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])),
+ $calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)),
+ );
// If it's shared, limit search to public events
if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
@@ -2179,28 +2315,30 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
}
- $calendarOr->add($subscriptionAnd);
+ $calendarOr[] = $subscriptionAnd;
}
foreach ($searchProperties as $property) {
- $propertyAnd = $calendarObjectIdQuery->expr()->andX();
- $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
- $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
+ $propertyAnd = $calendarObjectIdQuery->expr()->andX(
+ $calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)),
+ $calendarObjectIdQuery->expr()->isNull('cob.parameter'),
+ );
- $searchOr->add($propertyAnd);
+ $searchOr[] = $propertyAnd;
}
foreach ($searchParameters as $property => $parameter) {
- $parameterAnd = $calendarObjectIdQuery->expr()->andX();
- $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
- $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
+ $parameterAnd = $calendarObjectIdQuery->expr()->andX(
+ $calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)),
+ $calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)),
+ );
- $searchOr->add($parameterAnd);
+ $searchOr[] = $parameterAnd;
}
- if ($calendarOr->count() === 0) {
+ if (empty($calendarOr)) {
return [];
}
- if ($searchOr->count() === 0) {
+ if (empty($searchOr)) {
return [];
}
@@ -2208,8 +2346,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->from($this->dbObjectPropertiesTable, 'cob')
->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
- ->andWhere($calendarOr)
- ->andWhere($searchOr)
+ ->andWhere($calendarObjectIdQuery->expr()->orX(...$calendarOr))
+ ->andWhere($calendarObjectIdQuery->expr()->orX(...$searchOr))
->andWhere($calendarObjectIdQuery->expr()->isNull('deleted_at'));
if ($pattern !== '') {
@@ -2244,7 +2382,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$result = $calendarObjectIdQuery->executeQuery();
$matches = [];
while (($row = $result->fetch()) !== false) {
- $matches[] = (int) $row['objectid'];
+ $matches[] = (int)$row['objectid'];
}
$result->closeCursor();
@@ -2331,7 +2469,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'calendardata' => $this->readBlob($row['calendardata']),
'component' => strtolower($row['componenttype']),
'classification' => (int)$row['classification'],
- 'deleted_at' => isset($row['deleted_at']) ? ((int) $row['deleted_at']) : null,
+ 'deleted_at' => isset($row['deleted_at']) ? ((int)$row['deleted_at']) : null,
];
}
@@ -2405,74 +2543,57 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
);
$stmt = $qb->executeQuery();
$currentToken = $stmt->fetchOne();
+ $initialSync = !is_numeric($syncToken);
if ($currentToken === false) {
return null;
}
- $result = [
- 'syncToken' => $currentToken,
- 'added' => [],
- 'modified' => [],
- 'deleted' => [],
- ];
-
- if ($syncToken) {
- $qb = $this->db->getQueryBuilder();
-
- $qb->select('uri', 'operation')
- ->from('calendarchanges')
- ->where(
- $qb->expr()->andX(
- $qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)),
- $qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)),
- $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
- $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
- )
- )->orderBy('synctoken');
- if (is_int($limit) && $limit > 0) {
- $qb->setMaxResults($limit);
- }
-
- // Fetching all changes
- $stmt = $qb->executeQuery();
- $changes = [];
-
- // This loop ensures that any duplicates are overwritten, only the
- // last change on a node is relevant.
- while ($row = $stmt->fetch()) {
- $changes[$row['uri']] = $row['operation'];
- }
- $stmt->closeCursor();
-
- foreach ($changes as $uri => $operation) {
- switch ($operation) {
- case 1:
- $result['added'][] = $uri;
- break;
- case 2:
- $result['modified'][] = $uri;
- break;
- case 3:
- $result['deleted'][] = $uri;
- break;
- }
- }
- } else {
- // No synctoken supplied, this is the initial sync.
+ // evaluate if this is a initial sync and construct appropriate command
+ if ($initialSync) {
$qb = $this->db->getQueryBuilder();
$qb->select('uri')
->from('calendarobjects')
- ->where(
- $qb->expr()->andX(
- $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
- $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
- )
- );
- $stmt = $qb->executeQuery();
+ ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
+ ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
+ ->andWhere($qb->expr()->isNull('deleted_at'));
+ } else {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('uri', $qb->func()->max('operation'))
+ ->from('calendarchanges')
+ ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
+ ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
+ ->andWhere($qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)))
+ ->andWhere($qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)))
+ ->groupBy('uri');
+ }
+ // evaluate if limit exists
+ if (is_numeric($limit)) {
+ $qb->setMaxResults($limit);
+ }
+ // execute command
+ $stmt = $qb->executeQuery();
+ // build results
+ $result = ['syncToken' => $currentToken, 'added' => [], 'modified' => [], 'deleted' => []];
+ // retrieve results
+ if ($initialSync) {
$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
- $stmt->closeCursor();
+ } else {
+ // \PDO::FETCH_NUM is needed due to the inconsistent field names
+ // produced by doctrine for MAX() with different databases
+ while ($entry = $stmt->fetch(\PDO::FETCH_NUM)) {
+ // assign uri (column 0) to appropriate mutation based on operation (column 1)
+ // forced (int) is needed as doctrine with OCI returns the operation field as string not integer
+ match ((int)$entry[1]) {
+ 1 => $result['added'][] = $entry[0],
+ 2 => $result['modified'][] = $entry[0],
+ 3 => $result['deleted'][] = $entry[0],
+ default => $this->logger->debug('Unknown calendar change operation detected')
+ };
+ }
}
+ $stmt->closeCursor();
+
return $result;
}, $this->db);
}
@@ -2535,7 +2656,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'lastmodified' => $row['lastmodified'],
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
- '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
];
$subscriptions[] = $this->rowToSubscription($row, $subscription);
@@ -2657,7 +2778,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function deleteSubscription($subscriptionId) {
- $this->atomic(function () use ($subscriptionId) {
+ $this->atomic(function () use ($subscriptionId): void {
$subscriptionRow = $this->getSubscriptionById($subscriptionId);
$query = $this->db->getQueryBuilder();
@@ -2740,9 +2861,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function getSchedulingObjects($principalUri) {
$query = $this->db->getQueryBuilder();
$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
- ->from('schedulingobjects')
- ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
- ->executeQuery();
+ ->from('schedulingobjects')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->executeQuery();
$results = [];
while (($row = $stmt->fetch()) !== false) {
@@ -2770,9 +2891,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->cachedObjects = [];
$query = $this->db->getQueryBuilder();
$query->delete('schedulingobjects')
- ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
- ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
- ->executeStatement();
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
+ ->executeStatement();
}
/**
@@ -2790,7 +2911,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->setMaxResults($limit);
$result = $query->executeQuery();
$count = $result->rowCount();
- if($count === 0) {
+ if ($count === 0) {
return;
}
$ids = array_map(static function (array $id) {
@@ -2802,12 +2923,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$deleteQuery = $this->db->getQueryBuilder();
$deleteQuery->delete('schedulingobjects')
->where($deleteQuery->expr()->in('id', $deleteQuery->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY));
- foreach(array_chunk($ids, 1000) as $chunk) {
+ foreach (array_chunk($ids, 1000) as $chunk) {
$deleteQuery->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
$numDeleted += $deleteQuery->executeStatement();
}
- if($numDeleted === $limit) {
+ if ($numDeleted === $limit) {
$this->logger->info("Deleted $limit scheduling objects, continuing with next batch");
$this->deleteOutdatedSchedulingObjects($modifiedBefore, $limit);
}
@@ -2849,7 +2970,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->cachedObjects = [];
$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
- $this->atomic(function () use ($calendarId, $objectUris, $operation, $calendarType, $table) {
+ $this->atomic(function () use ($calendarId, $objectUris, $operation, $calendarType, $table): void {
$query = $this->db->getQueryBuilder();
$query->select('synctoken')
->from($table)
@@ -2866,7 +2987,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'calendarid' => $query->createNamedParameter($calendarId),
'operation' => $query->createNamedParameter($operation),
'calendartype' => $query->createNamedParameter($calendarType),
- 'created_at' => time(),
+ 'created_at' => $query->createNamedParameter(time()),
]);
foreach ($objectUris as $uri) {
$query->setParameter('uri', $uri);
@@ -2884,7 +3005,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function restoreChanges(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR): void {
$this->cachedObjects = [];
- $this->atomic(function () use ($calendarId, $calendarType) {
+ $this->atomic(function () use ($calendarId, $calendarType): void {
$qbAdded = $this->db->getQueryBuilder();
$qbAdded->select('uri')
->from('calendarobjects')
@@ -2915,7 +3036,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
);
$resultDeleted = $qbDeleted->executeQuery();
$deletedUris = array_map(function (string $uri) {
- return str_replace("-deleted.ics", ".ics", $uri);
+ return str_replace('-deleted.ics', '.ics', $uri);
}, $resultDeleted->fetchAll(\PDO::FETCH_COLUMN));
$resultDeleted->closeCursor();
$this->addChanges($calendarId, $deletedUris, 3, $calendarType);
@@ -2987,7 +3108,15 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$lastOccurrence = $firstOccurrence;
}
} else {
- $it = new EventIterator($vEvents);
+ try {
+ $it = new EventIterator($vEvents);
+ } catch (NoInstancesException $e) {
+ $this->logger->debug('Caught no instance exception for calendar data. This usually indicates invalid calendar data.', [
+ 'app' => 'dav',
+ 'exception' => $e,
+ ]);
+ throw new Forbidden($e->getMessage());
+ }
$maxDate = new DateTime(self::MAX_DATE);
$firstOccurrence = $it->getDtStart()->getTimestamp();
if ($it->isInfinite()) {
@@ -3042,7 +3171,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param list<string> $remove
*/
public function updateShares(IShareable $shareable, array $add, array $remove): void {
- $this->atomic(function () use ($shareable, $add, $remove) {
+ $this->atomic(function () use ($shareable, $add, $remove): void {
$calendarId = $shareable->getResourceId();
$calendarRow = $this->getCalendarById($calendarId);
if ($calendarRow === null) {
@@ -3069,7 +3198,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/**
* @param boolean $value
- * @param \OCA\DAV\CalDAV\Calendar $calendar
+ * @param Calendar $calendar
* @return string|null
*/
public function setPublishStatus($value, $calendar) {
@@ -3104,7 +3233,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
- * @param \OCA\DAV\CalDAV\Calendar $calendar
+ * @param Calendar $calendar
* @return mixed
*/
public function getPublishStatus($calendar) {
@@ -3140,7 +3269,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*/
public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$this->cachedObjects = [];
- $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $calendarType) {
+ $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $calendarType): void {
$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
try {
@@ -3181,7 +3310,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$query->setParameter('name', $property->name);
$query->setParameter('parameter', null);
- $query->setParameter('value', $value);
+ $query->setParameter('value', mb_strcut($value, 0, 254));
$query->executeStatement();
}
@@ -3212,7 +3341,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* deletes all birthday calendars
*/
public function deleteAllBirthdayCalendars() {
- $this->atomic(function () {
+ $this->atomic(function (): void {
$query = $this->db->getQueryBuilder();
$result = $query->select(['id'])->from('calendars')
->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
@@ -3232,7 +3361,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param $subscriptionId
*/
public function purgeAllCachedEventsForSubscription($subscriptionId) {
- $this->atomic(function () use ($subscriptionId) {
+ $this->atomic(function () use ($subscriptionId): void {
$query = $this->db->getQueryBuilder();
$query->select('uri')
->from('calendarobjects')
@@ -3269,6 +3398,45 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
+ * @param int $subscriptionId
+ * @param array<int> $calendarObjectIds
+ * @param array<string> $calendarObjectUris
+ */
+ public function purgeCachedEventsForSubscription(int $subscriptionId, array $calendarObjectIds, array $calendarObjectUris): void {
+ if (empty($calendarObjectUris)) {
+ return;
+ }
+
+ $this->atomic(function () use ($subscriptionId, $calendarObjectIds, $calendarObjectUris): void {
+ foreach (array_chunk($calendarObjectIds, 1000) as $chunk) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->dbObjectPropertiesTable)
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->andWhere($query->expr()->in('id', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY))
+ ->executeStatement();
+
+ $query = $this->db->getQueryBuilder();
+ $query->delete('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->andWhere($query->expr()->in('id', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY))
+ ->executeStatement();
+ }
+
+ foreach (array_chunk($calendarObjectUris, 1000) as $chunk) {
+ $query = $this->db->getQueryBuilder();
+ $query->delete('calendarchanges')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->andWhere($query->expr()->in('uri', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY), IQueryBuilder::PARAM_STR_ARRAY))
+ ->executeStatement();
+ }
+ $this->addChanges($subscriptionId, $calendarObjectUris, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
+ }, $this->db);
+ }
+
+ /**
* Move a calendar from one user to another
*
* @param string $uriName
@@ -3351,7 +3519,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->from('calendarchanges');
$result = $query->executeQuery();
- $maxId = (int) $result->fetchOne();
+ $maxId = (int)$result->fetchOne();
$result->closeCursor();
if (!$maxId || $maxId < $keep) {
return 0;
@@ -3455,4 +3623,68 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
return $subscription;
}
+
+ /**
+ * delete all invitations from a given calendar
+ *
+ * @since 31.0.0
+ *
+ * @param int $calendarId
+ *
+ * @return void
+ */
+ protected function purgeCalendarInvitations(int $calendarId): void {
+ // select all calendar object uid's
+ $cmd = $this->db->getQueryBuilder();
+ $cmd->select('uid')
+ ->from($this->dbObjectsTable)
+ ->where($cmd->expr()->eq('calendarid', $cmd->createNamedParameter($calendarId)));
+ $allIds = $cmd->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
+ // delete all links that match object uid's
+ $cmd = $this->db->getQueryBuilder();
+ $cmd->delete($this->dbObjectInvitationsTable)
+ ->where($cmd->expr()->in('uid', $cmd->createParameter('uids'), IQueryBuilder::PARAM_STR_ARRAY));
+ foreach (array_chunk($allIds, 1000) as $chunkIds) {
+ $cmd->setParameter('uids', $chunkIds, IQueryBuilder::PARAM_STR_ARRAY);
+ $cmd->executeStatement();
+ }
+ }
+
+ /**
+ * Delete all invitations from a given calendar event
+ *
+ * @since 31.0.0
+ *
+ * @param string $eventId UID of the event
+ *
+ * @return void
+ */
+ protected function purgeObjectInvitations(string $eventId): void {
+ $cmd = $this->db->getQueryBuilder();
+ $cmd->delete($this->dbObjectInvitationsTable)
+ ->where($cmd->expr()->eq('uid', $cmd->createNamedParameter($eventId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR));
+ $cmd->executeStatement();
+ }
+
+ public function unshare(IShareable $shareable, string $principal): void {
+ $this->atomic(function () use ($shareable, $principal): void {
+ $calendarData = $this->getCalendarById($shareable->getResourceId());
+ if ($calendarData === null) {
+ throw new \RuntimeException('Trying to update shares for non-existing calendar: ' . $shareable->getResourceId());
+ }
+
+ $oldShares = $this->getShares($shareable->getResourceId());
+ $unshare = $this->calendarSharingBackend->unshare($shareable, $principal);
+
+ if ($unshare) {
+ $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent(
+ $shareable->getResourceId(),
+ $calendarData,
+ $oldShares,
+ [],
+ [$principal]
+ ));
+ }
+ }, $this->db);
+ }
}