Skip to content

Commit

Permalink
Merge pull request #40618 from nextcloud/feat/39162/advanced_search
Browse files Browse the repository at this point in the history
Advanced search: backend allows multiples terms to search
  • Loading branch information
Altahrim authored Nov 10, 2023
2 parents a66dbcd + ddb8b68 commit fa761b5
Show file tree
Hide file tree
Showing 37 changed files with 1,423 additions and 300 deletions.
73 changes: 40 additions & 33 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -1855,8 +1841,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();

Expand Down Expand Up @@ -2074,11 +2066,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;

Expand Down Expand Up @@ -2160,6 +2153,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 = [];
Expand Down Expand Up @@ -3187,7 +3194,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();
Expand Down
54 changes: 42 additions & 12 deletions apps/dav/lib/CardDAV/CardDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
use OC\Search\Filter\DateTimeFilter;
use PDO;
use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Backend\SyncSupport;
Expand Down Expand Up @@ -1109,7 +1110,15 @@ public function searchPrincipalUri(string $principalUri,
* @param string $pattern
* @param array $searchProperties
* @param array $options
* @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options
* @psalm-param array{
* types?: bool,
* escape_like_param?: bool,
* limit?: int,
* offset?: int,
* wildcard?: bool,
* since?: DateTimeFilter|null,
* until?: DateTimeFilter|null,
* } $options
* @return array
*/
private function searchByAddressBookIds(array $addressBookIds,
Expand All @@ -1130,32 +1139,31 @@ private function searchByAddressBookIds(array $addressBookIds,
return [];
}

$propertyOr = $query2->expr()->orX();
foreach ($searchProperties as $property) {
if ($escapePattern) {
if ($escapePattern) {
$searchProperties = array_filter($searchProperties, function ($property) use ($pattern) {
if ($property === 'EMAIL' && str_contains($pattern, ' ')) {
// There can be no spaces in emails
continue;
return false;
}

if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
// There can be no chars in cloud ids which are not valid for user ids plus :/
// worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
continue;
return false;
}
}

$propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
return true;
});
}

if ($propertyOr->count() === 0) {
if (empty($searchProperties)) {
return [];
}

$query2->selectDistinct('cp.cardid')
->from($this->dbCardsPropertiesTable, 'cp')
->andWhere($addressBookOr)
->andWhere($propertyOr);
->andWhere($query2->expr()->in('cp.name', $query2->createNamedParameter($searchProperties, IQueryBuilder::PARAM_STR_ARRAY)));

// No need for like when the pattern is empty
if ('' !== $pattern) {
Expand All @@ -1167,14 +1175,36 @@ private function searchByAddressBookIds(array $addressBookIds,
$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
}
}

if (isset($options['limit'])) {
$query2->setMaxResults($options['limit']);
}
if (isset($options['offset'])) {
$query2->setFirstResult($options['offset']);
}

if (isset($options['since']) || isset($options['until'])) {
$query2->join('cp', $this->dbCardsPropertiesTable, 'cp_bday', 'cp.cardid = cp_bday.cardid');
$query2->andWhere($query2->expr()->eq('cp_bday.name', $query2->createNamedParameter('BDAY')));
/**
* FIXME Find a way to match only 4 last digits
* BDAY can be --1018 without year or 20001019 with it
* $bDayOr = $query2->expr()->orX();
* if ($options['since'] instanceof DateTimeFilter) {
* $bDayOr->add(
* $query2->expr()->gte('SUBSTR(cp_bday.value, -4)',
* $query2->createNamedParameter($options['since']->get()->format('md')))
* );
* }
* if ($options['until'] instanceof DateTimeFilter) {
* $bDayOr->add(
* $query2->expr()->lte('SUBSTR(cp_bday.value, -4)',
* $query2->createNamedParameter($options['until']->get()->format('md')))
* );
* }
* $query2->andWhere($bDayOr);
*/
}

$result = $query2->execute();
$matches = $result->fetchAll();
$result->closeCursor();
Expand Down Expand Up @@ -1410,7 +1440,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();
Expand Down
Loading

0 comments on commit fa761b5

Please sign in to comment.