aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib')
-rw-r--r--apps/dav/lib/AppInfo/Application.php6
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php61
-rw-r--r--apps/dav/lib/Search/ContactsSearchProvider.php189
-rw-r--r--apps/dav/lib/Search/ContactsSearchResultEntry.php30
4 files changed, 278 insertions, 8 deletions
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index bf1e6146330..6f2f7b29153 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -54,6 +54,7 @@ use OCA\DAV\CardDAV\ContactsManager;
use OCA\DAV\CardDAV\PhotoCache;
use OCA\DAV\CardDAV\SyncService;
use OCA\DAV\HookManager;
+use OCA\DAV\Search\ContactsSearchProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
@@ -96,6 +97,11 @@ class Application extends App implements IBootstrap {
* Register capabilities
*/
$context->registerCapability(Capabilities::class);
+
+ /*
+ * Register Search Providers
+ */
+ $context->registerSearchProvider(ContactsSearchProvider::class);
}
public function boot(IBootContext $context): void {
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 9d602025c7a..3b3474cdd01 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -951,7 +951,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
/**
- * search contact
+ * Search contacts in a specific address-book
*
* @param int $addressBookId
* @param string $pattern which should match within the $searchProperties
@@ -962,11 +962,55 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* - '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 = []) {
+ public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
+ return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
+ }
+
+ /**
+ * Search contacts in all address-books accessible by a user
+ *
+ * @param string $principalUri
+ * @param string $pattern
+ * @param array $searchProperties
+ * @param array $options
+ * @return array
+ */
+ public function searchPrincipalUri(string $principalUri,
+ string $pattern,
+ array $searchProperties,
+ array $options = []): array {
+ $addressBookIds = array_map(static function ($row):int {
+ return (int) $row['id'];
+ }, $this->getAddressBooksForUser($principalUri));
+
+ return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
+ }
+
+ /**
+ * @param array $addressBookIds
+ * @param string $pattern
+ * @param array $searchProperties
+ * @param array $options
+ * @return array
+ */
+ private function searchByAddressBookIds(array $addressBookIds,
+ string $pattern,
+ array $searchProperties,
+ array $options = []): array {
$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
$query2 = $this->db->getQueryBuilder();
- $or = $query2->expr()->orX();
+
+ $addressBookOr = $query2->expr()->orX();
+ foreach ($addressBookIds as $addressBookId) {
+ $addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
+ }
+
+ if ($addressBookOr->count() === 0) {
+ return [];
+ }
+
+ $propertyOr = $query2->expr()->orX();
foreach ($searchProperties as $property) {
if ($escapePattern) {
if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
@@ -980,17 +1024,17 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
}
- $or->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
+ $propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
}
- if ($or->count() === 0) {
+ if ($propertyOr->count() === 0) {
return [];
}
$query2->selectDistinct('cp.cardid')
->from($this->dbCardsPropertiesTable, 'cp')
- ->andWhere($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)))
- ->andWhere($or);
+ ->andWhere($addressBookOr)
+ ->andWhere($propertyOr);
// No need for like when the pattern is empty
if ('' !== $pattern) {
@@ -1016,7 +1060,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}, $matches);
$query = $this->db->getQueryBuilder();
- $query->select('c.carddata', 'c.uri')
+ $query->select('c.addressbookid', 'c.carddata', 'c.uri')
->from($this->dbCardsTable, 'c')
->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
@@ -1026,6 +1070,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$result->closeCursor();
return array_map(function ($array) {
+ $array['addressbookid'] = (int) $array['addressbookid'];
$modified = false;
$array['carddata'] = $this->readBlob($array['carddata'], $modified);
if ($modified) {
diff --git a/apps/dav/lib/Search/ContactsSearchProvider.php b/apps/dav/lib/Search/ContactsSearchProvider.php
new file mode 100644
index 00000000000..656b484c2b9
--- /dev/null
+++ b/apps/dav/lib/Search/ContactsSearchProvider.php
@@ -0,0 +1,189 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Search;
+
+use OCA\DAV\CardDAV\CardDavBackend;
+use OCP\App\IAppManager;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\Search\IProvider;
+use OCP\Search\ISearchQuery;
+use OCP\Search\SearchResult;
+use Sabre\VObject\Component\VCard;
+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[]
+ */
+ private static $searchProperties = [
+ 'N',
+ 'FN',
+ 'NICKNAME',
+ 'EMAIL',
+ 'ADR',
+ ];
+
+ /**
+ * 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;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getId(): string {
+ return 'contacts-dav';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getName(): string {
+ return $this->l10n->t('Contacts');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function search(IUser $user, ISearchQuery $query): SearchResult {
+ if (!$this->appManager->isEnabledForUser('contacts', $user)) {
+ return SearchResult::complete($this->getName(), []);
+ }
+
+ $principalUri = 'principals/users/' . $user->getUID();
+ $addressBooks = $this->backend->getAddressBooksForUser($principalUri);
+ $addressBooksById = [];
+ foreach ($addressBooks as $addressBook) {
+ $addressBooksById[(int) $addressBook['id']] = $addressBook;
+ }
+
+ $searchResults = $this->backend->searchPrincipalUri(
+ $principalUri,
+ $query->getTerm(),
+ self::$searchProperties,
+ [
+ 'limit' => $query->getLimit(),
+ 'offset' => $query->getCursor(),
+ ]
+ );
+ $formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):ContactsSearchResultEntry {
+ $addressBook = $addressBooksById[$contactRow['addressbookid']];
+
+ /** @var VCard $vCard */
+ $vCard = Reader::read($contactRow['carddata']);
+ $thumbnailUrl = '';
+ if ($vCard->PHOTO) {
+ $thumbnailUrl = $this->getDavUrlForContact($addressBook['principaluri'], $addressBook['uri'], $contactRow['uri']) . '?photo';
+ }
+
+ $title = (string)$vCard->FN;
+ $subline = $this->generateSubline($vCard);
+ $resourceUrl = $this->getDeepLinkToContactsApp($addressBook['uri'], (string) $vCard->UID);
+
+ return new ContactsSearchResultEntry($thumbnailUrl, $title, $subline, $resourceUrl, 'icon-contacts-dark', true);
+ }, $searchResults);
+
+ return SearchResult::paginated(
+ $this->getName(),
+ $formattedResults,
+ $query->getCursor() + count($formattedResults)
+ );
+ }
+
+ /**
+ * @param string $principalUri
+ * @param string $addressBookUri
+ * @param string $contactsUri
+ * @return string
+ */
+ protected function getDavUrlForContact(string $principalUri,
+ string $addressBookUri,
+ string $contactsUri): string {
+ [, $principalType, $principalId] = explode('/', $principalUri, 3);
+
+ return $this->urlGenerator->getAbsoluteURL(
+ $this->urlGenerator->linkTo('', 'remote.php') . '/dav/addressbooks/'
+ . $principalType . '/'
+ . $principalId . '/'
+ . $addressBookUri . '/'
+ . $contactsUri
+ );
+ }
+
+ /**
+ * @param string $addressBookUri
+ * @param string $contactUid
+ * @return string
+ */
+ protected function getDeepLinkToContactsApp(string $addressBookUri,
+ string $contactUid): string {
+ return $this->urlGenerator->getAbsoluteURL(
+ $this->urlGenerator->linkToRoute('contacts.contacts.direct', [
+ 'contact' => $contactUid . '~' . $addressBookUri
+ ])
+ );
+ }
+
+ /**
+ * @param VCard $vCard
+ * @return string
+ */
+ protected function generateSubline(VCard $vCard): string {
+ $emailAddresses = $vCard->select('EMAIL');
+ if (!is_array($emailAddresses) || empty($emailAddresses)) {
+ return '';
+ }
+
+ return (string)$emailAddresses[0];
+ }
+}
diff --git a/apps/dav/lib/Search/ContactsSearchResultEntry.php b/apps/dav/lib/Search/ContactsSearchResultEntry.php
new file mode 100644
index 00000000000..698fc1b3f4a
--- /dev/null
+++ b/apps/dav/lib/Search/ContactsSearchResultEntry.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Search;
+
+use OCP\Search\ASearchResultEntry;
+
+class ContactsSearchResultEntry extends ASearchResultEntry {
+}