mirror of
https://github.com/nextcloud/server.git
synced 2026-04-22 06:37:56 -04:00
feat(caldav): Allow advanced search for events/tasks
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
This commit is contained in:
parent
a75a93af8e
commit
3545a1c613
6 changed files with 96 additions and 135 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1855,8 +1841,14 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
*
|
||||
* @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();
|
||||
|
||||
|
|
@ -2074,11 +2066,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
* @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;
|
||||
|
||||
|
|
@ -2160,6 +2153,20 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
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 = [];
|
||||
|
|
@ -3187,7 +3194,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
|
|||
$maxId = (int) $result->fetchOne();
|
||||
$result->closeCursor();
|
||||
if (!$maxId || $maxId < $keep) {
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = $this->db->getQueryBuilder();
|
||||
|
|
|
|||
|
|
@ -41,18 +41,6 @@ use Sabre\VObject\Reader;
|
|||
|
||||
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 @@ class ContactsSearchProvider implements IProvider {
|
|||
|
||||
$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 @@ class ContactsSearchProvider implements IProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class ContactsSearchProvider implements IProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class ContactsSearchProvider implements IProvider {
|
|||
|
||||
/**
|
||||
* @param VCard $vCard
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSubline(VCard $vCard): string {
|
||||
$emailAddresses = $vCard->select('EMAIL');
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ use Sabre\VObject\Property;
|
|||
* @package OCA\DAV\Search
|
||||
*/
|
||||
class EventsSearchProvider extends ACalendarSearchProvider {
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
|
|
@ -95,8 +94,10 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
/**
|
||||
* @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 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
|
||||
$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 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
. $calendarObjectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Component $eventComponent
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSubline(Component $eventComponent): string {
|
||||
$dtStart = $eventComponent->DTSTART;
|
||||
$dtEnd = $this->getDTEndForEvent($eventComponent);
|
||||
|
|
@ -207,10 +200,6 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
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 @@ class EventsSearchProvider extends ACalendarSearchProvider {
|
|||
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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ use Sabre\VObject\Component;
|
|||
* @package OCA\DAV\Search
|
||||
*/
|
||||
class TasksSearchProvider extends ACalendarSearchProvider {
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
|
|
@ -88,8 +87,10 @@ class TasksSearchProvider extends ACalendarSearchProvider {
|
|||
/**
|
||||
* @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 @@ class TasksSearchProvider extends ACalendarSearchProvider {
|
|||
|
||||
$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 @@ class TasksSearchProvider extends ACalendarSearchProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class TasksSearchProvider extends ACalendarSearchProvider {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Component $taskComponent
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSubline(Component $taskComponent): string {
|
||||
if ($taskComponent->COMPLETED) {
|
||||
$completedDateTime = new \DateTime($taskComponent->COMPLETED->getDateTime()->format(\DateTimeInterface::ATOM));
|
||||
|
|
|
|||
|
|
@ -328,10 +328,10 @@ class EventsSearchProviderTest extends TestCase {
|
|||
]);
|
||||
$this->backend->expects($this->once())
|
||||
->method('searchPrincipalUri')
|
||||
->with('principals/users/john.doe', 'search term', ['VEVENT'],
|
||||
->with('principals/users/john.doe', '', ['VEVENT'],
|
||||
['SUMMARY', 'LOCATION', 'DESCRIPTION', 'ATTENDEE', 'ORGANIZER', 'CATEGORIES'],
|
||||
['ATTENDEE' => ['CN'], 'ORGANIZER' => ['CN']],
|
||||
['limit' => 5, 'offset' => 20])
|
||||
['limit' => 5, 'offset' => 20, 'timerange' => ['start' => null, 'end' => null]])
|
||||
->willReturn([
|
||||
[
|
||||
'calendarid' => 99,
|
||||
|
|
|
|||
|
|
@ -213,10 +213,10 @@ class TasksSearchProviderTest extends TestCase {
|
|||
]);
|
||||
$this->backend->expects($this->once())
|
||||
->method('searchPrincipalUri')
|
||||
->with('principals/users/john.doe', 'search term', ['VTODO'],
|
||||
->with('principals/users/john.doe', '', ['VTODO'],
|
||||
['SUMMARY', 'DESCRIPTION', 'CATEGORIES'],
|
||||
[],
|
||||
['limit' => 5, 'offset' => 20])
|
||||
['limit' => 5, 'offset' => 20, 'since' => null, 'until' => null])
|
||||
->willReturn([
|
||||
[
|
||||
'calendarid' => 99,
|
||||
|
|
|
|||
Loading…
Reference in a new issue