1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright Copyright (c) 2020, Georg Ehrke
7 *
8 * @author Georg Ehrke <oc.list@georgehrke.com>
9 * @author Joas Schilling <coding@schilljs.com>
10 * @author John Molakvoæ <skjnldsv@protonmail.com>
11 *
12 * @license GNU AGPL version 3 or any later version
13 *
14 * This program is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Affero General Public License as
16 * published by the Free Software Foundation, either version 3 of the
17 * License, or (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Affero General Public License for more details.
23 *
24 * You should have received a copy of the GNU Affero General Public License
25 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 *
27 */
28namespace OCA\DAV\Search;
29
30use OCA\DAV\CardDAV\CardDavBackend;
31use OCP\App\IAppManager;
32use OCP\IL10N;
33use OCP\IURLGenerator;
34use OCP\IUser;
35use OCP\Search\IProvider;
36use OCP\Search\ISearchQuery;
37use OCP\Search\SearchResult;
38use OCP\Search\SearchResultEntry;
39use Sabre\VObject\Component\VCard;
40use Sabre\VObject\Reader;
41
42class ContactsSearchProvider implements IProvider {
43
44	/** @var IAppManager */
45	private $appManager;
46
47	/** @var IL10N */
48	private $l10n;
49
50	/** @var IURLGenerator */
51	private $urlGenerator;
52
53	/** @var CardDavBackend */
54	private $backend;
55
56	/**
57	 * @var string[]
58	 */
59	private static $searchProperties = [
60		'N',
61		'FN',
62		'NICKNAME',
63		'EMAIL',
64		'TEL',
65		'ADR',
66		'TITLE',
67		'ORG',
68		'NOTE',
69	];
70
71	/**
72	 * ContactsSearchProvider constructor.
73	 *
74	 * @param IAppManager $appManager
75	 * @param IL10N $l10n
76	 * @param IURLGenerator $urlGenerator
77	 * @param CardDavBackend $backend
78	 */
79	public function __construct(IAppManager $appManager,
80								IL10N $l10n,
81								IURLGenerator $urlGenerator,
82								CardDavBackend $backend) {
83		$this->appManager = $appManager;
84		$this->l10n = $l10n;
85		$this->urlGenerator = $urlGenerator;
86		$this->backend = $backend;
87	}
88
89	/**
90	 * @inheritDoc
91	 */
92	public function getId(): string {
93		return 'contacts';
94	}
95
96	/**
97	 * @inheritDoc
98	 */
99	public function getName(): string {
100		return $this->l10n->t('Contacts');
101	}
102
103	/**
104	 * @inheritDoc
105	 */
106	public function getOrder(string $route, array $routeParameters): int {
107		if ($route === 'contacts.Page.index') {
108			return -1;
109		}
110		return 25;
111	}
112
113	/**
114	 * @inheritDoc
115	 */
116	public function search(IUser $user, ISearchQuery $query): SearchResult {
117		if (!$this->appManager->isEnabledForUser('contacts', $user)) {
118			return SearchResult::complete($this->getName(), []);
119		}
120
121		$principalUri = 'principals/users/' . $user->getUID();
122		$addressBooks = $this->backend->getAddressBooksForUser($principalUri);
123		$addressBooksById = [];
124		foreach ($addressBooks as $addressBook) {
125			$addressBooksById[(int) $addressBook['id']] = $addressBook;
126		}
127
128		$searchResults = $this->backend->searchPrincipalUri(
129			$principalUri,
130			$query->getTerm(),
131			self::$searchProperties,
132			[
133				'limit' => $query->getLimit(),
134				'offset' => $query->getCursor(),
135			]
136		);
137		$formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):SearchResultEntry {
138			$addressBook = $addressBooksById[$contactRow['addressbookid']];
139
140			/** @var VCard $vCard */
141			$vCard = Reader::read($contactRow['carddata']);
142			$thumbnailUrl = '';
143			if ($vCard->PHOTO) {
144				$thumbnailUrl = $this->getDavUrlForContact($addressBook['principaluri'], $addressBook['uri'], $contactRow['uri']) . '?photo';
145			}
146
147			$title = (string)$vCard->FN;
148			$subline = $this->generateSubline($vCard);
149			$resourceUrl = $this->getDeepLinkToContactsApp($addressBook['uri'], (string) $vCard->UID);
150
151			return new SearchResultEntry($thumbnailUrl, $title, $subline, $resourceUrl, 'icon-contacts-dark', true);
152		}, $searchResults);
153
154		return SearchResult::paginated(
155			$this->getName(),
156			$formattedResults,
157			$query->getCursor() + count($formattedResults)
158		);
159	}
160
161	/**
162	 * @param string $principalUri
163	 * @param string $addressBookUri
164	 * @param string $contactsUri
165	 * @return string
166	 */
167	protected function getDavUrlForContact(string $principalUri,
168										   string $addressBookUri,
169										   string $contactsUri): string {
170		[, $principalType, $principalId] = explode('/', $principalUri, 3);
171
172		return $this->urlGenerator->getAbsoluteURL(
173			$this->urlGenerator->linkTo('', 'remote.php') . '/dav/addressbooks/'
174				. $principalType . '/'
175				. $principalId . '/'
176				. $addressBookUri . '/'
177				. $contactsUri
178		);
179	}
180
181	/**
182	 * @param string $addressBookUri
183	 * @param string $contactUid
184	 * @return string
185	 */
186	protected function getDeepLinkToContactsApp(string $addressBookUri,
187												string $contactUid): string {
188		return $this->urlGenerator->getAbsoluteURL(
189			$this->urlGenerator->linkToRoute('contacts.contacts.direct', [
190				'contact' => $contactUid . '~' . $addressBookUri
191			])
192		);
193	}
194
195	/**
196	 * @param VCard $vCard
197	 * @return string
198	 */
199	protected function generateSubline(VCard $vCard): string {
200		$emailAddresses = $vCard->select('EMAIL');
201		if (!is_array($emailAddresses) || empty($emailAddresses)) {
202			return '';
203		}
204
205		return (string)$emailAddresses[0];
206	}
207}
208