From 434b761143679759dd0643999f47ba475af129ae Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Tue, 26 Sep 2023 11:07:49 +0200 Subject: [PATCH] feat(caldav): Allow advanced search for events/tasks Signed-off-by: Benjamin Gaussorgues --- apps/dav/lib/CalDAV/CalDavBackend.php | 73 ++++++++++--------- .../dav/lib/Search/ContactsSearchProvider.php | 64 +++++----------- apps/dav/lib/Search/EventsSearchProvider.php | 60 ++++++--------- apps/dav/lib/Search/TasksSearchProvider.php | 26 +++---- 4 files changed, 92 insertions(+), 131 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 5f2fe7e5dcead..69667f349c606 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -208,36 +208,22 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ protected array $userDisplayNames; - private IDBConnection $db; private Backend $calendarSharingBackend; - private Principal $principalBackend; - private IUserManager $userManager; - private ISecureRandom $random; - private LoggerInterface $logger; - private IEventDispatcher $dispatcher; - private IConfig $config; - private bool $legacyEndpoint; private string $dbObjectPropertiesTable = 'calendarobjects_props'; private array $cachedObjects = []; - public function __construct(IDBConnection $db, - Principal $principalBackend, - IUserManager $userManager, - IGroupManager $groupManager, - ISecureRandom $random, - LoggerInterface $logger, - IEventDispatcher $dispatcher, - IConfig $config, - bool $legacyEndpoint = false) { - $this->db = $db; - $this->principalBackend = $principalBackend; - $this->userManager = $userManager; + public function __construct( + private IDBConnection $db, + private Principal $principalBackend, + private IUserManager $userManager, + IGroupManager $groupManager, + private ISecureRandom $random, + private LoggerInterface $logger, + private IEventDispatcher $dispatcher, + private IConfig $config, + private bool $legacyEndpoint = false, + ) { $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar'); - $this->random = $random; - $this->logger = $logger; - $this->dispatcher = $dispatcher; - $this->config = $config; - $this->legacyEndpoint = $legacyEndpoint; } /** @@ -1854,8 +1840,14 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of * * @return array */ - public function search(array $calendarInfo, $pattern, array $searchProperties, - array $options, $limit, $offset) { + public function search( + array $calendarInfo, + $pattern, + array $searchProperties, + array $options, + $limit, + $offset + ) { $outerQuery = $this->db->getQueryBuilder(); $innerQuery = $this->db->getQueryBuilder(); @@ -2069,11 +2061,12 @@ private function transformSearchProperty(Property $prop) { * @return array */ public function searchPrincipalUri(string $principalUri, - string $pattern, - array $componentTypes, - array $searchProperties, - array $searchParameters, - array $options = []): array { + string $pattern, + array $componentTypes, + array $searchProperties, + array $searchParameters, + array $options = [] + ): array { return $this->atomic(function () use ($principalUri, $pattern, $componentTypes, $searchProperties, $searchParameters, $options) { $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false; @@ -2155,6 +2148,20 @@ public function searchPrincipalUri(string $principalUri, if (isset($options['offset'])) { $calendarObjectIdQuery->setFirstResult($options['offset']); } + if (isset($options['timerange'])) { + if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) { + $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt( + 'lastoccurence', + $calendarObjectIdQuery->createNamedParameter($options['timerange']['start']->getTimeStamp()), + )); + } + if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) { + $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt( + 'firstoccurence', + $calendarObjectIdQuery->createNamedParameter($options['timerange']['end']->getTimeStamp()), + )); + } + } $result = $calendarObjectIdQuery->executeQuery(); $matches = $result->fetchAll(); @@ -3183,7 +3190,7 @@ public function pruneOutdatedSyncTokens(int $keep = 10_000): int { $maxId = (int) $result->fetchOne(); $result->closeCursor(); if (!$maxId || $maxId < $keep) { - return 0; + return 0; } $query = $this->db->getQueryBuilder(); diff --git a/apps/dav/lib/Search/ContactsSearchProvider.php b/apps/dav/lib/Search/ContactsSearchProvider.php index a7c2969016b47..6b0fea1b70a87 100644 --- a/apps/dav/lib/Search/ContactsSearchProvider.php +++ b/apps/dav/lib/Search/ContactsSearchProvider.php @@ -41,18 +41,6 @@ class ContactsSearchProvider implements IProvider { - /** @var IAppManager */ - private $appManager; - - /** @var IL10N */ - private $l10n; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var CardDavBackend */ - private $backend; - /** * @var string[] */ @@ -68,22 +56,12 @@ class ContactsSearchProvider implements IProvider { 'NOTE', ]; - /** - * ContactsSearchProvider constructor. - * - * @param IAppManager $appManager - * @param IL10N $l10n - * @param IURLGenerator $urlGenerator - * @param CardDavBackend $backend - */ - public function __construct(IAppManager $appManager, - IL10N $l10n, - IURLGenerator $urlGenerator, - CardDavBackend $backend) { - $this->appManager = $appManager; - $this->l10n = $l10n; - $this->urlGenerator = $urlGenerator; - $this->backend = $backend; + public function __construct( + private IAppManager $appManager, + private IL10N $l10n, + private IURLGenerator $urlGenerator, + private CardDavBackend $backend, + ) { } /** @@ -127,11 +105,13 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { $searchResults = $this->backend->searchPrincipalUri( $principalUri, - $query->getTerm(), + $query->getFilter('term')?->get() ?? '', self::$searchProperties, [ 'limit' => $query->getLimit(), 'offset' => $query->getCursor(), + 'since' => $query->getFilter('since'), + 'until' => $query->getFilter('until'), ] ); $formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):SearchResultEntry { @@ -158,15 +138,11 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); } - /** - * @param string $principalUri - * @param string $addressBookUri - * @param string $contactsUri - * @return string - */ - protected function getDavUrlForContact(string $principalUri, - string $addressBookUri, - string $contactsUri): string { + protected function getDavUrlForContact( + string $principalUri, + string $addressBookUri, + string $contactsUri, + ): string { [, $principalType, $principalId] = explode('/', $principalUri, 3); return $this->urlGenerator->getAbsoluteURL( @@ -178,13 +154,10 @@ protected function getDavUrlForContact(string $principalUri, ); } - /** - * @param string $addressBookUri - * @param string $contactUid - * @return string - */ - protected function getDeepLinkToContactsApp(string $addressBookUri, - string $contactUid): string { + protected function getDeepLinkToContactsApp( + string $addressBookUri, + string $contactUid, + ): string { return $this->urlGenerator->getAbsoluteURL( $this->urlGenerator->linkToRoute('contacts.contacts.direct', [ 'contact' => $contactUid . '~' . $addressBookUri @@ -194,7 +167,6 @@ protected function getDeepLinkToContactsApp(string $addressBookUri, /** * @param VCard $vCard - * @return string */ protected function generateSubline(VCard $vCard): string { $emailAddresses = $vCard->select('EMAIL'); diff --git a/apps/dav/lib/Search/EventsSearchProvider.php b/apps/dav/lib/Search/EventsSearchProvider.php index 07fc90397ed2d..22c87b1f854a9 100644 --- a/apps/dav/lib/Search/EventsSearchProvider.php +++ b/apps/dav/lib/Search/EventsSearchProvider.php @@ -42,7 +42,6 @@ * @package OCA\DAV\Search */ class EventsSearchProvider extends ACalendarSearchProvider { - /** * @var string[] */ @@ -95,8 +94,10 @@ public function getOrder(string $route, array $routeParameters): int { /** * @inheritDoc */ - public function search(IUser $user, - ISearchQuery $query): SearchResult { + public function search( + IUser $user, + ISearchQuery $query, + ): SearchResult { if (!$this->appManager->isEnabledForUser('calendar', $user)) { return SearchResult::complete($this->getName(), []); } @@ -107,13 +108,17 @@ public function search(IUser $user, $searchResults = $this->backend->searchPrincipalUri( $principalUri, - $query->getTerm(), + $query->getFilter('term')?->get() ?? '', [self::$componentType], self::$searchProperties, self::$searchParameters, [ 'limit' => $query->getLimit(), 'offset' => $query->getCursor(), + 'timerange' => [ + 'start' => $query->getFilter('since')?->get(), + 'end' => $query->getFilter('until')?->get(), + ], ] ); $formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById):SearchResultEntry { @@ -138,15 +143,11 @@ public function search(IUser $user, ); } - /** - * @param string $principalUri - * @param string $calendarUri - * @param string $calendarObjectUri - * @return string - */ - protected function getDeepLinkToCalendarApp(string $principalUri, - string $calendarUri, - string $calendarObjectUri): string { + protected function getDeepLinkToCalendarApp( + string $principalUri, + string $calendarUri, + string $calendarObjectUri, + ): string { $davUrl = $this->getDavUrlForCalendarObject($principalUri, $calendarUri, $calendarObjectUri); // This route will automatically figure out what recurrence-id to open return $this->urlGenerator->getAbsoluteURL( @@ -156,15 +157,11 @@ protected function getDeepLinkToCalendarApp(string $principalUri, ); } - /** - * @param string $principalUri - * @param string $calendarUri - * @param string $calendarObjectUri - * @return string - */ - protected function getDavUrlForCalendarObject(string $principalUri, - string $calendarUri, - string $calendarObjectUri): string { + protected function getDavUrlForCalendarObject( + string $principalUri, + string $calendarUri, + string $calendarObjectUri + ): string { [,, $principalId] = explode('/', $principalUri, 3); return $this->urlGenerator->linkTo('', 'remote.php') . '/dav/calendars/' @@ -173,10 +170,6 @@ protected function getDavUrlForCalendarObject(string $principalUri, . $calendarObjectUri; } - /** - * @param Component $eventComponent - * @return string - */ protected function generateSubline(Component $eventComponent): string { $dtStart = $eventComponent->DTSTART; $dtEnd = $this->getDTEndForEvent($eventComponent); @@ -207,10 +200,6 @@ protected function generateSubline(Component $eventComponent): string { return "$formattedStartDate $formattedStartTime - $formattedEndDate $formattedEndTime"; } - /** - * @param Component $eventComponent - * @return Property - */ protected function getDTEndForEvent(Component $eventComponent):Property { if (isset($eventComponent->DTEND)) { $end = $eventComponent->DTEND; @@ -233,13 +222,10 @@ protected function getDTEndForEvent(Component $eventComponent):Property { return $end; } - /** - * @param \DateTime $dtStart - * @param \DateTime $dtEnd - * @return bool - */ - protected function isDayEqual(\DateTime $dtStart, - \DateTime $dtEnd) { + protected function isDayEqual( + \DateTime $dtStart, + \DateTime $dtEnd, + ): bool { return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d'); } } diff --git a/apps/dav/lib/Search/TasksSearchProvider.php b/apps/dav/lib/Search/TasksSearchProvider.php index 763720ee4ae0a..91692ea1c2ab7 100644 --- a/apps/dav/lib/Search/TasksSearchProvider.php +++ b/apps/dav/lib/Search/TasksSearchProvider.php @@ -41,7 +41,6 @@ * @package OCA\DAV\Search */ class TasksSearchProvider extends ACalendarSearchProvider { - /** * @var string[] */ @@ -88,8 +87,10 @@ public function getOrder(string $route, array $routeParameters): int { /** * @inheritDoc */ - public function search(IUser $user, - ISearchQuery $query): SearchResult { + public function search( + IUser $user, + ISearchQuery $query, + ): SearchResult { if (!$this->appManager->isEnabledForUser('tasks', $user)) { return SearchResult::complete($this->getName(), []); } @@ -100,13 +101,15 @@ public function search(IUser $user, $searchResults = $this->backend->searchPrincipalUri( $principalUri, - $query->getTerm(), + $query->getFilter('term')?->get() ?? '', [self::$componentType], self::$searchProperties, self::$searchParameters, [ 'limit' => $query->getLimit(), 'offset' => $query->getCursor(), + 'since' => $query->getFilter('since'), + 'until' => $query->getFilter('until'), ] ); $formattedResults = \array_map(function (array $taskRow) use ($calendarsById, $subscriptionsById):SearchResultEntry { @@ -131,13 +134,10 @@ public function search(IUser $user, ); } - /** - * @param string $calendarUri - * @param string $taskUri - * @return string - */ - protected function getDeepLinkToTasksApp(string $calendarUri, - string $taskUri): string { + protected function getDeepLinkToTasksApp( + string $calendarUri, + string $taskUri, + ): string { return $this->urlGenerator->getAbsoluteURL( $this->urlGenerator->linkToRoute('tasks.page.index') . '#/calendars/' @@ -147,10 +147,6 @@ protected function getDeepLinkToTasksApp(string $calendarUri, ); } - /** - * @param Component $taskComponent - * @return string - */ protected function generateSubline(Component $taskComponent): string { if ($taskComponent->COMPLETED) { $completedDateTime = new \DateTime($taskComponent->COMPLETED->getDateTime()->format(\DateTimeInterface::ATOM));