diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 5e355611c11d3..3eea0f1d5c894 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -105,6 +105,8 @@ public function getDisplayName() { * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results * @return array an array of contacts which are arrays of key-value-pairs * example result: * [ diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 24b91f7cec365..ba600bf1c2dcd 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -907,31 +907,49 @@ public function updateShares(IShareable $shareable, $add, $remove) { * @param array $searchProperties defines the properties within the query pattern should match * @param array $options = array() to define the search behavior * - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results * @return array an array of contacts which are arrays of key-value-pairs */ public function search($addressBookId, $pattern, $searchProperties, $options = array()) { - $query = $this->db->getQueryBuilder(); $query2 = $this->db->getQueryBuilder(); - $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp'); - $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId))); + $query2->selectDistinct('cp.cardid') + ->from($this->dbCardsPropertiesTable, 'cp') + ->andWhere($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId))); $or = $query2->expr()->orX(); foreach ($searchProperties as $property) { - $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property))); + $or->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property))); } $query2->andWhere($or); // No need for like when the pattern is empty if ('' !== $pattern) { if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) { - $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern))); + $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern))); } else { - $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); + $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); } } - $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c') - ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL()))); + if (isset($options['limit'])) { + $query2->setMaxResults($options['limit']); + } + if (isset($options['offset'])) { + $query2->setFirstResult($options['offset']); + } + + $result = $query2->execute(); + $matches = $result->fetchAll(); + $result->closeCursor(); + $matches = array_map(function ($match) { + return (int) $match['cardid']; + }, $matches); + + $query = $this->db->getQueryBuilder(); + $query->select('c.carddata', 'c.uri') + ->from($this->dbCardsTable, 'c') + ->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY))); $result = $query->execute(); $cards = $result->fetchAll(); diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 251ce88457b08..98853920b3bba 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -761,6 +761,8 @@ public function dataTestSearch() { ['Do', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], 'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], 'case insensitive' => ['john', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], + 'limit' => ['john', ['FN'], ['limit' => 1], [['uri0', 'John Doe']]], + 'limit and offset' => ['john', ['FN'], ['limit' => 1, 'offset' => 1], [['uri1', 'John M. Doe']]], 'find "_" escaped' => ['_', ['CLOUD'], [], [['uri2', 'find without options']]], 'find not empty ClOUD' => ['%_%', ['CLOUD'], ['escape_like_param'=>false], [['uri0', 'John Doe'], ['uri2', 'find without options']]], ]; diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index f4af4737c1abe..d5ee407b82fe7 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -81,8 +81,7 @@ public function search($search, $limit, $offset, ISearchResult $searchResult) { $emailType = new SearchResultType('emails'); // Search in contacts - //@todo Pagination missing - $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']); + $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN'], ['limit' => $limit, 'offset' => $offset]); $lowerSearch = strtolower($search); foreach ($addressBookContacts as $contact) { if (isset($contact['EMAIL'])) { diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index 5173e889b9ccb..5cbecf5385bec 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -68,8 +68,7 @@ public function search($search, $limit, $offset, ISearchResult $searchResult) { $resultType = new SearchResultType('remotes'); // Search in contacts - //@todo Pagination missing - $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); + $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], ['limit' => $limit, 'offset' => $offset]); foreach ($addressBookContacts as $contact) { if (isset($contact['isLocalSystemBook'])) { continue; diff --git a/lib/private/ContactsManager.php b/lib/private/ContactsManager.php index ba709baefe576..427f2f6772a19 100644 --- a/lib/private/ContactsManager.php +++ b/lib/private/ContactsManager.php @@ -39,6 +39,8 @@ class ContactsManager implements \OCP\Contacts\IManager { * @param array $searchProperties defines the properties within the query pattern should match * @param array $options = array() to define the search behavior * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results * @return array an array of contacts which are arrays of key-value-pairs */ public function search($pattern, $searchProperties = array(), $options = array()) { diff --git a/lib/public/Contacts/IManager.php b/lib/public/Contacts/IManager.php index 9dd19c4cbf413..fe462627fc0e7 100644 --- a/lib/public/Contacts/IManager.php +++ b/lib/public/Contacts/IManager.php @@ -95,6 +95,8 @@ interface IManager { * @param array $searchProperties defines the properties within the query pattern should match * @param array $options = array() to define the search behavior * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results * @return array an array of contacts which are arrays of key-value-pairs * @since 6.0.0 */ diff --git a/lib/public/IAddressBook.php b/lib/public/IAddressBook.php index 4f652706d57be..c39fb3fd1a99c 100644 --- a/lib/public/IAddressBook.php +++ b/lib/public/IAddressBook.php @@ -70,6 +70,8 @@ public function getDisplayName(); * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results * @return array an array of contacts which are arrays of key-value-pairs * example result: * [