aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/lib/CalDAV/CalDavBackend.php1782
-rw-r--r--apps/dav/lib/CalDAV/Reminder/INotificationProvider.php4
-rw-r--r--apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php8
-rw-r--r--apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php6
-rw-r--r--apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php7
-rw-r--r--apps/dav/lib/CalDAV/Reminder/ReminderService.php9
-rw-r--r--apps/dav/lib/CalDAV/Schedule/IMipPlugin.php4
-rw-r--r--apps/dav/lib/CalDAV/Schedule/IMipService.php34
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php659
-rw-r--r--apps/dav/lib/DAV/Sharing/Backend.php25
-rw-r--r--apps/encryption/lib/Crypto/Crypt.php10
-rw-r--r--apps/files_versions/lib/Capabilities.php2
-rw-r--r--build/psalm-baseline.xml1
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php106
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php3
-rw-r--r--lib/private/Files/ObjectStore/S3.php25
-rw-r--r--lib/private/Files/Type/Loader.php59
-rw-r--r--lib/private/Preview/Generator.php140
-rw-r--r--lib/private/Session/Internal.php1
-rw-r--r--lib/public/AppFramework/Db/IMapperException.php2
-rw-r--r--tests/lib/Preview/GeneratorTest.php19
21 files changed, 1507 insertions, 1399 deletions
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
index d11aaa06a1a..70f2ca8290f 100644
--- a/apps/dav/lib/CalDAV/CalDavBackend.php
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php
@@ -315,132 +315,134 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return array
*/
public function getCalendarsForUser($principalUri) {
- $principalUriOriginal = $principalUri;
- $principalUri = $this->convertPrincipal($principalUri, true);
- $fields = array_column($this->propertyMap, 0);
- $fields[] = 'id';
- $fields[] = 'uri';
- $fields[] = 'synctoken';
- $fields[] = 'components';
- $fields[] = 'principaluri';
- $fields[] = 'transparent';
-
- // Making fields a comma-delimited list
- $query = $this->db->getQueryBuilder();
- $query->select($fields)
- ->from('calendars')
- ->orderBy('calendarorder', 'ASC');
+ return $this->atomic(function () use ($principalUri) {
+ $principalUriOriginal = $principalUri;
+ $principalUri = $this->convertPrincipal($principalUri, true);
+ $fields = array_column($this->propertyMap, 0);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'synctoken';
+ $fields[] = 'components';
+ $fields[] = 'principaluri';
+ $fields[] = 'transparent';
+
+ // Making fields a comma-delimited list
+ $query = $this->db->getQueryBuilder();
+ $query->select($fields)
+ ->from('calendars')
+ ->orderBy('calendarorder', 'ASC');
- if ($principalUri === '') {
- $query->where($query->expr()->emptyString('principaluri'));
- } else {
- $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
- }
+ if ($principalUri === '') {
+ $query->where($query->expr()->emptyString('principaluri'));
+ } else {
+ $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
+ }
- $result = $query->executeQuery();
+ $result = $query->executeQuery();
- $calendars = [];
- while ($row = $result->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
- $components = [];
- if ($row['components']) {
- $components = explode(',', $row['components']);
- }
+ $calendars = [];
+ while ($row = $result->fetch()) {
+ $row['principaluri'] = (string) $row['principaluri'];
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',', $row['components']);
+ }
- $calendar = [
- '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_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),
- ];
+ $calendar = [
+ '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_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),
+ ];
- $calendar = $this->rowToCalendar($row, $calendar);
- $calendar = $this->addOwnerPrincipalToCalendar($calendar);
- $calendar = $this->addResourceTypeToCalendar($row, $calendar);
+ $calendar = $this->rowToCalendar($row, $calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
- if (!isset($calendars[$calendar['id']])) {
- $calendars[$calendar['id']] = $calendar;
+ if (!isset($calendars[$calendar['id']])) {
+ $calendars[$calendar['id']] = $calendar;
+ }
}
- }
- $result->closeCursor();
+ $result->closeCursor();
- // query for shared calendars
- $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
- $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
+ // query for shared calendars
+ $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
+ $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
- $principals[] = $principalUri;
+ $principals[] = $principalUri;
- $fields = array_column($this->propertyMap, 0);
- $fields[] = 'a.id';
- $fields[] = 'a.uri';
- $fields[] = 'a.synctoken';
- $fields[] = 'a.components';
- $fields[] = 'a.principaluri';
- $fields[] = 'a.transparent';
- $fields[] = 's.access';
- $query = $this->db->getQueryBuilder();
- $query->select($fields)
- ->from('dav_shares', 's')
- ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
- ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
- ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
- ->setParameter('type', 'calendar')
- ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
-
- $result = $query->executeQuery();
+ $fields = array_column($this->propertyMap, 0);
+ $fields[] = 'a.id';
+ $fields[] = 'a.uri';
+ $fields[] = 'a.synctoken';
+ $fields[] = 'a.components';
+ $fields[] = 'a.principaluri';
+ $fields[] = 'a.transparent';
+ $fields[] = 's.access';
+ $query = $this->db->getQueryBuilder();
+ $query->select($fields)
+ ->from('dav_shares', 's')
+ ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
+ ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
+ ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
+ ->setParameter('type', 'calendar')
+ ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
- $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
- while ($row = $result->fetch()) {
- $row['principaluri'] = (string) $row['principaluri'];
- if ($row['principaluri'] === $principalUri) {
- continue;
- }
+ $result = $query->executeQuery();
- $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
- if (isset($calendars[$row['id']])) {
- if ($readOnly) {
- // New share can not have more permissions then the old one.
+ $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
+ while ($row = $result->fetch()) {
+ $row['principaluri'] = (string) $row['principaluri'];
+ if ($row['principaluri'] === $principalUri) {
continue;
}
- if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
- $calendars[$row['id']][$readOnlyPropertyName] === 0) {
- // Old share is already read-write, no more permissions can be gained
- continue;
+
+ $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
+ if (isset($calendars[$row['id']])) {
+ if ($readOnly) {
+ // New share can not have more permissions then the old one.
+ continue;
+ }
+ if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
+ $calendars[$row['id']][$readOnlyPropertyName] === 0) {
+ // Old share is already read-write, no more permissions can be gained
+ continue;
+ }
}
- }
- [, $name] = Uri\split($row['principaluri']);
- $uri = $row['uri'] . '_shared_by_' . $name;
- $row['displayname'] = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? ($name ?? '')) . ')';
- $components = [];
- if ($row['components']) {
- $components = explode(',', $row['components']);
- }
- $calendar = [
- '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_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),
- $readOnlyPropertyName => $readOnly,
- ];
+ [, $name] = Uri\split($row['principaluri']);
+ $uri = $row['uri'] . '_shared_by_' . $name;
+ $row['displayname'] = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? ($name ?? '')) . ')';
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',', $row['components']);
+ }
+ $calendar = [
+ '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_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),
+ $readOnlyPropertyName => $readOnly,
+ ];
- $calendar = $this->rowToCalendar($row, $calendar);
- $calendar = $this->addOwnerPrincipalToCalendar($calendar);
- $calendar = $this->addResourceTypeToCalendar($row, $calendar);
+ $calendar = $this->rowToCalendar($row, $calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
- $calendars[$calendar['id']] = $calendar;
- }
- $result->closeCursor();
+ $calendars[$calendar['id']] = $calendar;
+ }
+ $result->closeCursor();
- return array_values($calendars);
+ return array_values($calendars);
+ }, $this->db);
}
/**
@@ -830,39 +832,41 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function updateCalendar($calendarId, PropPatch $propPatch) {
- $supportedProperties = array_keys($this->propertyMap);
- $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
-
- $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
- $newValues = [];
- foreach ($mutations as $propertyName => $propertyValue) {
- switch ($propertyName) {
- case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
- $fieldName = 'transparent';
- $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
- break;
- default:
- $fieldName = $this->propertyMap[$propertyName][0];
- $newValues[$fieldName] = $propertyValue;
- break;
+ $this->atomic(function () use ($calendarId, $propPatch) {
+ $supportedProperties = array_keys($this->propertyMap);
+ $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
+
+ $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
+ $newValues = [];
+ foreach ($mutations as $propertyName => $propertyValue) {
+ switch ($propertyName) {
+ case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
+ $fieldName = 'transparent';
+ $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
+ break;
+ default:
+ $fieldName = $this->propertyMap[$propertyName][0];
+ $newValues[$fieldName] = $propertyValue;
+ break;
+ }
}
- }
- $query = $this->db->getQueryBuilder();
- $query->update('calendars');
- foreach ($newValues as $fieldName => $value) {
- $query->set($fieldName, $query->createNamedParameter($value));
- }
- $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
- $query->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->update('calendars');
+ foreach ($newValues as $fieldName => $value) {
+ $query->set($fieldName, $query->createNamedParameter($value));
+ }
+ $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
+ $query->executeStatement();
- $this->addChange($calendarId, "", 2);
+ $this->addChange($calendarId, "", 2);
- $calendarData = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent($calendarId, $calendarData, $shares, $mutations));
+ $calendarData = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
+ $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent($calendarId, $calendarData, $shares, $mutations));
- return true;
- });
+ return true;
+ });
+ }, $this->db);
}
/**
@@ -872,81 +876,85 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function deleteCalendar($calendarId, bool $forceDeletePermanently = false) {
- // 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.
- $calendarData = $this->getCalendarById($calendarId);
- $isBirthdayCalendar = isset($calendarData['uri']) && $calendarData['uri'] === BirthdayService::BIRTHDAY_CALENDAR_URI;
- $trashbinDisabled = $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0';
- if ($forceDeletePermanently || $isBirthdayCalendar || $trashbinDisabled) {
+ $this->atomic(function () use ($calendarId, $forceDeletePermanently) {
+ // 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.
$calendarData = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
-
- $qbDeleteCalendarObjectProps = $this->db->getQueryBuilder();
- $qbDeleteCalendarObjectProps->delete($this->dbObjectPropertiesTable)
- ->where($qbDeleteCalendarObjectProps->expr()->eq('calendarid', $qbDeleteCalendarObjectProps->createNamedParameter($calendarId)))
- ->andWhere($qbDeleteCalendarObjectProps->expr()->eq('calendartype', $qbDeleteCalendarObjectProps->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
- ->executeStatement();
-
- $qbDeleteCalendarObjects = $this->db->getQueryBuilder();
- $qbDeleteCalendarObjects->delete('calendarobjects')
- ->where($qbDeleteCalendarObjects->expr()->eq('calendarid', $qbDeleteCalendarObjects->createNamedParameter($calendarId)))
- ->andWhere($qbDeleteCalendarObjects->expr()->eq('calendartype', $qbDeleteCalendarObjects->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
- ->executeStatement();
-
- $qbDeleteCalendarChanges = $this->db->getQueryBuilder();
- $qbDeleteCalendarChanges->delete('calendarchanges')
- ->where($qbDeleteCalendarChanges->expr()->eq('calendarid', $qbDeleteCalendarChanges->createNamedParameter($calendarId)))
- ->andWhere($qbDeleteCalendarChanges->expr()->eq('calendartype', $qbDeleteCalendarChanges->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
- ->executeStatement();
-
- $this->calendarSharingBackend->deleteAllShares($calendarId);
-
- $qbDeleteCalendar = $this->db->getQueryBuilder();
- $qbDeleteCalendar->delete('calendars')
- ->where($qbDeleteCalendar->expr()->eq('id', $qbDeleteCalendar->createNamedParameter($calendarId)))
- ->executeStatement();
+ $isBirthdayCalendar = isset($calendarData['uri']) && $calendarData['uri'] === BirthdayService::BIRTHDAY_CALENDAR_URI;
+ $trashbinDisabled = $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0';
+ if ($forceDeletePermanently || $isBirthdayCalendar || $trashbinDisabled) {
+ $calendarData = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
- // Only dispatch if we actually deleted anything
- if ($calendarData) {
- $this->dispatcher->dispatchTyped(new CalendarDeletedEvent($calendarId, $calendarData, $shares));
- }
- } else {
- $qbMarkCalendarDeleted = $this->db->getQueryBuilder();
- $qbMarkCalendarDeleted->update('calendars')
- ->set('deleted_at', $qbMarkCalendarDeleted->createNamedParameter(time()))
- ->where($qbMarkCalendarDeleted->expr()->eq('id', $qbMarkCalendarDeleted->createNamedParameter($calendarId)))
- ->executeStatement();
+ $qbDeleteCalendarObjectProps = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjectProps->delete($this->dbObjectPropertiesTable)
+ ->where($qbDeleteCalendarObjectProps->expr()->eq('calendarid', $qbDeleteCalendarObjectProps->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarObjectProps->expr()->eq('calendartype', $qbDeleteCalendarObjectProps->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
+
+ $qbDeleteCalendarObjects = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjects->delete('calendarobjects')
+ ->where($qbDeleteCalendarObjects->expr()->eq('calendarid', $qbDeleteCalendarObjects->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarObjects->expr()->eq('calendartype', $qbDeleteCalendarObjects->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
+
+ $qbDeleteCalendarChanges = $this->db->getQueryBuilder();
+ $qbDeleteCalendarChanges->delete('calendarchanges')
+ ->where($qbDeleteCalendarChanges->expr()->eq('calendarid', $qbDeleteCalendarChanges->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarChanges->expr()->eq('calendartype', $qbDeleteCalendarChanges->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
+
+ $this->calendarSharingBackend->deleteAllShares($calendarId);
+
+ $qbDeleteCalendar = $this->db->getQueryBuilder();
+ $qbDeleteCalendar->delete('calendars')
+ ->where($qbDeleteCalendar->expr()->eq('id', $qbDeleteCalendar->createNamedParameter($calendarId)))
+ ->executeStatement();
+
+ // Only dispatch if we actually deleted anything
+ if ($calendarData) {
+ $this->dispatcher->dispatchTyped(new CalendarDeletedEvent($calendarId, $calendarData, $shares));
+ }
+ } else {
+ $qbMarkCalendarDeleted = $this->db->getQueryBuilder();
+ $qbMarkCalendarDeleted->update('calendars')
+ ->set('deleted_at', $qbMarkCalendarDeleted->createNamedParameter(time()))
+ ->where($qbMarkCalendarDeleted->expr()->eq('id', $qbMarkCalendarDeleted->createNamedParameter($calendarId)))
+ ->executeStatement();
- $calendarData = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
- if ($calendarData) {
- $this->dispatcher->dispatchTyped(new CalendarMovedToTrashEvent(
- $calendarId,
- $calendarData,
- $shares
- ));
+ $calendarData = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
+ if ($calendarData) {
+ $this->dispatcher->dispatchTyped(new CalendarMovedToTrashEvent(
+ $calendarId,
+ $calendarData,
+ $shares
+ ));
+ }
}
- }
+ }, $this->db);
}
public function restoreCalendar(int $id): void {
- $qb = $this->db->getQueryBuilder();
- $update = $qb->update('calendars')
- ->set('deleted_at', $qb->createNamedParameter(null))
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
- $update->executeStatement();
-
- $calendarData = $this->getCalendarById($id);
- $shares = $this->getShares($id);
- if ($calendarData === null) {
- throw new RuntimeException('Calendar data that was just written can\'t be read back. Check your database configuration.');
- }
- $this->dispatcher->dispatchTyped(new CalendarRestoredEvent(
- $id,
- $calendarData,
- $shares
- ));
+ $this->atomic(function () use ($id) {
+ $qb = $this->db->getQueryBuilder();
+ $update = $qb->update('calendars')
+ ->set('deleted_at', $qb->createNamedParameter(null))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $update->executeStatement();
+
+ $calendarData = $this->getCalendarById($id);
+ $shares = $this->getShares($id);
+ if ($calendarData === null) {
+ throw new RuntimeException('Calendar data that was just written can\'t be read back. Check your database configuration.');
+ }
+ $this->dispatcher->dispatchTyped(new CalendarRestoredEvent(
+ $id,
+ $calendarData,
+ $shares
+ ));
+ }, $this->db);
}
/**
@@ -1206,74 +1214,76 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$extraData = $this->getDenormalizedData($calendarData);
- // Try to detect duplicates
- $qb = $this->db->getQueryBuilder();
- $qb->select($qb->func()->count('*'))
- ->from('calendarobjects')
- ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
- ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($extraData['uid'])))
- ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
- ->andWhere($qb->expr()->isNull('deleted_at'));
- $result = $qb->executeQuery();
- $count = (int) $result->fetchOne();
- $result->closeCursor();
+ return $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $extraData, $calendarType) {
+ // Try to detect duplicates
+ $qb = $this->db->getQueryBuilder();
+ $qb->select($qb->func()->count('*'))
+ ->from('calendarobjects')
+ ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
+ ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($extraData['uid'])))
+ ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
+ ->andWhere($qb->expr()->isNull('deleted_at'));
+ $result = $qb->executeQuery();
+ $count = (int) $result->fetchOne();
+ $result->closeCursor();
- if ($count !== 0) {
- throw new BadRequest('Calendar object with uid already exists in this calendar collection.');
- }
- // For a more specific error message we also try to explicitly look up the UID but as a deleted entry
- $qbDel = $this->db->getQueryBuilder();
- $qbDel->select('*')
- ->from('calendarobjects')
- ->where($qbDel->expr()->eq('calendarid', $qbDel->createNamedParameter($calendarId)))
- ->andWhere($qbDel->expr()->eq('uid', $qbDel->createNamedParameter($extraData['uid'])))
- ->andWhere($qbDel->expr()->eq('calendartype', $qbDel->createNamedParameter($calendarType)))
- ->andWhere($qbDel->expr()->isNotNull('deleted_at'));
- $result = $qbDel->executeQuery();
- $found = $result->fetch();
- $result->closeCursor();
- if ($found !== false) {
- // the object existed previously but has been deleted
- // remove the trashbin entry and continue as if it was a new object
- $this->deleteCalendarObject($calendarId, $found['uri']);
- }
+ if ($count !== 0) {
+ throw new BadRequest('Calendar object with uid already exists in this calendar collection.');
+ }
+ // For a more specific error message we also try to explicitly look up the UID but as a deleted entry
+ $qbDel = $this->db->getQueryBuilder();
+ $qbDel->select('*')
+ ->from('calendarobjects')
+ ->where($qbDel->expr()->eq('calendarid', $qbDel->createNamedParameter($calendarId)))
+ ->andWhere($qbDel->expr()->eq('uid', $qbDel->createNamedParameter($extraData['uid'])))
+ ->andWhere($qbDel->expr()->eq('calendartype', $qbDel->createNamedParameter($calendarType)))
+ ->andWhere($qbDel->expr()->isNotNull('deleted_at'));
+ $result = $qbDel->executeQuery();
+ $found = $result->fetch();
+ $result->closeCursor();
+ if ($found !== false) {
+ // the object existed previously but has been deleted
+ // remove the trashbin entry and continue as if it was a new object
+ $this->deleteCalendarObject($calendarId, $found['uri']);
+ }
- $query = $this->db->getQueryBuilder();
- $query->insert('calendarobjects')
- ->values([
- 'calendarid' => $query->createNamedParameter($calendarId),
- 'uri' => $query->createNamedParameter($objectUri),
- 'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
- 'lastmodified' => $query->createNamedParameter(time()),
- 'etag' => $query->createNamedParameter($extraData['etag']),
- 'size' => $query->createNamedParameter($extraData['size']),
- 'componenttype' => $query->createNamedParameter($extraData['componentType']),
- 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
- 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
- 'classification' => $query->createNamedParameter($extraData['classification']),
- 'uid' => $query->createNamedParameter($extraData['uid']),
- 'calendartype' => $query->createNamedParameter($calendarType),
- ])
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->insert('calendarobjects')
+ ->values([
+ 'calendarid' => $query->createNamedParameter($calendarId),
+ 'uri' => $query->createNamedParameter($objectUri),
+ 'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
+ 'lastmodified' => $query->createNamedParameter(time()),
+ 'etag' => $query->createNamedParameter($extraData['etag']),
+ 'size' => $query->createNamedParameter($extraData['size']),
+ 'componenttype' => $query->createNamedParameter($extraData['componentType']),
+ 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
+ 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
+ 'classification' => $query->createNamedParameter($extraData['classification']),
+ 'uid' => $query->createNamedParameter($extraData['uid']),
+ 'calendartype' => $query->createNamedParameter($calendarType),
+ ])
+ ->executeStatement();
- $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
- $this->addChange($calendarId, $objectUri, 1, $calendarType);
+ $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
+ $this->addChange($calendarId, $objectUri, 1, $calendarType);
- $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
- assert($objectRow !== null);
+ $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
+ assert($objectRow !== null);
- if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
- $calendarRow = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
+ if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
+ $calendarRow = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent($calendarId, $calendarRow, $shares, $objectRow));
- } else {
- $subscriptionRow = $this->getSubscriptionById($calendarId);
+ $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent($calendarId, $calendarRow, $shares, $objectRow));
+ } else {
+ $subscriptionRow = $this->getSubscriptionById($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent($calendarId, $subscriptionRow, [], $objectRow));
- }
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent($calendarId, $subscriptionRow, [], $objectRow));
+ }
- return '"' . $extraData['etag'] . '"';
+ return '"' . $extraData['etag'] . '"';
+ }, $this->db);
}
/**
@@ -1297,40 +1307,43 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*/
public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$extraData = $this->getDenormalizedData($calendarData);
- $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']))
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
- ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
- ->executeStatement();
- $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
- $this->addChange($calendarId, $objectUri, 2, $calendarType);
+ 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']))
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
+ ->executeStatement();
- $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
- if (is_array($objectRow)) {
- if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
- $calendarRow = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
+ $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
+ $this->addChange($calendarId, $objectUri, 2, $calendarType);
- $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow));
- } else {
- $subscriptionRow = $this->getSubscriptionById($calendarId);
+ $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
+ if (is_array($objectRow)) {
+ if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
+ $calendarRow = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow));
+ $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow));
+ } else {
+ $subscriptionRow = $this->getSubscriptionById($calendarId);
+
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow));
+ }
}
- }
- return '"' . $extraData['etag'] . '"';
+ return '"' . $extraData['etag'] . '"';
+ }, $this->db);
}
/**
@@ -1346,43 +1359,45 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @throws Exception
*/
public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, int $objectId, string $oldPrincipalUri, string $newPrincipalUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
- $object = $this->getCalendarObjectById($oldPrincipalUri, $objectId);
- if (empty($object)) {
- return false;
- }
+ return $this->atomic(function () use ($sourceCalendarId, $targetCalendarId, $objectId, $oldPrincipalUri, $newPrincipalUri, $calendarType) {
+ $object = $this->getCalendarObjectById($oldPrincipalUri, $objectId);
+ if (empty($object)) {
+ return false;
+ }
- $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))
- ->executeStatement();
+ $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))
+ ->executeStatement();
- $this->purgeProperties($sourceCalendarId, $objectId);
- $this->updateProperties($targetCalendarId, $object['uri'], $object['calendardata'], $calendarType);
+ $this->purgeProperties($sourceCalendarId, $objectId);
+ $this->updateProperties($targetCalendarId, $object['uri'], $object['calendardata'], $calendarType);
- $this->addChange($sourceCalendarId, $object['uri'], 1, $calendarType);
- $this->addChange($targetCalendarId, $object['uri'], 3, $calendarType);
+ $this->addChange($sourceCalendarId, $object['uri'], 1, $calendarType);
+ $this->addChange($targetCalendarId, $object['uri'], 3, $calendarType);
- $object = $this->getCalendarObjectById($newPrincipalUri, $objectId);
- // Calendar Object wasn't found - possibly because it was deleted in the meantime by a different client
- if (empty($object)) {
- return false;
- }
+ $object = $this->getCalendarObjectById($newPrincipalUri, $objectId);
+ // Calendar Object wasn't found - possibly because it was deleted in the meantime by a different client
+ if (empty($object)) {
+ return false;
+ }
- $targetCalendarRow = $this->getCalendarById($targetCalendarId);
- // the calendar this event is being moved to does not exist any longer
- if (empty($targetCalendarRow)) {
- return false;
- }
+ $targetCalendarRow = $this->getCalendarById($targetCalendarId);
+ // the calendar this event is being moved to does not exist any longer
+ if (empty($targetCalendarRow)) {
+ return false;
+ }
- if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
- $sourceShares = $this->getShares($sourceCalendarId);
- $targetShares = $this->getShares($targetCalendarId);
- $sourceCalendarRow = $this->getCalendarById($sourceCalendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectMovedEvent($sourceCalendarId, $sourceCalendarRow, $targetCalendarId, $targetCalendarRow, $sourceShares, $targetShares, $object));
- }
- return true;
+ if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
+ $sourceShares = $this->getShares($sourceCalendarId);
+ $targetShares = $this->getShares($targetCalendarId);
+ $sourceCalendarRow = $this->getCalendarById($sourceCalendarId);
+ $this->dispatcher->dispatchTyped(new CalendarObjectMovedEvent($sourceCalendarId, $sourceCalendarRow, $targetCalendarId, $targetCalendarRow, $sourceShares, $targetShares, $object));
+ }
+ return true;
+ }, $this->db);
}
@@ -1415,77 +1430,79 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR, bool $forceDeletePermanently = false) {
- $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
+ $this->atomic(function () use ($calendarId, $objectUri, $calendarType, $forceDeletePermanently) {
+ $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
- if ($data === null) {
- // Nothing to delete
- return;
- }
+ if ($data === null) {
+ // Nothing to delete
+ return;
+ }
- if ($forceDeletePermanently || $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0') {
- $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
- $stmt->execute([$calendarId, $objectUri, $calendarType]);
+ if ($forceDeletePermanently || $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0') {
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
+ $stmt->execute([$calendarId, $objectUri, $calendarType]);
- $this->purgeProperties($calendarId, $data['id']);
+ $this->purgeProperties($calendarId, $data['id']);
- if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
- $calendarRow = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
+ if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
+ $calendarRow = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent($calendarId, $calendarRow, $shares, $data));
- } else {
- $subscriptionRow = $this->getSubscriptionById($calendarId);
+ $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent($calendarId, $calendarRow, $shares, $data));
+ } else {
+ $subscriptionRow = $this->getSubscriptionById($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent($calendarId, $subscriptionRow, [], $data));
- }
- } else {
- $pathInfo = pathinfo($data['uri']);
- if (!empty($pathInfo['extension'])) {
- // Append a suffix to "free" the old URI for recreation
- $newUri = sprintf(
- "%s-deleted.%s",
- $pathInfo['filename'],
- $pathInfo['extension']
- );
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent($calendarId, $subscriptionRow, [], $data));
+ }
} else {
- $newUri = sprintf(
- "%s-deleted",
- $pathInfo['filename']
- );
- }
-
- // Try to detect conflicts before the DB does
- // As unlikely as it seems, this can happen when the user imports, then deletes, imports and deletes again
- $newObject = $this->getCalendarObject($calendarId, $newUri, $calendarType);
- if ($newObject !== null) {
- throw new Forbidden("A calendar object with URI $newUri already exists in calendar $calendarId, therefore this object can't be moved into the trashbin");
- }
+ $pathInfo = pathinfo($data['uri']);
+ if (!empty($pathInfo['extension'])) {
+ // Append a suffix to "free" the old URI for recreation
+ $newUri = sprintf(
+ "%s-deleted.%s",
+ $pathInfo['filename'],
+ $pathInfo['extension']
+ );
+ } else {
+ $newUri = sprintf(
+ "%s-deleted",
+ $pathInfo['filename']
+ );
+ }
- $qb = $this->db->getQueryBuilder();
- $markObjectDeletedQuery = $qb->update('calendarobjects')
- ->set('deleted_at', $qb->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
- ->set('uri', $qb->createNamedParameter($newUri))
- ->where(
- $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
- $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
- $qb->expr()->eq('uri', $qb->createNamedParameter($objectUri))
- );
- $markObjectDeletedQuery->executeStatement();
+ // Try to detect conflicts before the DB does
+ // As unlikely as it seems, this can happen when the user imports, then deletes, imports and deletes again
+ $newObject = $this->getCalendarObject($calendarId, $newUri, $calendarType);
+ if ($newObject !== null) {
+ throw new Forbidden("A calendar object with URI $newUri already exists in calendar $calendarId, therefore this object can't be moved into the trashbin");
+ }
- $calendarData = $this->getCalendarById($calendarId);
- if ($calendarData !== null) {
- $this->dispatcher->dispatchTyped(
- new CalendarObjectMovedToTrashEvent(
- $calendarId,
- $calendarData,
- $this->getShares($calendarId),
- $data
- )
- );
+ $qb = $this->db->getQueryBuilder();
+ $markObjectDeletedQuery = $qb->update('calendarobjects')
+ ->set('deleted_at', $qb->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
+ ->set('uri', $qb->createNamedParameter($newUri))
+ ->where(
+ $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
+ $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
+ $qb->expr()->eq('uri', $qb->createNamedParameter($objectUri))
+ );
+ $markObjectDeletedQuery->executeStatement();
+
+ $calendarData = $this->getCalendarById($calendarId);
+ if ($calendarData !== null) {
+ $this->dispatcher->dispatchTyped(
+ new CalendarObjectMovedToTrashEvent(
+ $calendarId,
+ $calendarData,
+ $this->getShares($calendarId),
+ $data
+ )
+ );
+ }
}
- }
- $this->addChange($calendarId, $objectUri, 3, $calendarType);
+ $this->addChange($calendarId, $objectUri, 3, $calendarType);
+ }, $this->db);
}
/**
@@ -1494,50 +1511,52 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @throws Forbidden
*/
public function restoreCalendarObject(array $objectData): void {
- $id = (int) $objectData['id'];
- $restoreUri = str_replace("-deleted.ics", ".ics", $objectData['uri']);
- $targetObject = $this->getCalendarObject(
- $objectData['calendarid'],
- $restoreUri
- );
- if ($targetObject !== null) {
- throw new Forbidden("Can not restore calendar $id because a calendar object with the URI $restoreUri already exists");
- }
+ $this->atomic(function () use ($objectData) {
+ $id = (int) $objectData['id'];
+ $restoreUri = str_replace("-deleted.ics", ".ics", $objectData['uri']);
+ $targetObject = $this->getCalendarObject(
+ $objectData['calendarid'],
+ $restoreUri
+ );
+ if ($targetObject !== null) {
+ throw new Forbidden("Can not restore calendar $id because a calendar object with the URI $restoreUri already exists");
+ }
- $qb = $this->db->getQueryBuilder();
- $update = $qb->update('calendarobjects')
- ->set('uri', $qb->createNamedParameter($restoreUri))
- ->set('deleted_at', $qb->createNamedParameter(null))
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
- $update->executeStatement();
-
- // Make sure this change is tracked in the changes table
- $qb2 = $this->db->getQueryBuilder();
- $selectObject = $qb2->select('calendardata', 'uri', 'calendarid', 'calendartype')
- ->selectAlias('componenttype', 'component')
- ->from('calendarobjects')
- ->where($qb2->expr()->eq('id', $qb2->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
- $result = $selectObject->executeQuery();
- $row = $result->fetch();
- $result->closeCursor();
- if ($row === false) {
- // Welp, this should possibly not have happened, but let's ignore
- return;
- }
- $this->addChange($row['calendarid'], $row['uri'], 1, (int) $row['calendartype']);
+ $qb = $this->db->getQueryBuilder();
+ $update = $qb->update('calendarobjects')
+ ->set('uri', $qb->createNamedParameter($restoreUri))
+ ->set('deleted_at', $qb->createNamedParameter(null))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $update->executeStatement();
+
+ // Make sure this change is tracked in the changes table
+ $qb2 = $this->db->getQueryBuilder();
+ $selectObject = $qb2->select('calendardata', 'uri', 'calendarid', 'calendartype')
+ ->selectAlias('componenttype', 'component')
+ ->from('calendarobjects')
+ ->where($qb2->expr()->eq('id', $qb2->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $result = $selectObject->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+ if ($row === false) {
+ // Welp, this should possibly not have happened, but let's ignore
+ return;
+ }
+ $this->addChange($row['calendarid'], $row['uri'], 1, (int) $row['calendartype']);
- $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'],
- $calendarRow,
- $this->getShares((int) $row['calendarid']),
- $row
- )
- );
+ $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'],
+ $calendarRow,
+ $this->getShares((int) $row['calendarid']),
+ $row
+ )
+ );
+ }, $this->db);
}
/**
@@ -1686,118 +1705,120 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return array
*/
public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
- $calendars = $this->getCalendarsForUser($principalUri);
- $ownCalendars = [];
- $sharedCalendars = [];
+ return $this->atomic(function () use ($principalUri, $filters, $limit, $offset) {
+ $calendars = $this->getCalendarsForUser($principalUri);
+ $ownCalendars = [];
+ $sharedCalendars = [];
- $uriMapper = [];
+ $uriMapper = [];
- foreach ($calendars as $calendar) {
- if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
- $ownCalendars[] = $calendar['id'];
- } else {
- $sharedCalendars[] = $calendar['id'];
+ foreach ($calendars as $calendar) {
+ if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
+ $ownCalendars[] = $calendar['id'];
+ } else {
+ $sharedCalendars[] = $calendar['id'];
+ }
+ $uriMapper[$calendar['id']] = $calendar['uri'];
+ }
+ if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
+ return [];
}
- $uriMapper[$calendar['id']] = $calendar['uri'];
- }
- if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
- return [];
- }
-
- $query = $this->db->getQueryBuilder();
- // Calendar id expressions
- $calendarExpressions = [];
- foreach ($ownCalendars as $id) {
- $calendarExpressions[] = $query->expr()->andX(
- $query->expr()->eq('c.calendarid',
- $query->createNamedParameter($id)),
- $query->expr()->eq('c.calendartype',
- $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
- }
- foreach ($sharedCalendars as $id) {
- $calendarExpressions[] = $query->expr()->andX(
- $query->expr()->eq('c.calendarid',
- $query->createNamedParameter($id)),
- $query->expr()->eq('c.classification',
- $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
- $query->expr()->eq('c.calendartype',
- $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
- }
- if (count($calendarExpressions) === 1) {
- $calExpr = $calendarExpressions[0];
- } else {
- $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
- }
+ $query = $this->db->getQueryBuilder();
+ // Calendar id expressions
+ $calendarExpressions = [];
+ foreach ($ownCalendars as $id) {
+ $calendarExpressions[] = $query->expr()->andX(
+ $query->expr()->eq('c.calendarid',
+ $query->createNamedParameter($id)),
+ $query->expr()->eq('c.calendartype',
+ $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
+ }
+ foreach ($sharedCalendars as $id) {
+ $calendarExpressions[] = $query->expr()->andX(
+ $query->expr()->eq('c.calendarid',
+ $query->createNamedParameter($id)),
+ $query->expr()->eq('c.classification',
+ $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
+ $query->expr()->eq('c.calendartype',
+ $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
+ }
- // Component expressions
- $compExpressions = [];
- foreach ($filters['comps'] as $comp) {
- $compExpressions[] = $query->expr()
- ->eq('c.componenttype', $query->createNamedParameter($comp));
- }
+ if (count($calendarExpressions) === 1) {
+ $calExpr = $calendarExpressions[0];
+ } else {
+ $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
+ }
- if (count($compExpressions) === 1) {
- $compExpr = $compExpressions[0];
- } else {
- $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
- }
+ // Component expressions
+ $compExpressions = [];
+ foreach ($filters['comps'] as $comp) {
+ $compExpressions[] = $query->expr()
+ ->eq('c.componenttype', $query->createNamedParameter($comp));
+ }
- if (!isset($filters['props'])) {
- $filters['props'] = [];
- }
- if (!isset($filters['params'])) {
- $filters['params'] = [];
- }
+ if (count($compExpressions) === 1) {
+ $compExpr = $compExpressions[0];
+ } else {
+ $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
+ }
- $propParamExpressions = [];
- foreach ($filters['props'] as $prop) {
- $propParamExpressions[] = $query->expr()->andX(
- $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
- $query->expr()->isNull('i.parameter')
- );
- }
- foreach ($filters['params'] as $param) {
- $propParamExpressions[] = $query->expr()->andX(
- $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
- $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
- );
- }
+ if (!isset($filters['props'])) {
+ $filters['props'] = [];
+ }
+ if (!isset($filters['params'])) {
+ $filters['params'] = [];
+ }
- if (count($propParamExpressions) === 1) {
- $propParamExpr = $propParamExpressions[0];
- } else {
- $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
- }
+ $propParamExpressions = [];
+ foreach ($filters['props'] as $prop) {
+ $propParamExpressions[] = $query->expr()->andX(
+ $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
+ $query->expr()->isNull('i.parameter')
+ );
+ }
+ foreach ($filters['params'] as $param) {
+ $propParamExpressions[] = $query->expr()->andX(
+ $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
+ $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
+ );
+ }
- $query->select(['c.calendarid', 'c.uri'])
- ->from($this->dbObjectPropertiesTable, 'i')
- ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
- ->where($calExpr)
- ->andWhere($compExpr)
- ->andWhere($propParamExpr)
- ->andWhere($query->expr()->iLike('i.value',
- $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')))
- ->andWhere($query->expr()->isNull('deleted_at'));
+ if (count($propParamExpressions) === 1) {
+ $propParamExpr = $propParamExpressions[0];
+ } else {
+ $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
+ }
- if ($offset) {
- $query->setFirstResult($offset);
- }
- if ($limit) {
- $query->setMaxResults($limit);
- }
+ $query->select(['c.calendarid', 'c.uri'])
+ ->from($this->dbObjectPropertiesTable, 'i')
+ ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
+ ->where($calExpr)
+ ->andWhere($compExpr)
+ ->andWhere($propParamExpr)
+ ->andWhere($query->expr()->iLike('i.value',
+ $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')))
+ ->andWhere($query->expr()->isNull('deleted_at'));
+
+ if ($offset) {
+ $query->setFirstResult($offset);
+ }
+ if ($limit) {
+ $query->setMaxResults($limit);
+ }
- $stmt = $query->executeQuery();
+ $stmt = $query->executeQuery();
- $result = [];
- while ($row = $stmt->fetch()) {
- $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
- if (!in_array($path, $result)) {
- $result[] = $path;
+ $result = [];
+ while ($row = $stmt->fetch()) {
+ $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
+ if (!in_array($path, $result)) {
+ $result[] = $path;
+ }
}
- }
- return $result;
+ return $result;
+ }, $this->db);
}
/**
@@ -2022,110 +2043,112 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
array $searchProperties,
array $searchParameters,
array $options = []): array {
- $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();
-
- // 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)));
+ 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();
+
+ // 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)));
+
+ // If it's shared, limit search to public events
+ if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
+ && $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
+ $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
+ }
- // If it's shared, limit search to public events
- if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
- && $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
- $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
+ $calendarOr->add($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)));
+
+ // If it's shared, limit search to public events
+ if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
+ && $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
+ $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
+ }
- $calendarOr->add($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)));
-
- // If it's shared, limit search to public events
- if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
- && $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
- $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
+ $calendarOr->add($subscriptionAnd);
}
- $calendarOr->add($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'));
+ 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'));
- $searchOr->add($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)));
+ $searchOr->add($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)));
- $searchOr->add($parameterAnd);
- }
+ $searchOr->add($parameterAnd);
+ }
- if ($calendarOr->count() === 0) {
- return [];
- }
- if ($searchOr->count() === 0) {
- return [];
- }
+ if ($calendarOr->count() === 0) {
+ return [];
+ }
+ if ($searchOr->count() === 0) {
+ return [];
+ }
- $calendarObjectIdQuery->selectDistinct('cob.objectid')
- ->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()->isNull('deleted_at'));
-
- if ('' !== $pattern) {
- if (!$escapePattern) {
- $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
- } else {
- $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
+ $calendarObjectIdQuery->selectDistinct('cob.objectid')
+ ->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()->isNull('deleted_at'));
+
+ if ('' !== $pattern) {
+ if (!$escapePattern) {
+ $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
+ } else {
+ $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
+ }
}
- }
- if (isset($options['limit'])) {
- $calendarObjectIdQuery->setMaxResults($options['limit']);
- }
- if (isset($options['offset'])) {
- $calendarObjectIdQuery->setFirstResult($options['offset']);
- }
+ if (isset($options['limit'])) {
+ $calendarObjectIdQuery->setMaxResults($options['limit']);
+ }
+ if (isset($options['offset'])) {
+ $calendarObjectIdQuery->setFirstResult($options['offset']);
+ }
- $result = $calendarObjectIdQuery->executeQuery();
- $matches = $result->fetchAll();
- $result->closeCursor();
- $matches = array_map(static function (array $match):int {
- return (int) $match['objectid'];
- }, $matches);
+ $result = $calendarObjectIdQuery->executeQuery();
+ $matches = $result->fetchAll();
+ $result->closeCursor();
+ $matches = array_map(static function (array $match):int {
+ return (int) $match['objectid'];
+ }, $matches);
- $query = $this->db->getQueryBuilder();
- $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
- ->from('calendarobjects')
- ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
+ $query = $this->db->getQueryBuilder();
+ $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
+ ->from('calendarobjects')
+ ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
- $result = $query->executeQuery();
- $calendarObjects = $result->fetchAll();
- $result->closeCursor();
+ $result = $query->executeQuery();
+ $calendarObjects = $result->fetchAll();
+ $result->closeCursor();
- return array_map(function (array $array): array {
- $array['calendarid'] = (int)$array['calendarid'];
- $array['calendartype'] = (int)$array['calendartype'];
- $array['calendardata'] = $this->readBlob($array['calendardata']);
+ return array_map(function (array $array): array {
+ $array['calendarid'] = (int)$array['calendarid'];
+ $array['calendartype'] = (int)$array['calendartype'];
+ $array['calendardata'] = $this->readBlob($array['calendardata']);
- return $array;
- }, $calendarObjects);
+ return $array;
+ }, $calendarObjects);
+ }, $this->db);
}
/**
@@ -2254,84 +2277,86 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return array
*/
public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
- // Current synctoken
- $qb = $this->db->getQueryBuilder();
- $qb->select('synctoken')
- ->from('calendars')
- ->where(
- $qb->expr()->eq('id', $qb->createNamedParameter($calendarId))
- );
- $stmt = $qb->executeQuery();
- $currentToken = $stmt->fetchOne();
-
- if ($currentToken === false) {
- return null;
- }
-
- $result = [
- 'syncToken' => $currentToken,
- 'added' => [],
- 'modified' => [],
- 'deleted' => [],
- ];
-
- if ($syncToken) {
+ return $this->atomic(function () use ($calendarId, $syncToken, $syncLevel, $limit, $calendarType) {
+ // Current synctoken
$qb = $this->db->getQueryBuilder();
-
- $qb->select('uri', 'operation')
- ->from('calendarchanges')
+ $qb->select('synctoken')
+ ->from('calendars')
->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
+ $qb->expr()->eq('id', $qb->createNamedParameter($calendarId))
+ );
$stmt = $qb->executeQuery();
- $changes = [];
+ $currentToken = $stmt->fetchOne();
- // 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'];
+ if ($currentToken === false) {
+ return null;
}
- $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;
+ $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.
+ $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();
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ $stmt->closeCursor();
}
- } else {
- // No synctoken supplied, this is the initial sync.
- $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();
- $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
- $stmt->closeCursor();
- }
- return $result;
+ return $result;
+ }, $this->db);
}
/**
@@ -2473,35 +2498,37 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function updateSubscription($subscriptionId, PropPatch $propPatch) {
- $supportedProperties = array_keys($this->subscriptionPropertyMap);
- $supportedProperties[] = '{http://calendarserver.org/ns/}source';
-
- $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
- $newValues = [];
-
- foreach ($mutations as $propertyName => $propertyValue) {
- if ($propertyName === '{http://calendarserver.org/ns/}source') {
- $newValues['source'] = $propertyValue->getHref();
- } else {
- $fieldName = $this->subscriptionPropertyMap[$propertyName][0];
- $newValues[$fieldName] = $propertyValue;
+ $this->atomic(function () use ($subscriptionId, $propPatch) {
+ $supportedProperties = array_keys($this->subscriptionPropertyMap);
+ $supportedProperties[] = '{http://calendarserver.org/ns/}source';
+
+ $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
+ $newValues = [];
+
+ foreach ($mutations as $propertyName => $propertyValue) {
+ if ($propertyName === '{http://calendarserver.org/ns/}source') {
+ $newValues['source'] = $propertyValue->getHref();
+ } else {
+ $fieldName = $this->subscriptionPropertyMap[$propertyName][0];
+ $newValues[$fieldName] = $propertyValue;
+ }
}
- }
- $query = $this->db->getQueryBuilder();
- $query->update('calendarsubscriptions')
- ->set('lastmodified', $query->createNamedParameter(time()));
- foreach ($newValues as $fieldName => $value) {
- $query->set($fieldName, $query->createNamedParameter($value));
- }
- $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->update('calendarsubscriptions')
+ ->set('lastmodified', $query->createNamedParameter(time()));
+ foreach ($newValues as $fieldName => $value) {
+ $query->set($fieldName, $query->createNamedParameter($value));
+ }
+ $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
+ ->executeStatement();
- $subscriptionRow = $this->getSubscriptionById($subscriptionId);
- $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
+ $subscriptionRow = $this->getSubscriptionById($subscriptionId);
+ $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
- return true;
- });
+ return true;
+ });
+ }, $this->db);
}
/**
@@ -2511,32 +2538,34 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return void
*/
public function deleteSubscription($subscriptionId) {
- $subscriptionRow = $this->getSubscriptionById($subscriptionId);
+ $this->atomic(function () use ($subscriptionId) {
+ $subscriptionRow = $this->getSubscriptionById($subscriptionId);
- $query = $this->db->getQueryBuilder();
- $query->delete('calendarsubscriptions')
- ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->delete('calendarsubscriptions')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
+ ->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)))
- ->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)))
+ ->executeStatement();
- $query->delete('calendarchanges')
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
- ->executeStatement();
+ $query->delete('calendarchanges')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->executeStatement();
- $query->delete($this->dbObjectPropertiesTable)
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
- ->executeStatement();
+ $query->delete($this->dbObjectPropertiesTable)
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->executeStatement();
- if ($subscriptionRow) {
- $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
- }
+ if ($subscriptionRow) {
+ $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
+ }
+ }, $this->db);
}
/**
@@ -2657,32 +2686,35 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param int $calendarType
* @return void
*/
- protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
+ protected function addChange(int $calendarId, string $objectUri, int $operation, int $calendarType = self::CALENDAR_TYPE_CALENDAR): void {
$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
- $query = $this->db->getQueryBuilder();
- $query->select('synctoken')
- ->from($table)
- ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
- $result = $query->executeQuery();
- $syncToken = (int)$result->fetchOne();
- $result->closeCursor();
+ $this->atomic(function () use ($calendarId, $objectUri, $operation, $calendarType, $table) {
+ $query = $this->db->getQueryBuilder();
+ $query->select('synctoken')
+ ->from($table)
+ ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
+ $result = $query->executeQuery();
+ $syncToken = (int)$result->fetchOne();
+ $result->closeCursor();
- $query = $this->db->getQueryBuilder();
- $query->insert('calendarchanges')
- ->values([
- 'uri' => $query->createNamedParameter($objectUri),
- 'synctoken' => $query->createNamedParameter($syncToken),
- 'calendarid' => $query->createNamedParameter($calendarId),
- 'operation' => $query->createNamedParameter($operation),
- 'calendartype' => $query->createNamedParameter($calendarType),
- ])
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->insert('calendarchanges')
+ ->values([
+ 'uri' => $query->createNamedParameter($objectUri),
+ 'synctoken' => $query->createNamedParameter($syncToken),
+ 'calendarid' => $query->createNamedParameter($calendarId),
+ 'operation' => $query->createNamedParameter($operation),
+ 'calendartype' => $query->createNamedParameter($calendarType),
+ ])
+ ->executeStatement();
- $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
- $stmt->execute([
- $calendarId
- ]);
+ $query = $this->db->getQueryBuilder();
+ $query->update($table)
+ ->set('synctoken', $query->createNamedParameter($syncToken + 1, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
+ ->executeStatement();
+ }, $this->db);
}
/**
@@ -2805,16 +2837,18 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param list<string> $remove
*/
public function updateShares(IShareable $shareable, array $add, array $remove): void {
- $calendarId = $shareable->getResourceId();
- $calendarRow = $this->getCalendarById($calendarId);
- if ($calendarRow === null) {
- throw new \RuntimeException('Trying to update shares for innexistant calendar: ' . $calendarId);
- }
- $oldShares = $this->getShares($calendarId);
+ $this->atomic(function () use ($shareable, $add, $remove) {
+ $calendarId = $shareable->getResourceId();
+ $calendarRow = $this->getCalendarById($calendarId);
+ if ($calendarRow === null) {
+ throw new \RuntimeException('Trying to update shares for innexistant calendar: ' . $calendarId);
+ }
+ $oldShares = $this->getShares($calendarId);
- $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
+ $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
- $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent($calendarId, $calendarRow, $oldShares, $add, $remove));
+ $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent($calendarId, $calendarRow, $oldShares, $add, $remove));
+ }, $this->db);
}
/**
@@ -2830,32 +2864,34 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return string|null
*/
public function setPublishStatus($value, $calendar) {
- $calendarId = $calendar->getResourceId();
- $calendarData = $this->getCalendarById($calendarId);
+ return $this->atomic(function () use ($value, $calendar) {
+ $calendarId = $calendar->getResourceId();
+ $calendarData = $this->getCalendarById($calendarId);
- $query = $this->db->getQueryBuilder();
- if ($value) {
- $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
- $query->insert('dav_shares')
- ->values([
- 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
- 'type' => $query->createNamedParameter('calendar'),
- 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
- 'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
- 'publicuri' => $query->createNamedParameter($publicUri)
- ]);
- $query->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ if ($value) {
+ $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
+ $query->insert('dav_shares')
+ ->values([
+ 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
+ 'type' => $query->createNamedParameter('calendar'),
+ 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
+ 'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
+ 'publicuri' => $query->createNamedParameter($publicUri)
+ ]);
+ $query->executeStatement();
- $this->dispatcher->dispatchTyped(new CalendarPublishedEvent($calendarId, $calendarData, $publicUri));
- return $publicUri;
- }
- $query->delete('dav_shares')
- ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
- ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
- $query->executeStatement();
+ $this->dispatcher->dispatchTyped(new CalendarPublishedEvent($calendarId, $calendarData, $publicUri));
+ return $publicUri;
+ }
+ $query->delete('dav_shares')
+ ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
+ ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
+ $query->executeStatement();
- $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData));
- return null;
+ $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData));
+ return null;
+ }, $this->db);
}
/**
@@ -2893,127 +2929,133 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param int $calendarType
*/
public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
- $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
-
- try {
- $vCalendar = $this->readCalendarData($calendarData);
- } catch (\Exception $ex) {
- return;
- }
-
- $this->purgeProperties($calendarId, $objectId);
-
- $query = $this->db->getQueryBuilder();
- $query->insert($this->dbObjectPropertiesTable)
- ->values(
- [
- 'calendarid' => $query->createNamedParameter($calendarId),
- 'calendartype' => $query->createNamedParameter($calendarType),
- 'objectid' => $query->createNamedParameter($objectId),
- 'name' => $query->createParameter('name'),
- 'parameter' => $query->createParameter('parameter'),
- 'value' => $query->createParameter('value'),
- ]
- );
+ $this->atomic(function () use ($calendarId, $objectUri, $calendarData, $calendarType) {
+ $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
- $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
- foreach ($vCalendar->getComponents() as $component) {
- if (!in_array($component->name, $indexComponents)) {
- continue;
+ try {
+ $vCalendar = $this->readCalendarData($calendarData);
+ } catch (\Exception $ex) {
+ return;
}
- foreach ($component->children() as $property) {
- if (in_array($property->name, self::INDEXED_PROPERTIES, true)) {
- $value = $property->getValue();
- // is this a shitty db?
- if (!$this->db->supports4ByteText()) {
- $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
- }
- $value = mb_strcut($value, 0, 254);
+ $this->purgeProperties($calendarId, $objectId);
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert($this->dbObjectPropertiesTable)
+ ->values(
+ [
+ 'calendarid' => $query->createNamedParameter($calendarId),
+ 'calendartype' => $query->createNamedParameter($calendarType),
+ 'objectid' => $query->createNamedParameter($objectId),
+ 'name' => $query->createParameter('name'),
+ 'parameter' => $query->createParameter('parameter'),
+ 'value' => $query->createParameter('value'),
+ ]
+ );
- $query->setParameter('name', $property->name);
- $query->setParameter('parameter', null);
- $query->setParameter('value', $value);
- $query->executeStatement();
+ $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
+ foreach ($vCalendar->getComponents() as $component) {
+ if (!in_array($component->name, $indexComponents)) {
+ continue;
}
- if (array_key_exists($property->name, self::$indexParameters)) {
- $parameters = $property->parameters();
- $indexedParametersForProperty = self::$indexParameters[$property->name];
+ foreach ($component->children() as $property) {
+ if (in_array($property->name, self::INDEXED_PROPERTIES, true)) {
+ $value = $property->getValue();
+ // is this a shitty db?
+ if (!$this->db->supports4ByteText()) {
+ $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
+ }
+ $value = mb_strcut($value, 0, 254);
- foreach ($parameters as $key => $value) {
- if (in_array($key, $indexedParametersForProperty)) {
- // is this a shitty db?
- if ($this->db->supports4ByteText()) {
- $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
- }
+ $query->setParameter('name', $property->name);
+ $query->setParameter('parameter', null);
+ $query->setParameter('value', $value);
+ $query->executeStatement();
+ }
- $query->setParameter('name', $property->name);
- $query->setParameter('parameter', mb_strcut($key, 0, 254));
- $query->setParameter('value', mb_strcut($value, 0, 254));
- $query->executeStatement();
+ if (array_key_exists($property->name, self::$indexParameters)) {
+ $parameters = $property->parameters();
+ $indexedParametersForProperty = self::$indexParameters[$property->name];
+
+ foreach ($parameters as $key => $value) {
+ if (in_array($key, $indexedParametersForProperty)) {
+ // is this a shitty db?
+ if ($this->db->supports4ByteText()) {
+ $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
+ }
+
+ $query->setParameter('name', $property->name);
+ $query->setParameter('parameter', mb_strcut($key, 0, 254));
+ $query->setParameter('value', mb_strcut($value, 0, 254));
+ $query->executeStatement();
+ }
}
}
}
}
- }
+ }, $this->db);
}
/**
* deletes all birthday calendars
*/
public function deleteAllBirthdayCalendars() {
- $query = $this->db->getQueryBuilder();
- $result = $query->select(['id'])->from('calendars')
- ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
- ->executeQuery();
+ $this->atomic(function () {
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select(['id'])->from('calendars')
+ ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
+ ->executeQuery();
- $ids = $result->fetchAll();
- $result->closeCursor();
- foreach ($ids as $id) {
- $this->deleteCalendar(
- $id['id'],
- true // No data to keep in the trashbin, if the user re-enables then we regenerate
- );
- }
+ $ids = $result->fetchAll();
+ $result->closeCursor();
+ foreach ($ids as $id) {
+ $this->deleteCalendar(
+ $id['id'],
+ true // No data to keep in the trashbin, if the user re-enables then we regenerate
+ );
+ }
+ }, $this->db);
}
/**
* @param $subscriptionId
*/
public function purgeAllCachedEventsForSubscription($subscriptionId) {
- $query = $this->db->getQueryBuilder();
- $query->select('uri')
- ->from('calendarobjects')
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
- $stmt = $query->executeQuery();
+ $this->atomic(function () use ($subscriptionId) {
+ $query = $this->db->getQueryBuilder();
+ $query->select('uri')
+ ->from('calendarobjects')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
+ $stmt = $query->executeQuery();
- $uris = [];
- foreach ($stmt->fetchAll() as $row) {
- $uris[] = $row['uri'];
- }
- $stmt->closeCursor();
+ $uris = [];
+ foreach ($stmt->fetchAll() as $row) {
+ $uris[] = $row['uri'];
+ }
+ $stmt->closeCursor();
- $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)))
- ->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)))
+ ->executeStatement();
- $query->delete('calendarchanges')
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
- ->executeStatement();
+ $query->delete('calendarchanges')
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->executeStatement();
- $query->delete($this->dbObjectPropertiesTable)
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
- ->executeStatement();
+ $query->delete($this->dbObjectPropertiesTable)
+ ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
+ ->executeStatement();
- foreach ($uris as $uri) {
- $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
- }
+ foreach ($uris as $uri) {
+ $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
+ }
+ }, $this->db);
}
/**
diff --git a/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php b/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php
index 505960ed662..e8cbfdd0ab3 100644
--- a/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php
+++ b/apps/dav/lib/CalDAV/Reminder/INotificationProvider.php
@@ -42,13 +42,13 @@ interface INotificationProvider {
* Send notification
*
* @param VEvent $vevent
- * @param string $calendarDisplayName
+ * @param string|null $calendarDisplayName
* @param string[] $principalEmailAddresses All email addresses associated to the principal owning the calendar object
* @param IUser[] $users
* @return void
*/
public function send(VEvent $vevent,
- string $calendarDisplayName,
+ ?string $calendarDisplayName,
array $principalEmailAddresses,
array $users = []): void;
}
diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php
index 6986328facd..bccbec5fe3c 100644
--- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php
+++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/AbstractProvider.php
@@ -82,13 +82,13 @@ abstract class AbstractProvider implements INotificationProvider {
* Send notification
*
* @param VEvent $vevent
- * @param string $calendarDisplayName
+ * @param string|null $calendarDisplayName
* @param string[] $principalEmailAddresses
* @param IUser[] $users
* @return void
*/
abstract public function send(VEvent $vevent,
- string $calendarDisplayName,
+ ?string $calendarDisplayName,
array $principalEmailAddresses,
array $users = []): void;
@@ -185,4 +185,8 @@ abstract class AbstractProvider implements INotificationProvider {
return clone $vevent->DTSTART;
}
+
+ protected function getCalendarDisplayNameFallback(string $lang): string {
+ return $this->getL10NForLang($lang)->t('Untitled calendar');
+ }
}
diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php
index 32072688967..da275efdcf1 100644
--- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php
+++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php
@@ -70,13 +70,13 @@ class EmailProvider extends AbstractProvider {
* Send out notification via email
*
* @param VEvent $vevent
- * @param string $calendarDisplayName
+ * @param string|null $calendarDisplayName
* @param string[] $principalEmailAddresses
* @param array $users
* @throws \Exception
*/
public function send(VEvent $vevent,
- string $calendarDisplayName,
+ ?string $calendarDisplayName,
array $principalEmailAddresses,
array $users = []):void {
$fallbackLanguage = $this->getFallbackLanguage();
@@ -115,7 +115,7 @@ class EmailProvider extends AbstractProvider {
$template = $this->mailer->createEMailTemplate('dav.calendarReminder');
$template->addHeader();
$this->addSubjectAndHeading($template, $l10n, $vevent);
- $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent);
+ $this->addBulletList($template, $l10n, $calendarDisplayName ?? $this->getCalendarDisplayNameFallback($lang), $vevent);
$template->addFooter();
foreach ($emailAddresses as $emailAddress) {
diff --git a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php
index 833d74079aa..be8bafd2f35 100644
--- a/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php
+++ b/apps/dav/lib/CalDAV/Reminder/NotificationProvider/PushProvider.php
@@ -73,13 +73,13 @@ class PushProvider extends AbstractProvider {
* Send push notification to all users.
*
* @param VEvent $vevent
- * @param string $calendarDisplayName
+ * @param string|null $calendarDisplayName
* @param string[] $principalEmailAddresses
* @param IUser[] $users
* @throws \Exception
*/
public function send(VEvent $vevent,
- string $calendarDisplayName,
+ ?string $calendarDisplayName,
array $principalEmailAddresses,
array $users = []):void {
if ($this->config->getAppValue('dav', 'sendEventRemindersPush', 'no') !== 'yes') {
@@ -87,7 +87,6 @@ class PushProvider extends AbstractProvider {
}
$eventDetails = $this->extractEventDetails($vevent);
- $eventDetails['calendar_displayname'] = $calendarDisplayName;
$eventUUID = (string) $vevent->UID;
if (!$eventUUID) {
return;
@@ -95,6 +94,8 @@ class PushProvider extends AbstractProvider {
$eventUUIDHash = hash('sha256', $eventUUID, false);
foreach ($users as $user) {
+ $eventDetails['calendar_displayname'] = $calendarDisplayName ?? $this->getCalendarDisplayNameFallback($this->l10nFactory->getUserLanguage($user));
+
/** @var INotification $notification */
$notification = $this->manager->createNotification();
$notification->setApp(Application::APP_ID)
diff --git a/apps/dav/lib/CalDAV/Reminder/ReminderService.php b/apps/dav/lib/CalDAV/Reminder/ReminderService.php
index a2daa3cc98e..bca154a48e8 100644
--- a/apps/dav/lib/CalDAV/Reminder/ReminderService.php
+++ b/apps/dav/lib/CalDAV/Reminder/ReminderService.php
@@ -147,7 +147,14 @@ class ReminderService {
continue;
}
- $vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
+ try {
+ $vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
+ } catch (MaxInstancesExceededException $e) {
+ $this->logger->debug('Recurrence with too many instances detected, skipping VEVENT', ['exception' => $e]);
+ $this->backend->removeReminder($reminder['id']);
+ continue;
+ }
+
if (!$vevent) {
$this->logger->debug('Reminder {id} does not belong to a valid event', [
'id' => $reminder['id'],
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
index 76e84a2b54b..329197445dd 100644
--- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
@@ -217,10 +217,12 @@ class IMipPlugin extends SabreIMipPlugin {
$sender = substr($iTipMessage->sender, 7);
+ $replyingAttendee = null;
switch (strtolower($iTipMessage->method)) {
case self::METHOD_REPLY:
$method = self::METHOD_REPLY;
$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
+ $replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
break;
case self::METHOD_CANCEL:
$method = self::METHOD_CANCEL;
@@ -256,7 +258,7 @@ class IMipPlugin extends SabreIMipPlugin {
$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
$template->addHeader();
- $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified);
+ $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified, $replyingAttendee);
$this->imipService->addBulletList($template, $vEvent, $data);
// Only add response buttons to invitation requests: Fix Issue #11230
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php
index 034a59a98d4..8596500f320 100644
--- a/apps/dav/lib/CalDAV/Schedule/IMipService.php
+++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php
@@ -366,7 +366,7 @@ class IMipService {
* @param bool $isModified
*/
public function addSubjectAndHeading(IEMailTemplate $template,
- string $method, string $sender, string $summary, bool $isModified): void {
+ string $method, string $sender, string $summary, bool $isModified, ?Property $replyingAttendee = null): void {
if ($method === IMipPlugin::METHOD_CANCEL) {
// TRANSLATORS Subject for email, when an invitation is cancelled. Ex: "Cancelled: {{Event Name}}"
$template->setSubject($this->l10n->t('Cancelled: %1$s', [$summary]));
@@ -374,7 +374,24 @@ class IMipService {
} elseif ($method === IMipPlugin::METHOD_REPLY) {
// TRANSLATORS Subject for email, when an invitation is replied to. Ex: "Re: {{Event Name}}"
$template->setSubject($this->l10n->t('Re: %1$s', [$summary]));
- $template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
+ // Build the strings
+ $partstat = (isset($replyingAttendee)) ? $replyingAttendee->offsetGet('PARTSTAT') : null;
+ $partstat = ($partstat instanceof Parameter) ? $partstat->getValue() : null;
+ switch ($partstat) {
+ case 'ACCEPTED':
+ $template->addHeading($this->l10n->t('%1$s has accepted your invitation', [$sender]));
+ break;
+ case 'TENTATIVE':
+ $template->addHeading($this->l10n->t('%1$s has tentatively accepted your invitation', [$sender]));
+ break;
+ case 'DECLINED':
+ $template->addHeading($this->l10n->t('%1$s has declined your invitation', [$sender]));
+ break;
+ case null:
+ default:
+ $template->addHeading($this->l10n->t('%1$s has responded to your invitation', [$sender]));
+ break;
+ }
} elseif ($method === IMipPlugin::METHOD_REQUEST && $isModified) {
// TRANSLATORS Subject for email, when an invitation is updated. Ex: "Invitation updated: {{Event Name}}"
$template->setSubject($this->l10n->t('Invitation updated: %1$s', [$summary]));
@@ -603,4 +620,17 @@ class IMipService {
$template->addBodyText($html, $text);
}
+
+ public function getReplyingAttendee(Message $iTipMessage): ?Property {
+ /** @var VEvent $vevent */
+ $vevent = $iTipMessage->message->VEVENT;
+ $attendees = $vevent->select('ATTENDEE');
+ foreach ($attendees as $attendee) {
+ /** @var Property $attendee */
+ if (strcasecmp($attendee->getValue(), $iTipMessage->sender) === 0) {
+ return $attendee;
+ }
+ }
+ return null;
+ }
}
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 666f1e7a85c..577d7282eae 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -50,7 +50,6 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\IGroupManager;
-use OCP\IUser;
use OCP\IUserManager;
use PDO;
use Sabre\CardDAV\Backend\BackendInterface;
@@ -61,7 +60,6 @@ use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
class CardDavBackend implements BackendInterface, SyncSupport {
-
use TTransactional;
public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
@@ -145,87 +143,89 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return array
*/
public function getAddressBooksForUser($principalUri) {
- $principalUriOriginal = $principalUri;
- $principalUri = $this->convertPrincipal($principalUri, true);
- $query = $this->db->getQueryBuilder();
- $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
- ->from('addressbooks')
- ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
+ return $this->atomic(function () use ($principalUri) {
+ $principalUriOriginal = $principalUri;
+ $principalUri = $this->convertPrincipal($principalUri, true);
+ $query = $this->db->getQueryBuilder();
+ $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
+ ->from('addressbooks')
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
- $addressBooks = [];
+ $addressBooks = [];
- $result = $query->execute();
- while ($row = $result->fetch()) {
- $addressBooks[$row['id']] = [
- 'id' => $row['id'],
- 'uri' => $row['uri'],
- 'principaluri' => $this->convertPrincipal($row['principaluri'], false),
- '{DAV:}displayname' => $row['displayname'],
- '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
- '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
- '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
- ];
-
- $this->addOwnerPrincipal($addressBooks[$row['id']]);
- }
- $result->closeCursor();
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $addressBooks[$row['id']] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $this->convertPrincipal($row['principaluri'], false),
+ '{DAV:}displayname' => $row['displayname'],
+ '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
+ ];
+
+ $this->addOwnerPrincipal($addressBooks[$row['id']]);
+ }
+ $result->closeCursor();
- // query for shared addressbooks
- $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
- $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
+ // query for shared addressbooks
+ $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
+ $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
- $principals[] = $principalUri;
+ $principals[] = $principalUri;
- $query = $this->db->getQueryBuilder();
- $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
- ->from('dav_shares', 's')
- ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
- ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
- ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
- ->setParameter('type', 'addressbook')
- ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
- ->execute();
-
- $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
- while ($row = $result->fetch()) {
- if ($row['principaluri'] === $principalUri) {
- continue;
- }
+ $query = $this->db->getQueryBuilder();
+ $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
+ ->from('dav_shares', 's')
+ ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
+ ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
+ ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
+ ->setParameter('type', 'addressbook')
+ ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
+ ->execute();
- $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
- if (isset($addressBooks[$row['id']])) {
- if ($readOnly) {
- // New share can not have more permissions then the old one.
- continue;
- }
- if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
- $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
- // Old share is already read-write, no more permissions can be gained
+ $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
+ while ($row = $result->fetch()) {
+ if ($row['principaluri'] === $principalUri) {
continue;
}
- }
-
- [, $name] = \Sabre\Uri\split($row['principaluri']);
- $uri = $row['uri'] . '_shared_by_' . $name;
- $displayName = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? $name ?? '') . ')';
- $addressBooks[$row['id']] = [
- 'id' => $row['id'],
- 'uri' => $uri,
- 'principaluri' => $principalUriOriginal,
- '{DAV:}displayname' => $displayName,
- '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
- '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
- '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
- '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
- $readOnlyPropertyName => $readOnly,
- ];
+ $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
+ if (isset($addressBooks[$row['id']])) {
+ if ($readOnly) {
+ // New share can not have more permissions then the old one.
+ continue;
+ }
+ if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
+ $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
+ // Old share is already read-write, no more permissions can be gained
+ continue;
+ }
+ }
- $this->addOwnerPrincipal($addressBooks[$row['id']]);
- }
- $result->closeCursor();
+ [, $name] = \Sabre\Uri\split($row['principaluri']);
+ $uri = $row['uri'] . '_shared_by_' . $name;
+ $displayName = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? $name ?? '') . ')';
+
+ $addressBooks[$row['id']] = [
+ 'id' => $row['id'],
+ 'uri' => $uri,
+ 'principaluri' => $principalUriOriginal,
+ '{DAV:}displayname' => $displayName,
+ '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
+ $readOnlyPropertyName => $readOnly,
+ ];
+
+ $this->addOwnerPrincipal($addressBooks[$row['id']]);
+ }
+ $result->closeCursor();
- return array_values($addressBooks);
+ return array_values($addressBooks);
+ }, $this->db);
}
public function getUsersOwnAddressBooks($principalUri) {
@@ -333,40 +333,42 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return void
*/
public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
- $supportedProperties = [
- '{DAV:}displayname',
- '{' . Plugin::NS_CARDDAV . '}addressbook-description',
- ];
+ $this->atomic(function () use ($addressBookId, $propPatch) {
+ $supportedProperties = [
+ '{DAV:}displayname',
+ '{' . Plugin::NS_CARDDAV . '}addressbook-description',
+ ];
- $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
- $updates = [];
- foreach ($mutations as $property => $newValue) {
- switch ($property) {
- case '{DAV:}displayname':
- $updates['displayname'] = $newValue;
- break;
- case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
- $updates['description'] = $newValue;
- break;
+ $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
+ $updates = [];
+ foreach ($mutations as $property => $newValue) {
+ switch ($property) {
+ case '{DAV:}displayname':
+ $updates['displayname'] = $newValue;
+ break;
+ case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
+ $updates['description'] = $newValue;
+ break;
+ }
}
- }
- $query = $this->db->getQueryBuilder();
- $query->update('addressbooks');
+ $query = $this->db->getQueryBuilder();
+ $query->update('addressbooks');
- foreach ($updates as $key => $value) {
- $query->set($key, $query->createNamedParameter($value));
- }
- $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
- ->executeStatement();
+ foreach ($updates as $key => $value) {
+ $query->set($key, $query->createNamedParameter($value));
+ }
+ $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
+ ->executeStatement();
- $this->addChange($addressBookId, "", 2);
+ $this->addChange($addressBookId, "", 2);
- $addressBookRow = $this->getAddressBookById((int)$addressBookId);
- $shares = $this->getShares((int)$addressBookId);
- $this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
+ $addressBookRow = $this->getAddressBookById((int)$addressBookId);
+ $shares = $this->getShares((int)$addressBookId);
+ $this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
- return true;
- });
+ return true;
+ });
+ }, $this->db);
}
/**
@@ -410,7 +412,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$values['displayname'] = $url;
}
- [$addressBookId, $addressBookRow] = $this->atomic(function() use ($values) {
+ [$addressBookId, $addressBookRow] = $this->atomic(function () use ($values) {
$query = $this->db->getQueryBuilder();
$query->insert('addressbooks')
->values([
@@ -442,38 +444,40 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return void
*/
public function deleteAddressBook($addressBookId) {
- $addressBookId = (int)$addressBookId;
- $addressBookData = $this->getAddressBookById($addressBookId);
- $shares = $this->getShares($addressBookId);
+ $this->atomic(function () use ($addressBookId) {
+ $addressBookId = (int)$addressBookId;
+ $addressBookData = $this->getAddressBookById($addressBookId);
+ $shares = $this->getShares($addressBookId);
- $query = $this->db->getQueryBuilder();
- $query->delete($this->dbCardsTable)
- ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
- ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->dbCardsTable)
+ ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
+ ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
- $query = $this->db->getQueryBuilder();
- $query->delete('addressbookchanges')
- ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
- ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->delete('addressbookchanges')
+ ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
+ ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
- $query = $this->db->getQueryBuilder();
- $query->delete('addressbooks')
- ->where($query->expr()->eq('id', $query->createParameter('id')))
- ->setParameter('id', $addressBookId, IQueryBuilder::PARAM_INT)
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->delete('addressbooks')
+ ->where($query->expr()->eq('id', $query->createParameter('id')))
+ ->setParameter('id', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
- $this->sharingBackend->deleteAllShares($addressBookId);
+ $this->sharingBackend->deleteAllShares($addressBookId);
- $query = $this->db->getQueryBuilder();
- $query->delete($this->dbCardsPropertiesTable)
- ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId, IQueryBuilder::PARAM_INT)))
- ->executeStatement();
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->dbCardsPropertiesTable)
+ ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
- if ($addressBookData) {
- $this->dispatcher->dispatchTyped(new AddressBookDeletedEvent($addressBookId, $addressBookData, $shares));
- }
+ if ($addressBookData) {
+ $this->dispatcher->dispatchTyped(new AddressBookDeletedEvent($addressBookId, $addressBookData, $shares));
+ }
+ }, $this->db);
}
/**
@@ -631,47 +635,48 @@ class CardDavBackend implements BackendInterface, SyncSupport {
public function createCard($addressBookId, $cardUri, $cardData, bool $checkAlreadyExists = true) {
$etag = md5($cardData);
$uid = $this->getUID($cardData);
-
- if ($checkAlreadyExists) {
- $q = $this->db->getQueryBuilder();
- $q->select('uid')
- ->from($this->dbCardsTable)
- ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
- ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
- ->setMaxResults(1);
- $result = $q->executeQuery();
- $count = (bool)$result->fetchOne();
- $result->closeCursor();
- if ($count) {
- throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
+ return $this->atomic(function () use ($addressBookId, $cardUri, $cardData, $checkAlreadyExists, $etag, $uid) {
+ if ($checkAlreadyExists) {
+ $q = $this->db->getQueryBuilder();
+ $q->select('uid')
+ ->from($this->dbCardsTable)
+ ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
+ ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
+ ->setMaxResults(1);
+ $result = $q->executeQuery();
+ $count = (bool)$result->fetchOne();
+ $result->closeCursor();
+ if ($count) {
+ throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
+ }
}
- }
- $query = $this->db->getQueryBuilder();
- $query->insert('cards')
- ->values([
- 'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
- 'uri' => $query->createNamedParameter($cardUri),
- 'lastmodified' => $query->createNamedParameter(time()),
- 'addressbookid' => $query->createNamedParameter($addressBookId),
- 'size' => $query->createNamedParameter(strlen($cardData)),
- 'etag' => $query->createNamedParameter($etag),
- 'uid' => $query->createNamedParameter($uid),
- ])
- ->execute();
+ $query = $this->db->getQueryBuilder();
+ $query->insert('cards')
+ ->values([
+ 'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
+ 'uri' => $query->createNamedParameter($cardUri),
+ 'lastmodified' => $query->createNamedParameter(time()),
+ 'addressbookid' => $query->createNamedParameter($addressBookId),
+ 'size' => $query->createNamedParameter(strlen($cardData)),
+ 'etag' => $query->createNamedParameter($etag),
+ 'uid' => $query->createNamedParameter($uid),
+ ])
+ ->execute();
- $etagCacheKey = "$addressBookId#$cardUri";
- $this->etagCache[$etagCacheKey] = $etag;
+ $etagCacheKey = "$addressBookId#$cardUri";
+ $this->etagCache[$etagCacheKey] = $etag;
- $this->addChange($addressBookId, $cardUri, 1);
- $this->updateProperties($addressBookId, $cardUri, $cardData);
+ $this->addChange($addressBookId, $cardUri, 1);
+ $this->updateProperties($addressBookId, $cardUri, $cardData);
- $addressBookData = $this->getAddressBookById($addressBookId);
- $shares = $this->getShares($addressBookId);
- $objectRow = $this->getCard($addressBookId, $cardUri);
- $this->dispatcher->dispatchTyped(new CardCreatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
+ $addressBookData = $this->getAddressBookById($addressBookId);
+ $shares = $this->getShares($addressBookId);
+ $objectRow = $this->getCard($addressBookId, $cardUri);
+ $this->dispatcher->dispatchTyped(new CardCreatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- return '"' . $etag . '"';
+ return '"' . $etag . '"';
+ }, $this->db);
}
/**
@@ -702,34 +707,37 @@ class CardDavBackend implements BackendInterface, SyncSupport {
public function updateCard($addressBookId, $cardUri, $cardData) {
$uid = $this->getUID($cardData);
$etag = md5($cardData);
- $query = $this->db->getQueryBuilder();
- // check for recently stored etag and stop if it is the same
- $etagCacheKey = "$addressBookId#$cardUri";
- if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
- return '"' . $etag . '"';
- }
+ return $this->atomic(function () use ($addressBookId, $cardUri, $cardData, $uid, $etag) {
+ $query = $this->db->getQueryBuilder();
- $query->update($this->dbCardsTable)
- ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
- ->set('lastmodified', $query->createNamedParameter(time()))
- ->set('size', $query->createNamedParameter(strlen($cardData)))
- ->set('etag', $query->createNamedParameter($etag))
- ->set('uid', $query->createNamedParameter($uid))
- ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
- ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
- ->execute();
+ // check for recently stored etag and stop if it is the same
+ $etagCacheKey = "$addressBookId#$cardUri";
+ if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
+ return '"' . $etag . '"';
+ }
- $this->etagCache[$etagCacheKey] = $etag;
+ $query->update($this->dbCardsTable)
+ ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
+ ->set('lastmodified', $query->createNamedParameter(time()))
+ ->set('size', $query->createNamedParameter(strlen($cardData)))
+ ->set('etag', $query->createNamedParameter($etag))
+ ->set('uid', $query->createNamedParameter($uid))
+ ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
+ ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+ ->execute();
+
+ $this->etagCache[$etagCacheKey] = $etag;
- $this->addChange($addressBookId, $cardUri, 2);
- $this->updateProperties($addressBookId, $cardUri, $cardData);
+ $this->addChange($addressBookId, $cardUri, 2);
+ $this->updateProperties($addressBookId, $cardUri, $cardData);
- $addressBookData = $this->getAddressBookById($addressBookId);
- $shares = $this->getShares($addressBookId);
- $objectRow = $this->getCard($addressBookId, $cardUri);
- $this->dispatcher->dispatchTyped(new CardUpdatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- return '"' . $etag . '"';
+ $addressBookData = $this->getAddressBookById($addressBookId);
+ $shares = $this->getShares($addressBookId);
+ $objectRow = $this->getCard($addressBookId, $cardUri);
+ $this->dispatcher->dispatchTyped(new CardUpdatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
+ return '"' . $etag . '"';
+ }, $this->db);
}
/**
@@ -740,32 +748,34 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return bool
*/
public function deleteCard($addressBookId, $cardUri) {
- $addressBookData = $this->getAddressBookById($addressBookId);
- $shares = $this->getShares($addressBookId);
- $objectRow = $this->getCard($addressBookId, $cardUri);
-
- try {
- $cardId = $this->getCardId($addressBookId, $cardUri);
- } catch (\InvalidArgumentException $e) {
- $cardId = null;
- }
- $query = $this->db->getQueryBuilder();
- $ret = $query->delete($this->dbCardsTable)
- ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
- ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
- ->executeStatement();
+ return $this->atomic(function () use ($addressBookId, $cardUri) {
+ $addressBookData = $this->getAddressBookById($addressBookId);
+ $shares = $this->getShares($addressBookId);
+ $objectRow = $this->getCard($addressBookId, $cardUri);
+
+ try {
+ $cardId = $this->getCardId($addressBookId, $cardUri);
+ } catch (\InvalidArgumentException $e) {
+ $cardId = null;
+ }
+ $query = $this->db->getQueryBuilder();
+ $ret = $query->delete($this->dbCardsTable)
+ ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
+ ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
+ ->executeStatement();
- $this->addChange($addressBookId, $cardUri, 3);
+ $this->addChange($addressBookId, $cardUri, 3);
- if ($ret === 1) {
- if ($cardId !== null) {
- $this->dispatcher->dispatchTyped(new CardDeletedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- $this->purgeProperties($addressBookId, $cardId);
+ if ($ret === 1) {
+ if ($cardId !== null) {
+ $this->dispatcher->dispatchTyped(new CardDeletedEvent($addressBookId, $addressBookData, $shares, $objectRow));
+ $this->purgeProperties($addressBookId, $cardId);
+ }
+ return true;
}
- return true;
- }
- return false;
+ return false;
+ }, $this->db);
}
/**
@@ -826,81 +836,83 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*/
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
// Current synctoken
- $qb = $this->db->getQueryBuilder();
- $qb->select('synctoken')
- ->from('addressbooks')
- ->where(
- $qb->expr()->eq('id', $qb->createNamedParameter($addressBookId))
- );
- $stmt = $qb->executeQuery();
- $currentToken = $stmt->fetchOne();
- $stmt->closeCursor();
-
- if (is_null($currentToken)) {
- return [];
- }
-
- $result = [
- 'syncToken' => $currentToken,
- 'added' => [],
- 'modified' => [],
- 'deleted' => [],
- ];
-
- if ($syncToken) {
+ return $this->atomic(function () use ($addressBookId, $syncToken, $syncLevel, $limit) {
$qb = $this->db->getQueryBuilder();
- $qb->select('uri', 'operation')
- ->from('addressbookchanges')
+ $qb->select('synctoken')
+ ->from('addressbooks')
->where(
- $qb->expr()->andX(
- $qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)),
- $qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)),
- $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId))
- )
- )->orderBy('synctoken');
+ $qb->expr()->eq('id', $qb->createNamedParameter($addressBookId))
+ );
+ $stmt = $qb->executeQuery();
+ $currentToken = $stmt->fetchOne();
+ $stmt->closeCursor();
- if (is_int($limit) && $limit > 0) {
- $qb->setMaxResults($limit);
+ if (is_null($currentToken)) {
+ return [];
}
- // Fetching all changes
- $stmt = $qb->executeQuery();
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
- $changes = [];
+ if ($syncToken) {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('uri', 'operation')
+ ->from('addressbookchanges')
+ ->where(
+ $qb->expr()->andX(
+ $qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)),
+ $qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)),
+ $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId))
+ )
+ )->orderBy('synctoken');
+
+ if (is_int($limit) && $limit > 0) {
+ $qb->setMaxResults($limit);
+ }
- // This loop ensures that any duplicates are overwritten, only the
- // last change on a node is relevant.
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
- $changes[$row['uri']] = $row['operation'];
- }
- $stmt->closeCursor();
+ // Fetching all changes
+ $stmt = $qb->executeQuery();
- 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;
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $changes[$row['uri']] = $row['operation'];
}
+ $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 {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('uri')
+ ->from('cards')
+ ->where(
+ $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId))
+ );
+ // No synctoken supplied, this is the initial sync.
+ $stmt = $qb->executeQuery();
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ $stmt->closeCursor();
}
- } else {
- $qb = $this->db->getQueryBuilder();
- $qb->select('uri')
- ->from('cards')
- ->where(
- $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId))
- );
- // No synctoken supplied, this is the initial sync.
- $stmt = $qb->executeQuery();
- $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
- $stmt->closeCursor();
- }
- return $result;
+ return $result;
+ }, $this->db);
}
/**
@@ -911,19 +923,32 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param int $operation 1 = add, 2 = modify, 3 = delete
* @return void
*/
- protected function addChange($addressBookId, $objectUri, $operation) {
- $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
- $stmt = $this->db->prepare($sql);
- $stmt->execute([
- $objectUri,
- $addressBookId,
- $operation,
- $addressBookId
- ]);
- $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
- $stmt->execute([
- $addressBookId
- ]);
+ protected function addChange(int $addressBookId, string $objectUri, int $operation): void {
+ $this->atomic(function () use ($addressBookId, $objectUri, $operation) {
+ $query = $this->db->getQueryBuilder();
+ $query->select('synctoken')
+ ->from('addressbooks')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)));
+ $result = $query->executeQuery();
+ $syncToken = (int)$result->fetchOne();
+ $result->closeCursor();
+
+ $query = $this->db->getQueryBuilder();
+ $query->insert('addressbookchanges')
+ ->values([
+ 'uri' => $query->createNamedParameter($objectUri),
+ 'synctoken' => $query->createNamedParameter($syncToken),
+ 'addressbookid' => $query->createNamedParameter($addressBookId),
+ 'operation' => $query->createNamedParameter($operation),
+ ])
+ ->executeStatement();
+
+ $query = $this->db->getQueryBuilder();
+ $query->update('addressbooks')
+ ->set('synctoken', $query->createNamedParameter($syncToken + 1, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
+ ->executeStatement();
+ }, $this->db);
}
/**
@@ -973,13 +998,15 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param list<string> $remove
*/
public function updateShares(IShareable $shareable, array $add, array $remove): void {
- $addressBookId = $shareable->getResourceId();
- $addressBookData = $this->getAddressBookById($addressBookId);
- $oldShares = $this->getShares($addressBookId);
+ $this->atomic(function () use ($shareable, $add, $remove) {
+ $addressBookId = $shareable->getResourceId();
+ $addressBookData = $this->getAddressBookById($addressBookId);
+ $oldShares = $this->getShares($addressBookId);
- $this->sharingBackend->updateShares($shareable, $add, $remove);
+ $this->sharingBackend->updateShares($shareable, $add, $remove);
- $this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
+ $this->dispatcher->dispatchTyped(new AddressBookShareUpdatedEvent($addressBookId, $addressBookData, $oldShares, $add, $remove));
+ }, $this->db);
}
/**
@@ -998,7 +1025,9 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return array an array of contacts which are arrays of key-value-pairs
*/
public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
- return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
+ return $this->atomic(function () use ($addressBookId, $pattern, $searchProperties, $options) {
+ return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
+ }, $this->db);
}
/**
@@ -1014,11 +1043,13 @@ class CardDavBackend implements BackendInterface, SyncSupport {
string $pattern,
array $searchProperties,
array $options = []): array {
- $addressBookIds = array_map(static function ($row):int {
- return (int) $row['id'];
- }, $this->getAddressBooksForUser($principalUri));
+ return $this->atomic(function () use ($principalUri, $pattern, $searchProperties, $options) {
+ $addressBookIds = array_map(static function ($row):int {
+ return (int) $row['id'];
+ }, $this->getAddressBooksForUser($principalUri));
- return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
+ return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
+ }, $this->db);
}
/**
@@ -1219,27 +1250,24 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param string $vCardSerialized
*/
protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
- $cardId = $this->getCardId($addressBookId, $cardUri);
- $vCard = $this->readCard($vCardSerialized);
-
- $this->purgeProperties($addressBookId, $cardId);
-
- $query = $this->db->getQueryBuilder();
- $query->insert($this->dbCardsPropertiesTable)
- ->values(
- [
- 'addressbookid' => $query->createNamedParameter($addressBookId),
- 'cardid' => $query->createNamedParameter($cardId),
- 'name' => $query->createParameter('name'),
- 'value' => $query->createParameter('value'),
- 'preferred' => $query->createParameter('preferred')
- ]
- );
+ $this->atomic(function () use ($addressBookId, $cardUri, $vCardSerialized) {
+ $cardId = $this->getCardId($addressBookId, $cardUri);
+ $vCard = $this->readCard($vCardSerialized);
+ $this->purgeProperties($addressBookId, $cardId);
- $this->db->beginTransaction();
+ $query = $this->db->getQueryBuilder();
+ $query->insert($this->dbCardsPropertiesTable)
+ ->values(
+ [
+ 'addressbookid' => $query->createNamedParameter($addressBookId),
+ 'cardid' => $query->createNamedParameter($cardId),
+ 'name' => $query->createParameter('name'),
+ 'value' => $query->createParameter('value'),
+ 'preferred' => $query->createParameter('preferred')
+ ]
+ );
- try {
foreach ($vCard->children() as $property) {
if (!in_array($property->name, self::$indexProperties)) {
continue;
@@ -1256,10 +1284,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query->setParameter('preferred', $preferred);
$query->execute();
}
- $this->db->commit();
- } catch (\Exception $e) {
- $this->db->rollBack();
- }
+ }, $this->db);
}
/**
diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php
index 7ba5ffd2700..813f99dcbbd 100644
--- a/apps/dav/lib/DAV/Sharing/Backend.php
+++ b/apps/dav/lib/DAV/Sharing/Backend.php
@@ -29,12 +29,15 @@
namespace OCA\DAV\DAV\Sharing;
use OCA\DAV\Connector\Sabre\Principal;
+use OCP\AppFramework\Db\TTransactional;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
class Backend {
+ use TTransactional;
+
private IDBConnection $db;
private IUserManager $userManager;
private IGroupManager $groupManager;
@@ -58,18 +61,20 @@ class Backend {
* @param list<string> $remove
*/
public function updateShares(IShareable $shareable, array $add, array $remove): void {
- foreach ($add as $element) {
- $principal = $this->principalBackend->findByUri($element['href'], '');
- if ($principal !== '') {
- $this->shareWith($shareable, $element);
+ $this->atomic(function () use ($shareable, $add, $remove) {
+ foreach ($add as $element) {
+ $principal = $this->principalBackend->findByUri($element['href'], '');
+ if ($principal !== '') {
+ $this->shareWith($shareable, $element);
+ }
}
- }
- foreach ($remove as $element) {
- $principal = $this->principalBackend->findByUri($element, '');
- if ($principal !== '') {
- $this->unshare($shareable, $element);
+ foreach ($remove as $element) {
+ $principal = $this->principalBackend->findByUri($element, '');
+ if ($principal !== '') {
+ $this->unshare($shareable, $element);
+ }
}
- }
+ }, $this->db);
}
/**
diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php
index 22a697a1232..0cf6451d287 100644
--- a/apps/encryption/lib/Crypto/Crypt.php
+++ b/apps/encryption/lib/Crypto/Crypt.php
@@ -523,10 +523,12 @@ class Crypt {
$signature = $this->createSignature($data, $passPhrase);
$isCorrectHash = hash_equals($expectedSignature, $signature);
- if (!$isCorrectHash && $enforceSignature) {
- throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
- } elseif (!$isCorrectHash && !$enforceSignature) {
- $this->logger->info("Signature check skipped", ['app' => 'encryption']);
+ if (!$isCorrectHash) {
+ if ($enforceSignature) {
+ throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
+ } else {
+ $this->logger->info("Signature check skipped", ['app' => 'encryption']);
+ }
}
}
diff --git a/apps/files_versions/lib/Capabilities.php b/apps/files_versions/lib/Capabilities.php
index 6524943690a..e97a5c4aaa4 100644
--- a/apps/files_versions/lib/Capabilities.php
+++ b/apps/files_versions/lib/Capabilities.php
@@ -46,7 +46,7 @@ class Capabilities implements ICapability {
* @return array
*/
public function getCapabilities() {
- $groupFolderOrS3VersioningInstalled = $this->appManager->isInstalled('groupfolders') || $this->appManager->isInstalled('groupfolders');
+ $groupFolderOrS3VersioningInstalled = $this->appManager->isInstalled('groupfolders') || $this->appManager->isInstalled('files_versions_s3');
return [
'files' => [
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 7b093cc1a4e..ac3c786f5a6 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -130,7 +130,6 @@
</MoreSpecificImplementedParamType>
<NullableReturnStatement occurrences="2">
<code>null</code>
- <code>null</code>
</NullableReturnStatement>
</file>
<file src="apps/dav/lib/CalDAV/CalendarHome.php">
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 824e2e056c8..b1fa509d8c0 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -327,18 +327,20 @@ class PublicKeyTokenProvider implements IProvider {
throw new InvalidTokenException("Invalid token type");
}
- // When changing passwords all temp tokens are deleted
- $this->mapper->deleteTempToken($token);
-
- // Update the password for all tokens
- $tokens = $this->mapper->getTokenByUser($token->getUID());
- $hashedPassword = $this->hashPassword($password);
- foreach ($tokens as $t) {
- $publicKey = $t->getPublicKey();
- $t->setPassword($this->encryptPassword($password, $publicKey));
- $t->setPasswordHash($hashedPassword);
- $this->updateToken($t);
- }
+ $this->atomic(function () use ($password, $token) {
+ // When changing passwords all temp tokens are deleted
+ $this->mapper->deleteTempToken($token);
+
+ // Update the password for all tokens
+ $tokens = $this->mapper->getTokenByUser($token->getUID());
+ $hashedPassword = $this->hashPassword($password);
+ foreach ($tokens as $t) {
+ $publicKey = $t->getPublicKey();
+ $t->setPassword($this->encryptPassword($password, $publicKey));
+ $t->setPasswordHash($hashedPassword);
+ $this->updateToken($t);
+ }
+ }, $this->db);
}
private function hashPassword(string $password): string {
@@ -489,49 +491,51 @@ class PublicKeyTokenProvider implements IProvider {
return;
}
- // Update the password for all tokens
- $tokens = $this->mapper->getTokenByUser($uid);
- $newPasswordHash = null;
-
- /**
- * - true: The password hash could not be verified anymore
- * and the token needs to be updated with the newly encrypted password
- * - false: The hash could still be verified
- * - missing: The hash needs to be verified
- */
- $hashNeedsUpdate = [];
-
- foreach ($tokens as $t) {
- if (!isset($hashNeedsUpdate[$t->getPasswordHash()])) {
- if ($t->getPasswordHash() === null) {
- $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
- } elseif (!$this->hasher->verify(sha1($password) . $password, $t->getPasswordHash())) {
- $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
- } else {
- $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = false;
+ $this->atomic(function () use ($password, $uid) {
+ // Update the password for all tokens
+ $tokens = $this->mapper->getTokenByUser($uid);
+ $newPasswordHash = null;
+
+ /**
+ * - true: The password hash could not be verified anymore
+ * and the token needs to be updated with the newly encrypted password
+ * - false: The hash could still be verified
+ * - missing: The hash needs to be verified
+ */
+ $hashNeedsUpdate = [];
+
+ foreach ($tokens as $t) {
+ if (!isset($hashNeedsUpdate[$t->getPasswordHash()])) {
+ if ($t->getPasswordHash() === null) {
+ $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
+ } elseif (!$this->hasher->verify(sha1($password) . $password, $t->getPasswordHash())) {
+ $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
+ } else {
+ $hashNeedsUpdate[$t->getPasswordHash() ?: ''] = false;
+ }
}
- }
- $needsUpdating = $hashNeedsUpdate[$t->getPasswordHash() ?: ''] ?? true;
-
- if ($needsUpdating) {
- if ($newPasswordHash === null) {
- $newPasswordHash = $this->hashPassword($password);
+ $needsUpdating = $hashNeedsUpdate[$t->getPasswordHash() ?: ''] ?? true;
+
+ if ($needsUpdating) {
+ if ($newPasswordHash === null) {
+ $newPasswordHash = $this->hashPassword($password);
+ }
+
+ $publicKey = $t->getPublicKey();
+ $t->setPassword($this->encryptPassword($password, $publicKey));
+ $t->setPasswordHash($newPasswordHash);
+ $t->setPasswordInvalid(false);
+ $this->updateToken($t);
}
-
- $publicKey = $t->getPublicKey();
- $t->setPassword($this->encryptPassword($password, $publicKey));
- $t->setPasswordHash($newPasswordHash);
- $t->setPasswordInvalid(false);
- $this->updateToken($t);
}
- }
- // If password hashes are different we update them all to be equal so
- // that the next execution only needs to verify once
- if (count($hashNeedsUpdate) > 1) {
- $newPasswordHash = $this->hashPassword($password);
- $this->mapper->updateHashesForUser($uid, $newPasswordHash);
- }
+ // If password hashes are different we update them all to be equal so
+ // that the next execution only needs to verify once
+ if (count($hashNeedsUpdate) > 1) {
+ $newPasswordHash = $this->hashPassword($password);
+ $this->mapper->updateHashesForUser($uid, $newPasswordHash);
+ }
+ }, $this->db);
}
private function logOpensslError() {
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 8ca5cf53d16..921c50fd958 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -98,6 +98,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
public function mkdir($path) {
$path = $this->normalizePath($path);
if ($this->file_exists($path)) {
+ $this->logger->warning("Tried to create an object store folder that already exists: $path");
return false;
}
@@ -121,10 +122,12 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if ($parentType === false) {
if (!$this->mkdir($parent)) {
// something went wrong
+ $this->logger->warning("Parent folder ($parent) doesn't exist and couldn't be created");
return false;
}
} elseif ($parentType === 'file') {
// parent is a file
+ $this->logger->warning("Parent ($parent) is a file");
return false;
}
// finally create the new dir
diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php
index ebc8886f12d..76eee2bc962 100644
--- a/lib/private/Files/ObjectStore/S3.php
+++ b/lib/private/Files/ObjectStore/S3.php
@@ -69,13 +69,24 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload {
}
public function getMultipartUploads(string $urn, string $uploadId): array {
- $parts = $this->getConnection()->listParts([
- 'Bucket' => $this->bucket,
- 'Key' => $urn,
- 'UploadId' => $uploadId,
- 'MaxParts' => 10000
- ]);
- return $parts->get('Parts') ?? [];
+ $parts = [];
+ $isTruncated = true;
+ $partNumberMarker = 0;
+
+ while ($isTruncated) {
+ $result = $this->getConnection()->listParts([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn,
+ 'UploadId' => $uploadId,
+ 'MaxParts' => 1000,
+ 'PartNumberMarker' => $partNumberMarker
+ ]);
+ $parts = array_merge($parts, $result->get('Parts') ?? []);
+ $isTruncated = $result->get('IsTruncated');
+ $partNumberMarker = $result->get('NextPartNumberMarker');
+ }
+
+ return $parts;
}
public function completeMultipartUpload(string $urn, string $uploadId, array $result): int {
diff --git a/lib/private/Files/Type/Loader.php b/lib/private/Files/Type/Loader.php
index bf5af36ec6e..32013bc3786 100644
--- a/lib/private/Files/Type/Loader.php
+++ b/lib/private/Files/Type/Loader.php
@@ -24,6 +24,9 @@
*/
namespace OC\Files\Type;
+use OC\DB\Exceptions\DbalException;
+use OCP\AppFramework\Db\TTransactional;
+use OCP\DB\Exception as DBException;
use OCP\Files\IMimeTypeLoader;
use OCP\IDBConnection;
@@ -33,6 +36,8 @@ use OCP\IDBConnection;
* @package OC\Files\Type
*/
class Loader implements IMimeTypeLoader {
+ use TTransactional;
+
/** @var IDBConnection */
private $dbConnection;
@@ -108,31 +113,49 @@ class Loader implements IMimeTypeLoader {
* Store a mimetype in the DB
*
* @param string $mimetype
- * @param int inserted ID
+ * @return int inserted ID
*/
protected function store($mimetype) {
- $this->dbConnection->insertIfNotExist('*PREFIX*mimetypes', [
- 'mimetype' => $mimetype
- ]);
-
- $fetch = $this->dbConnection->getQueryBuilder();
- $fetch->select('id')
- ->from('mimetypes')
- ->where(
- $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype)
- ));
-
- $result = $fetch->execute();
- $row = $result->fetch();
- $result->closeCursor();
+ $row = $this->atomic(function () use ($mimetype) {
+ try {
+ $insert = $this->dbConnection->getQueryBuilder();
+ $insert->insert('mimetypes')
+ ->values([
+ 'mimetype' => $insert->createNamedParameter($mimetype)
+ ])
+ ->executeStatement();
+ return [
+ 'mimetype' => $mimetype,
+ 'id' => $insert->getLastInsertId(),
+ ];
+ } catch (DbalException $e) {
+ if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ throw $e;
+ }
+ $qb = $this->dbConnection->getQueryBuilder();
+ $row = $qb->select('id')
+ ->from('mimetypes')
+ ->where($qb->expr()->eq('mimetype', $qb->createNamedParameter($mimetype)))
+ ->executeQuery()
+ ->fetchOne();
+ if ($row) {
+ return [
+ 'mimetype' => $mimetype,
+ 'id' => $row['id'],
+ ];
+ }
+ throw new \Exception("Database threw an unique constraint on inserting a new mimetype, but couldn't return the ID for this very mimetype");
+ }
+ }, $this->dbConnection);
if (!$row) {
throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it");
}
+ $mimetypeId = (int) $row['id'];
- $this->mimetypes[$row['id']] = $mimetype;
- $this->mimetypeIds[$mimetype] = $row['id'];
- return $row['id'];
+ $this->mimetypes[$mimetypeId] = $mimetype;
+ $this->mimetypeIds[$mimetype] = $mimetypeId;
+ return $mimetypeId;
}
/**
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index 47f2952c6e6..ed9474fafb2 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -137,6 +137,8 @@ class Generator {
}
$previewFolder = $this->getPreviewFolder($file);
+ // List every existing preview first instead of trying to find them one by one
+ $previewFiles = $previewFolder->getDirectoryListing();
$previewVersion = '';
if ($file instanceof IVersionedPreviewFile) {
@@ -150,7 +152,7 @@ class Generator {
&& preg_match(Imaginary::supportedMimeTypes(), $mimeType)
&& $this->config->getSystemValueString('preview_imaginary_url', 'invalid') !== 'invalid') {
$crop = $specifications[0]['crop'] ?? false;
- $preview = $this->getSmallImagePreview($previewFolder, $file, $mimeType, $previewVersion, $crop);
+ $preview = $this->getSmallImagePreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion, $crop);
if ($preview->getSize() === 0) {
$preview->delete();
@@ -161,7 +163,7 @@ class Generator {
}
// Get the max preview and infer the max preview sizes from that
- $maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion);
+ $maxPreview = $this->getMaxPreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion);
$maxPreviewImage = null; // only load the image when we need it
if ($maxPreview->getSize() === 0) {
$maxPreview->delete();
@@ -197,7 +199,7 @@ class Generator {
// Try to get a cached preview. Else generate (and store) one
try {
try {
- $preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
+ $preview = $this->getCachedPreview($previewFiles, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
} catch (NotFoundException $e) {
if (!$this->previewManager->isMimeSupported($mimeType)) {
throw new NotFoundException();
@@ -208,6 +210,8 @@ class Generator {
}
$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
+ // New file, augment our array
+ $previewFiles[] = $preview;
}
} catch (\InvalidArgumentException $e) {
throw new NotFoundException("", 0, $e);
@@ -233,75 +237,19 @@ class Generator {
* Generate a small image straight away without generating a max preview first
* Preview generated is 256x256
*
+ * @param ISimpleFile[] $previewFiles
+ *
* @throws NotFoundException
*/
- private function getSmallImagePreview(ISimpleFolder $previewFolder, File $file, string $mimeType, string $prefix, bool $crop): ISimpleFile {
- $nodes = $previewFolder->getDirectoryListing();
-
- foreach ($nodes as $node) {
- $name = $node->getName();
- if (($prefix === '' || str_starts_with($name, $prefix))) {
- // Prefix match
- if (str_starts_with($name, $prefix . '256-256-crop') && $crop) {
- // Cropped image
- return $node;
- }
-
- if (str_starts_with($name, $prefix . '256-256.') && !$crop) {
- // Uncropped image
- return $node;
- }
- }
- }
-
- $previewProviders = $this->previewManager->getProviders();
- foreach ($previewProviders as $supportedMimeType => $providers) {
- // Filter out providers that does not support this mime
- if (!preg_match($supportedMimeType, $mimeType)) {
- continue;
- }
-
- foreach ($providers as $providerClosure) {
- $provider = $this->helper->getProvider($providerClosure);
- if (!($provider instanceof IProviderV2)) {
- continue;
- }
-
- if (!$provider->isAvailable($file)) {
- continue;
- }
-
- $preview = $this->helper->getThumbnail($provider, $file, 256, 256, $crop);
-
- if (!($preview instanceof IImage)) {
- continue;
- }
-
- // Try to get the extension.
- try {
- $ext = $this->getExtention($preview->dataMimeType());
- } catch (\InvalidArgumentException $e) {
- // Just continue to the next iteration if this preview doesn't have a valid mimetype
- continue;
- }
-
- $path = $this->generatePath(256, 256, $crop, $preview->dataMimeType(), $prefix);
- try {
- $file = $previewFolder->newFile($path);
- if ($preview instanceof IStreamImage) {
- $file->putContent($preview->resource());
- } else {
- $file->putContent($preview->data());
- }
- } catch (NotPermittedException $e) {
- throw new NotFoundException();
- }
+ private function getSmallImagePreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, string $mimeType, string $prefix, bool $crop): ISimpleFile {
+ $width = 256;
+ $height = 256;
- return $file;
- }
+ try {
+ return $this->getCachedPreview($previewFiles, $width, $height, $crop, $mimeType, $prefix);
+ } catch (NotFoundException $e) {
+ return $this->generateProviderPreview($previewFolder, $file, $width, $height, $crop, false, $mimeType, $prefix);
}
-
- throw new NotFoundException('No provider successfully handled the preview generation');
}
/**
@@ -398,22 +346,30 @@ class Generator {
/**
* @param ISimpleFolder $previewFolder
+ * @param ISimpleFile[] $previewFiles
* @param File $file
* @param string $mimeType
* @param string $prefix
* @return ISimpleFile
* @throws NotFoundException
*/
- private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeType, $prefix) {
- $nodes = $previewFolder->getDirectoryListing();
-
- foreach ($nodes as $node) {
+ private function getMaxPreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, $mimeType, $prefix) {
+ // We don't know the max preview size, so we can't use getCachedPreview.
+ // It might have been generated with a higher resolution than the current value.
+ foreach ($previewFiles as $node) {
$name = $node->getName();
if (($prefix === '' || strpos($name, $prefix) === 0) && strpos($name, 'max')) {
return $node;
}
}
+ $maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
+ $maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
+
+ return $this->generateProviderPreview($previewFolder, $file, $maxWidth, $maxHeight, false, true, $mimeType, $prefix);
+ }
+
+ private function generateProviderPreview(ISimpleFolder $previewFolder, File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, string $prefix) {
$previewProviders = $this->previewManager->getProviders();
foreach ($previewProviders as $supportedMimeType => $providers) {
// Filter out providers that does not support this mime
@@ -431,13 +387,10 @@ class Generator {
continue;
}
- $maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
- $maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
-
$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
try {
- $preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight);
+ $preview = $this->helper->getThumbnail($provider, $file, $width, $height);
} finally {
self::unguardWithSemaphore($sem);
}
@@ -446,15 +399,7 @@ class Generator {
continue;
}
- // Try to get the extention.
- try {
- $ext = $this->getExtention($preview->dataMimeType());
- } catch (\InvalidArgumentException $e) {
- // Just continue to the next iteration if this preview doesn't have a valid mimetype
- continue;
- }
-
- $path = $prefix . (string)$preview->width() . '-' . (string)$preview->height() . '-max.' . $ext;
+ $path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix);
try {
$file = $previewFolder->newFile($path);
if ($preview instanceof IStreamImage) {
@@ -470,7 +415,7 @@ class Generator {
}
}
- throw new NotFoundException();
+ throw new NotFoundException('No provider successfully handled the preview generation');
}
/**
@@ -487,15 +432,19 @@ class Generator {
* @param int $width
* @param int $height
* @param bool $crop
+ * @param bool $max
* @param string $mimeType
* @param string $prefix
* @return string
*/
- private function generatePath($width, $height, $crop, $mimeType, $prefix) {
+ private function generatePath($width, $height, $crop, $max, $mimeType, $prefix) {
$path = $prefix . (string)$width . '-' . (string)$height;
if ($crop) {
$path .= '-crop';
}
+ if ($max) {
+ $path .= '-max';
+ }
$ext = $this->getExtention($mimeType);
$path .= '.' . $ext;
@@ -637,7 +586,8 @@ class Generator {
self::unguardWithSemaphore($sem);
}
- $path = $this->generatePath($width, $height, $crop, $preview->dataMimeType(), $prefix);
+
+ $path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix);
try {
$file = $previewFolder->newFile($path);
$file->putContent($preview->data());
@@ -649,7 +599,7 @@ class Generator {
}
/**
- * @param ISimpleFolder $previewFolder
+ * @param ISimpleFile[] $files Array of FileInfo, as the result of getDirectoryListing()
* @param int $width
* @param int $height
* @param bool $crop
@@ -659,10 +609,14 @@ class Generator {
*
* @throws NotFoundException
*/
- private function getCachedPreview(ISimpleFolder $previewFolder, $width, $height, $crop, $mimeType, $prefix) {
- $path = $this->generatePath($width, $height, $crop, $mimeType, $prefix);
-
- return $previewFolder->getFile($path);
+ private function getCachedPreview($files, $width, $height, $crop, $mimeType, $prefix) {
+ $path = $this->generatePath($width, $height, $crop, false, $mimeType, $prefix);
+ foreach ($files as $file) {
+ if ($file->getName() === $path) {
+ return $file;
+ }
+ }
+ throw new NotFoundException();
}
/**
diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php
index 87dd5ed6014..cae139018f8 100644
--- a/lib/private/Session/Internal.php
+++ b/lib/private/Session/Internal.php
@@ -107,6 +107,7 @@ class Internal extends Session {
$this->reopen();
$this->invoke('session_unset');
$this->regenerateId();
+ $this->invoke('session_write_close');
$this->startSession(true);
$_SESSION = [];
}
diff --git a/lib/public/AppFramework/Db/IMapperException.php b/lib/public/AppFramework/Db/IMapperException.php
index a4af3cfa925..5381579d2a3 100644
--- a/lib/public/AppFramework/Db/IMapperException.php
+++ b/lib/public/AppFramework/Db/IMapperException.php
@@ -29,5 +29,5 @@ namespace OCP\AppFramework\Db;
/**
* @since 16.0.0
*/
-interface IMapperException {
+interface IMapperException extends \Throwable {
}
diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php
index 1f6f43dce1e..37fc3935139 100644
--- a/tests/lib/Preview/GeneratorTest.php
+++ b/tests/lib/Preview/GeneratorTest.php
@@ -105,15 +105,12 @@ class GeneratorTest extends \Test\TestCase {
$maxPreview->method('getMimeType')
->willReturn('image/png');
- $previewFolder->method('getDirectoryListing')
- ->willReturn([$maxPreview]);
-
$previewFile = $this->createMock(ISimpleFile::class);
$previewFile->method('getSize')->willReturn(1000);
+ $previewFile->method('getName')->willReturn('256-256.png');
- $previewFolder->method('getFile')
- ->with($this->equalTo('256-256.png'))
- ->willReturn($previewFile);
+ $previewFolder->method('getDirectoryListing')
+ ->willReturn([$maxPreview, $previewFile]);
$this->legacyEventDispatcher->expects($this->once())
->method('dispatch')
@@ -344,14 +341,12 @@ class GeneratorTest extends \Test\TestCase {
$maxPreview->method('getMimeType')
->willReturn('image/png');
- $previewFolder->method('getDirectoryListing')
- ->willReturn([$maxPreview]);
-
$preview = $this->createMock(ISimpleFile::class);
$preview->method('getSize')->willReturn(1000);
- $previewFolder->method('getFile')
- ->with($this->equalTo('1024-512-crop.png'))
- ->willReturn($preview);
+ $preview->method('getName')->willReturn('1024-512-crop.png');
+
+ $previewFolder->method('getDirectoryListing')
+ ->willReturn([$maxPreview, $preview]);
$this->previewManager->expects($this->never())
->method('isMimeSupported');