diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 909a582805996..ac59111990abc 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -72,7 +72,6 @@ use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; -use OCP\IUser; use OCP\IUserManager; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -120,7 +119,6 @@ * @package OCA\DAV\CalDAV */ class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { - use TTransactional; public const CALENDAR_TYPE_CALENDAR = 0; @@ -317,132 +315,134 @@ public function getDeletedCalendars(int $deletedBefore): array { * @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(); - - // query for shared calendars - $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true); - $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal)); + $result->closeCursor(); - $principals[] = $principalUri; + // query for shared calendars + $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true); + $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal)); - $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); + $principals[] = $principalUri; - $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); } /** @@ -469,7 +469,7 @@ public function getUsersOwnCalendars($principalUri) { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -521,7 +521,7 @@ public function getPublicCalendars() { $row['displayname'] = $row['displayname'] . "($name)"; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -586,7 +586,7 @@ public function getPublicCalendar($uri) { $row['displayname'] = $row['displayname'] . ' ' . "($name)"; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -639,7 +639,7 @@ public function getCalendarByUri($principal, $uri) { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ @@ -687,7 +687,7 @@ public function getCalendarById(int $calendarId): ?array { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ @@ -779,7 +779,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) { throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); } - $values['components'] = implode(',',$properties[$sccs]->getValue()); + $values['components'] = implode(',', $properties[$sccs]->getValue()); } elseif (isset($properties['components'])) { // Allow to provide components internally without having // to create a SupportedCalendarComponentSet object @@ -797,7 +797,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { } } - [$calendarId, $calendarData] = $this->atomic(function() use ($values) { + [$calendarId, $calendarData] = $this->atomic(function () use ($values) { $query = $this->db->getQueryBuilder(); $query->insert('calendars'); foreach ($values as $column => $value) { @@ -832,39 +832,41 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { * @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); } /** @@ -874,81 +876,85 @@ public function updateCalendar($calendarId, PropPatch $propPatch) { * @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); } /** @@ -1208,70 +1214,72 @@ public function getMultipleCalendarObjects($calendarId, array $uris, $calendarTy 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(); + $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($qb->func()->count('*')) - ->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(); - $count = (int) $result->fetchOne(); - $result->closeCursor(); - if ($count !== 0) { - throw new BadRequest('Deleted calendar object with uid already exists in this calendar collection.'); - } + 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($qb->func()->count('*')) + ->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(); + $count = (int) $result->fetchOne(); + $result->closeCursor(); + if ($count !== 0) { + throw new BadRequest('Deleted calendar object with uid already exists in this calendar collection.'); + } - $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)); + } + }, $this->db); return '"' . $extraData['etag'] . '"'; } @@ -1297,8 +1305,10 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData, $ca */ public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) { $extraData = $this->getDenormalizedData($calendarData); - $query = $this->db->getQueryBuilder(); - $query->update('calendarobjects') + + $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'])) @@ -1308,27 +1318,28 @@ public function updateCalendarObject($calendarId, $objectUri, $calendarData, $ca ->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(); + ->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); + $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType); + $this->addChange($calendarId, $objectUri, 2, $calendarType); - $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType); - if (is_array($objectRow)) { - if ($calendarType === self::CALENDAR_TYPE_CALENDAR) { - $calendarRow = $this->getCalendarById($calendarId); - $shares = $this->getShares($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 CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow)); - } else { - $subscriptionRow = $this->getSubscriptionById($calendarId); + $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow)); + } else { + $subscriptionRow = $this->getSubscriptionById($calendarId); - $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow)); + $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow)); + } } - } + }, $this->db); return '"' . $extraData['etag'] . '"'; } @@ -1346,43 +1357,45 @@ public function updateCalendarObject($calendarId, $objectUri, $calendarData, $ca * @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 +1428,79 @@ public function setClassification($calendarObjectId, $classification) { * @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 +1509,52 @@ public function deleteCalendarObject($calendarId, $objectUri, $calendarType = se * @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 +1703,120 @@ public function calendarQuery($calendarId, array $filters, $calendarType = self: * @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 = $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); - } + } + 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); } /** @@ -1860,7 +1879,7 @@ public function search(array $calendarInfo, $pattern, array $searchProperties, } } - if(isset($options['uid'])) { + if (isset($options['uid'])) { $outerQuery->andWhere($outerQuery->expr()->eq('uid', $outerQuery->createNamedParameter($options['uid']))); } @@ -2022,110 +2041,112 @@ public function searchPrincipalUri(string $principalUri, 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 +2275,86 @@ public function getCalendarObjectById(string $principalUri, int $id): ?array { * @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); } /** @@ -2435,7 +2458,7 @@ public function createSubscription($principalUri, $uri, array $properties) { } } - [$subscriptionId, $subscriptionRow] = $this->atomic(function() use ($values) { + [$subscriptionId, $subscriptionRow] = $this->atomic(function () use ($values) { $valuesToInsert = []; $query = $this->db->getQueryBuilder(); foreach (array_keys($values) as $name) { @@ -2473,35 +2496,37 @@ public function createSubscription($principalUri, $uri, array $properties) { * @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 +2536,34 @@ public function updateSubscription($subscriptionId, PropPatch $propPatch) { * @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); } /** @@ -2805,16 +2832,18 @@ private function readBlob($cardData) { * @param list $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 +2859,34 @@ public function getShares(int $resourceId): array { * @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); } /** @@ -2905,16 +2936,16 @@ public function updateProperties($calendarId, $objectUri, $calendarData, $calend $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'), - ] - ); + ->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'), + ] + ); $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO']; foreach ($vCalendar->getComponents() as $component) { @@ -2963,57 +2994,61 @@ public function updateProperties($calendarId, $objectUri, $calendarData, $calend * 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); } /**