From 2c5ebac458f899af190d2ef7bf8702624e700049 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 18 Jan 2018 15:08:24 +0530 Subject: [PATCH] Allow to search for real pattern in contacts Added an option escape_like_param to allow wildcards Signed-off-by: Arne Hamann --- apps/dav/lib/CardDAV/AddressBookImpl.php | 8 +-- apps/dav/lib/CardDAV/CardDavBackend.php | 10 +++- .../tests/unit/CardDAV/CardDavBackendTest.php | 49 +++++++++++++++---- lib/private/ContactsManager.php | 3 +- lib/public/Contacts/IManager.php | 3 +- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index ae727b8544..b5dbbe5981 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -97,20 +97,22 @@ class AddressBookImpl implements IAddressBook { /** * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options Options to define the output format - * - types boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array + * @param array $options Options to define the output format and search behavior + * - '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 * @return array an array of contacts which are arrays of key-value-pairs * example result: * [ * ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'], * ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']] * ] + * @param array $options = array() 'escape_like_param' - to not escape wildcards _ and % - for future use. One should always have options! * @return array an array of contacts which are arrays of key-value-pairs * @since 5.0.0 */ public function search($pattern, $searchProperties, $options) { - $results = $this->backend->search($this->getKey(), $pattern, $searchProperties); + $results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options = $options); $withTypes = \array_key_exists('types', $options) && $options['types'] === true; diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index f30a12bba4..ee9a790bc9 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -903,9 +903,11 @@ class CardDavBackend implements BackendInterface, SyncSupport { * @param int $addressBookId * @param string $pattern which should match within the $searchProperties * @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 * @return array an array of contacts which are arrays of key-value-pairs */ - public function search($addressBookId, $pattern, $searchProperties) { + public function search($addressBookId, $pattern, $searchProperties, $options = array()) { $query = $this->db->getQueryBuilder(); $query2 = $this->db->getQueryBuilder(); @@ -919,7 +921,11 @@ class CardDavBackend implements BackendInterface, SyncSupport { // No need for like when the pattern is empty if ('' !== $pattern) { - $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); + if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) { + $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern))); + } else { + $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))); + } } $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c') diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 86c85a972e..531f50e96c 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -642,22 +642,27 @@ class CardDavBackendTest extends TestCase { * * @param string $pattern * @param array $properties + * @param array $options * @param array $expected */ - public function testSearch($pattern, $properties, $expected) { + public function testSearch($pattern, $properties, $options, $expected) { /** @var VCard $vCards */ $vCards = []; $vCards[0] = new VCard(); $vCards[0]->add(new Text($vCards[0], 'UID', 'uid')); $vCards[0]->add(new Text($vCards[0], 'FN', 'John Doe')); - $vCards[0]->add(new Text($vCards[0], 'CLOUD', 'john@owncloud.org')); + $vCards[0]->add(new Text($vCards[0], 'CLOUD', 'john@nextcloud.com')); $vCards[1] = new VCard(); $vCards[1]->add(new Text($vCards[1], 'UID', 'uid')); $vCards[1]->add(new Text($vCards[1], 'FN', 'John M. Doe')); + $vCards[2] = new VCard(); + $vCards[2]->add(new Text($vCards[2], 'UID', 'uid')); + $vCards[2]->add(new Text($vCards[2], 'FN', 'find without options')); + $vCards[2]->add(new Text($vCards[2], 'CLOUD', 'peter_pan@nextcloud.com')); $vCardIds = []; $query = $this->db->getQueryBuilder(); - for($i=0; $i<2; $i++) { + for($i=0; $i < 3; $i++) { $query->insert($this->dbCardsTable) ->values( [ @@ -690,7 +695,7 @@ class CardDavBackendTest extends TestCase { 'addressbookid' => $query->createNamedParameter(0), 'cardid' => $query->createNamedParameter($vCardIds[0]), 'name' => $query->createNamedParameter('CLOUD'), - 'value' => $query->createNamedParameter('John@owncloud.org'), + 'value' => $query->createNamedParameter('John@nextcloud.com'), 'preferred' => $query->createNamedParameter(0) ] ); @@ -706,8 +711,30 @@ class CardDavBackendTest extends TestCase { ] ); $query->execute(); + $query->insert($this->dbCardsPropertiesTable) + ->values( + [ + 'addressbookid' => $query->createNamedParameter(0), + 'cardid' => $query->createNamedParameter($vCardIds[2]), + 'name' => $query->createNamedParameter('FN'), + 'value' => $query->createNamedParameter('find without options'), + 'preferred' => $query->createNamedParameter(0) + ] + ); + $query->execute(); + $query->insert($this->dbCardsPropertiesTable) + ->values( + [ + 'addressbookid' => $query->createNamedParameter(0), + 'cardid' => $query->createNamedParameter($vCardIds[2]), + 'name' => $query->createNamedParameter('CLOUD'), + 'value' => $query->createNamedParameter('peter_pan@nextcloud.com'), + 'preferred' => $query->createNamedParameter(0) + ] + ); + $query->execute(); - $result = $this->backend->search(0, $pattern, $properties); + $result = $this->backend->search(0, $pattern, $properties, $options); // check result $this->assertSame(count($expected), count($result)); @@ -726,11 +753,13 @@ class CardDavBackendTest extends TestCase { public function dataTestSearch() { return [ - ['John', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], - ['M. Doe', ['FN'], [['uri1', 'John M. Doe']]], - ['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']]] + ['John', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], + ['M. Doe', ['FN'], [], [['uri1', 'John M. Doe']]], + ['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']]], + '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/ContactsManager.php b/lib/private/ContactsManager.php index e279997e63..4a36f00de5 100644 --- a/lib/private/ContactsManager.php +++ b/lib/private/ContactsManager.php @@ -35,7 +35,8 @@ namespace OC { * * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - for future use. One should always have options! + * @param array $options = array() to define the search behavior + * - 'escape_like_param' - If set to false wildcards _ and % are not escaped * @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 e744a92d9e..84948a929c 100644 --- a/lib/public/Contacts/IManager.php +++ b/lib/public/Contacts/IManager.php @@ -90,7 +90,8 @@ interface IManager { * * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - for future use. One should always have options! + * @param array $options = array() to define the search behavior + * - 'escape_like_param' - If set to false wildcards _ and % are not escaped * @return array an array of contacts which are arrays of key-value-pairs * @since 6.0.0 */