Skip to content

Commit 5c8045f

Browse files
committed
fix(caldav): Expand recurring events for principal calendar search
Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
1 parent f1915e0 commit 5c8045f

4 files changed

Lines changed: 512 additions & 39 deletions

File tree

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2316,6 +2316,11 @@ public function searchPrincipalUri(string $principalUri,
23162316
$calendarOr = [];
23172317
$searchOr = [];
23182318

2319+
$start = null;
2320+
$end = null;
2321+
2322+
// Todo: The retries when $hasLimit && $hasTimeRange from https://github.com/nextcloud/server/pull/45222 should also be applied here to the calendarObjectIdQuery
2323+
23192324
// Fetch calendars and subscription
23202325
$calendars = $this->getCalendarsForUser($principalUri);
23212326
$subscriptions = $this->getSubscriptionsForUser($principalUri);
@@ -2394,19 +2399,21 @@ public function searchPrincipalUri(string $principalUri,
23942399
if (isset($options['offset'])) {
23952400
$calendarObjectIdQuery->setFirstResult($options['offset']);
23962401
}
2397-
if (isset($options['timerange'])) {
2398-
if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) {
2399-
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt(
2400-
'lastoccurence',
2401-
$calendarObjectIdQuery->createNamedParameter($options['timerange']['start']->getTimeStamp()),
2402-
));
2403-
}
2404-
if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) {
2405-
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt(
2406-
'firstoccurence',
2407-
$calendarObjectIdQuery->createNamedParameter($options['timerange']['end']->getTimeStamp()),
2408-
));
2409-
}
2402+
if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) {
2403+
/** @var DateTimeInterface $start */
2404+
$start = $options['timerange']['start'];
2405+
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt(
2406+
'lastoccurence',
2407+
$calendarObjectIdQuery->createNamedParameter($start->getTimestamp()),
2408+
));
2409+
}
2410+
if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) {
2411+
/** @var DateTimeInterface $end */
2412+
$end = $options['timerange']['end'];
2413+
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt(
2414+
'firstoccurence',
2415+
$calendarObjectIdQuery->createNamedParameter($end->getTimestamp()),
2416+
));
24102417
}
24112418

24122419
$result = $calendarObjectIdQuery->executeQuery();
@@ -2421,17 +2428,22 @@ public function searchPrincipalUri(string $principalUri,
24212428
->from('calendarobjects')
24222429
->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
24232430

2424-
$result = $query->executeQuery();
2425-
$calendarObjects = [];
2426-
while (($array = $result->fetchAssociative()) !== false) {
2427-
$array['calendarid'] = (int)$array['calendarid'];
2428-
$array['calendartype'] = (int)$array['calendartype'];
2429-
$array['calendardata'] = $this->readBlob($array['calendardata']);
2431+
$calendarObjects = $this->searchCalendarObjects($query, $start, $end);
24302432

2431-
$calendarObjects[] = $array;
2432-
}
2433-
$result->closeCursor();
2434-
return $calendarObjects;
2433+
return array_map(function ($event) use ($start, $end) {
2434+
$calendarData = Reader::read($event['calendardata']);
2435+
2436+
// Expand recurrences if an explicit time range is requested
2437+
if ($calendarData instanceof VCalendar && isset($start, $end)) {
2438+
$calendarData = $calendarData->expand($start, $end);
2439+
}
2440+
2441+
$event['calendardata'] = $calendarData->serialize();
2442+
$event['calendarid'] = (int)$event['calendarid'];
2443+
$event['calendartype'] = (int)$event['calendartype'];
2444+
2445+
return $event;
2446+
}, $calendarObjects);
24352447
}, $this->db);
24362448
}
24372449

apps/dav/lib/Search/EventsSearchProvider.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
namespace OCA\DAV\Search;
1010

11+
use DateTimeImmutable;
1112
use OCA\DAV\CalDAV\CalDavBackend;
1213
use OCP\IUser;
1314
use OCP\Search\IFilteringProvider;
@@ -100,6 +101,20 @@ public function search(
100101

101102
/** @var string|null $term */
102103
$term = $query->getFilter('term')?->get();
104+
105+
$since = $query->getFilter('since')?->get();
106+
$until = $query->getFilter('until')?->get();
107+
108+
if ($since !== null && $until === null) {
109+
$until = new DateTimeImmutable('now', new \DateTimeZone('Z'));
110+
}
111+
112+
/** @var array{start: DateTimeImmutable, end: DateTimeImmutable} $timeRange */
113+
$timeRange = [
114+
'start' => $since,
115+
'end' => $until,
116+
];
117+
103118
if ($term === null) {
104119
$searchResults = [];
105120
} else {
@@ -112,10 +127,7 @@ public function search(
112127
[
113128
'limit' => $query->getLimit(),
114129
'offset' => $query->getCursor(),
115-
'timerange' => [
116-
'start' => $query->getFilter('since')?->get(),
117-
'end' => $query->getFilter('until')?->get(),
118-
],
130+
'timerange' => $timeRange,
119131
]
120132
);
121133
}
@@ -132,10 +144,7 @@ public function search(
132144
[
133145
'limit' => $query->getLimit(),
134146
'offset' => $query->getCursor(),
135-
'timerange' => [
136-
'start' => $query->getFilter('since')?->get(),
137-
'end' => $query->getFilter('until')?->get(),
138-
],
147+
'timerange' => $timeRange,
139148
],
140149
);
141150

0 commit comments

Comments
 (0)