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