diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php index 94a4854dbd..0acec15047 100644 --- a/apps/files_sharing/lib/Controller/ShareesAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php @@ -6,6 +6,7 @@ * @author Joas Schilling * @author Roeland Jago Douma * @author Thomas Müller + * @author Arthur Schiwon * * @license AGPL-3.0 * @@ -27,52 +28,23 @@ namespace OCA\Files_Sharing\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCSController; -use OCP\Contacts\IManager; -use OCP\Federation\ICloudIdManager; -use OCP\Http\Client\IClientService; -use OCP\IGroup; -use OCP\IGroupManager; -use OCP\ILogger; +use OCP\Collaboration\Collaborators\ISearch; use OCP\IRequest; -use OCP\IUser; -use OCP\IUserManager; use OCP\IConfig; -use OCP\IUserSession; use OCP\IURLGenerator; use OCP\Share; +use OCP\Share\IManager; class ShareesAPIController extends OCSController { - - /** @var IGroupManager */ - protected $groupManager; - - /** @var IUserManager */ - protected $userManager; - - /** @var IManager */ - protected $contactsManager; - /** @var IConfig */ protected $config; - /** @var IUserSession */ - protected $userSession; - /** @var IURLGenerator */ protected $urlGenerator; - /** @var ILogger */ - protected $logger; - - /** @var \OCP\Share\IManager */ + /** @var IManager */ protected $shareManager; - /** @var IClientService */ - protected $clientService; - - /** @var ICloudIdManager */ - protected $cloudIdManager; - /** @var bool */ protected $shareWithGroupOnly = false; @@ -103,304 +75,31 @@ class ShareesAPIController extends OCSController { ]; protected $reachedEndFor = []; + /** @var ISearch */ + private $collaboratorSearch; /** * @param string $appName * @param IRequest $request - * @param IGroupManager $groupManager - * @param IUserManager $userManager - * @param IManager $contactsManager * @param IConfig $config - * @param IUserSession $userSession * @param IURLGenerator $urlGenerator - * @param ILogger $logger - * @param \OCP\Share\IManager $shareManager - * @param IClientService $clientService - * @param ICloudIdManager $cloudIdManager + * @param IManager $shareManager + * @param ISearch $collaboratorSearch */ - public function __construct($appName, - IRequest $request, - IGroupManager $groupManager, - IUserManager $userManager, - IManager $contactsManager, - IConfig $config, - IUserSession $userSession, - IURLGenerator $urlGenerator, - ILogger $logger, - \OCP\Share\IManager $shareManager, - IClientService $clientService, - ICloudIdManager $cloudIdManager + public function __construct( + $appName, + IRequest $request, + IConfig $config, + IURLGenerator $urlGenerator, + IManager $shareManager, + ISearch $collaboratorSearch ) { parent::__construct($appName, $request); - $this->groupManager = $groupManager; - $this->userManager = $userManager; - $this->contactsManager = $contactsManager; $this->config = $config; - $this->userSession = $userSession; $this->urlGenerator = $urlGenerator; - $this->logger = $logger; $this->shareManager = $shareManager; - $this->clientService = $clientService; - $this->cloudIdManager = $cloudIdManager; - } - - /** - * @param string $search - */ - protected function getUsers($search) { - $this->result['users'] = $this->result['exact']['users'] = $users = []; - - $userGroups = []; - if ($this->shareWithGroupOnly) { - // Search in all the groups this user is part of - $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); - foreach ($userGroups as $userGroup) { - $usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $this->limit, $this->offset); - foreach ($usersTmp as $uid => $userDisplayName) { - $users[$uid] = $userDisplayName; - } - } - } else { - // Search in all users - $usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset); - - foreach ($usersTmp as $user) { - $users[$user->getUID()] = $user->getDisplayName(); - } - } - - if (!$this->shareeEnumeration || sizeof($users) < $this->limit) { - $this->reachedEndFor[] = 'users'; - } - - $foundUserById = false; - $lowerSearch = strtolower($search); - foreach ($users as $uid => $userDisplayName) { - if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) { - if (strtolower($uid) === $lowerSearch) { - $foundUserById = true; - } - $this->result['exact']['users'][] = [ - 'label' => $userDisplayName, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_USER, - 'shareWith' => $uid, - ], - ]; - } else { - $this->result['users'][] = [ - 'label' => $userDisplayName, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_USER, - 'shareWith' => $uid, - ], - ]; - } - } - - if ($this->offset === 0 && !$foundUserById) { - // On page one we try if the search result has a direct hit on the - // user id and if so, we add that to the exact match list - $user = $this->userManager->get($search); - if ($user instanceof IUser) { - $addUser = true; - - if ($this->shareWithGroupOnly) { - // Only add, if we have a common group - $commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user)); - $addUser = !empty($commonGroups); - } - - if ($addUser) { - array_push($this->result['exact']['users'], [ - 'label' => $user->getDisplayName(), - 'value' => [ - 'shareType' => Share::SHARE_TYPE_USER, - 'shareWith' => $user->getUID(), - ], - ]); - } - } - } - - if (!$this->shareeEnumeration) { - $this->result['users'] = []; - } - } - - /** - * @param string $search - */ - protected function getGroups($search) { - $this->result['groups'] = $this->result['exact']['groups'] = []; - - $groups = $this->groupManager->search($search, $this->limit, $this->offset); - $groupIds = array_map(function (IGroup $group) { return $group->getGID(); }, $groups); - - if (!$this->shareeEnumeration || sizeof($groups) < $this->limit) { - $this->reachedEndFor[] = 'groups'; - } - - $userGroups = []; - if (!empty($groups) && $this->shareWithGroupOnly) { - // Intersect all the groups that match with the groups this user is a member of - $userGroups = $this->groupManager->getUserGroups($this->userSession->getUser()); - $userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups); - $groupIds = array_intersect($groupIds, $userGroups); - } - - $lowerSearch = strtolower($search); - foreach ($groups as $group) { - // FIXME: use a more efficient approach - $gid = $group->getGID(); - if (!in_array($gid, $groupIds)) { - continue; - } - if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) { - $this->result['exact']['groups'][] = [ - 'label' => $group->getDisplayName(), - 'value' => [ - 'shareType' => Share::SHARE_TYPE_GROUP, - 'shareWith' => $gid, - ], - ]; - } else { - $this->result['groups'][] = [ - 'label' => $group->getDisplayName(), - 'value' => [ - 'shareType' => Share::SHARE_TYPE_GROUP, - 'shareWith' => $gid, - ], - ]; - } - } - - if ($this->offset === 0 && empty($this->result['exact']['groups'])) { - // On page one we try if the search result has a direct hit on the - // user id and if so, we add that to the exact match list - $group = $this->groupManager->get($search); - if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) { - array_push($this->result['exact']['groups'], [ - 'label' => $group->getDisplayName(), - 'value' => [ - 'shareType' => Share::SHARE_TYPE_GROUP, - 'shareWith' => $group->getGID(), - ], - ]); - } - } - - if (!$this->shareeEnumeration) { - $this->result['groups'] = []; - } - } - - - /** - * @param string $search - * @suppress PhanUndeclaredClassMethod - */ - protected function getCircles($search) { - $this->result['circles'] = $this->result['exact']['circles'] = []; - - $result = \OCA\Circles\Api\Sharees::search($search, $this->limit, $this->offset); - if (array_key_exists('circles', $result['exact'])) { - $this->result['exact']['circles'] = $result['exact']['circles']; - } - if (array_key_exists('circles', $result)) { - $this->result['circles'] = $result['circles']; - } - } - - - /** - * @param string $search - * @return array - */ - protected function getRemote($search) { - $result = ['results' => [], 'exact' => []]; - - // Search in contacts - //@todo Pagination missing - $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); - $result['exactIdMatch'] = false; - foreach ($addressBookContacts as $contact) { - if (isset($contact['isLocalSystemBook'])) { - continue; - } - if (isset($contact['CLOUD'])) { - $cloudIds = $contact['CLOUD']; - if (!is_array($cloudIds)) { - $cloudIds = [$cloudIds]; - } - $lowerSearch = strtolower($search); - foreach ($cloudIds as $cloudId) { - try { - list(, $serverUrl) = $this->splitUserRemote($cloudId); - } catch (\InvalidArgumentException $e) { - continue; - } - - if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { - if (strtolower($cloudId) === $lowerSearch) { - $result['exactIdMatch'] = true; - } - $result['exact'][] = [ - 'label' => $contact['FN'] . " ($cloudId)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $cloudId, - 'server' => $serverUrl, - ], - ]; - } else { - $result['results'][] = [ - 'label' => $contact['FN'] . " ($cloudId)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $cloudId, - 'server' => $serverUrl, - ], - ]; - } - } - } - } - - if (!$this->shareeEnumeration) { - $result['results'] = []; - } - - if (!$result['exactIdMatch'] && $this->cloudIdManager->isValidCloudId($search) && $this->offset === 0) { - $result['exact'][] = [ - 'label' => $search, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $search, - ], - ]; - } - - $this->reachedEndFor[] = 'remotes'; - - return $result; - } - - /** - * split user and remote from federated cloud id - * - * @param string $address federated share address - * @return array [user, remoteURL] - * @throws \InvalidArgumentException - */ - public function splitUserRemote($address) { - try { - $cloudId = $this->cloudIdManager->resolveCloudId($address); - return [$cloudId->getUser(), $cloudId->getRemote()]; - } catch (\InvalidArgumentException $e) { - throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e); - } + $this->collaboratorSearch = $collaboratorSearch; } /** @@ -461,7 +160,9 @@ class ShareesAPIController extends OCSController { Share::SHARE_TYPE_USER, ]; - if ($itemType === 'file' || $itemType === 'folder') { + if ($itemType === null) { + throw new OCSBadRequestException('Missing itemType'); + } elseif ($itemType === 'file' || $itemType === 'folder') { if ($this->shareManager->allowGroupSharing()) { $shareTypes[] = Share::SHARE_TYPE_GROUP; } @@ -495,7 +196,20 @@ class ShareesAPIController extends OCSController { $this->limit = (int) $perPage; $this->offset = $perPage * ($page - 1); - return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage, $lookup); + list($result, $hasMoreResults) = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset); + + $response = new DataResponse($result); + + if ($hasMoreResults) { + $response->addHeader('Link', $this->getPaginationLink($page, [ + 'search' => $search, + 'itemType' => $itemType, + 'shareType' => $shareTypes, + 'perPage' => $perPage, + ])); + } + + return $response; } /** @@ -513,247 +227,6 @@ class ShareesAPIController extends OCSController { } } - /** - * Testable search function that does not need globals - * - * @param string $search - * @param string $itemType - * @param array $shareTypes - * @param int $page - * @param int $perPage - * @param bool $lookup - * @return DataResponse - * @throws OCSBadRequestException - */ - protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage, $lookup) { - // Verify arguments - if ($itemType === null) { - throw new OCSBadRequestException('Missing itemType'); - } - - // Get users - if (in_array(Share::SHARE_TYPE_USER, $shareTypes)) { - $this->getUsers($search); - } - - // Get groups - if (in_array(Share::SHARE_TYPE_GROUP, $shareTypes)) { - $this->getGroups($search); - } - - // Get circles - if (in_array(Share::SHARE_TYPE_CIRCLE, $shareTypes)) { - $this->getCircles($search); - } - - - // Get remote - $remoteResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false]; - if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes)) { - $remoteResults = $this->getRemote($search); - } - - // Get emails - $mailResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false]; - if (in_array(Share::SHARE_TYPE_EMAIL, $shareTypes)) { - $mailResults = $this->getEmail($search); - } - - // Get from lookup server - if ($lookup) { - $this->getLookup($search); - } - - // if we have a exact match, either for the federated cloud id or for the - // email address we only return the exact match. It is highly unlikely - // that the exact same email address and federated cloud id exists - if ($mailResults['exactIdMatch'] && !$remoteResults['exactIdMatch']) { - $this->result['emails'] = $mailResults['results']; - $this->result['exact']['emails'] = $mailResults['exact']; - } else if (!$mailResults['exactIdMatch'] && $remoteResults['exactIdMatch']) { - $this->result['remotes'] = $remoteResults['results']; - $this->result['exact']['remotes'] = $remoteResults['exact']; - } else { - $this->result['remotes'] = $remoteResults['results']; - $this->result['exact']['remotes'] = $remoteResults['exact']; - $this->result['emails'] = $mailResults['results']; - $this->result['exact']['emails'] = $mailResults['exact']; - } - - $response = new DataResponse($this->result); - - if (sizeof($this->reachedEndFor) < 3) { - $response->addHeader('Link', $this->getPaginationLink($page, [ - 'search' => $search, - 'itemType' => $itemType, - 'shareType' => $shareTypes, - 'perPage' => $perPage, - ])); - } - - return $response; - } - - /** - * @param string $search - * @return array - */ - protected function getEmail($search) { - $result = ['results' => [], 'exact' => [], 'exactIdMatch' => false]; - - // Search in contacts - //@todo Pagination missing - $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']); - $lowerSearch = strtolower($search); - foreach ($addressBookContacts as $contact) { - if (isset($contact['EMAIL'])) { - $emailAddresses = $contact['EMAIL']; - if (!is_array($emailAddresses)) { - $emailAddresses = [$emailAddresses]; - } - foreach ($emailAddresses as $emailAddress) { - $exactEmailMatch = strtolower($emailAddress) === $lowerSearch; - - if (isset($contact['isLocalSystemBook'])) { - if ($exactEmailMatch) { - try { - $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); - } catch (\InvalidArgumentException $e) { - continue; - } - - if (!$this->hasUserInResult($cloud->getUser())) { - $this->result['exact']['users'][] = [ - 'label' => $contact['FN'] . " ($emailAddress)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_USER, - 'shareWith' => $cloud->getUser(), - ], - ]; - } - return ['results' => [], 'exact' => [], 'exactIdMatch' => true]; - } - - if ($this->shareeEnumeration) { - try { - $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); - } catch (\InvalidArgumentException $e) { - continue; - } - - if (!$this->hasUserInResult($cloud->getUser())) { - $this->result['users'][] = [ - 'label' => $contact['FN'] . " ($emailAddress)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_USER, - 'shareWith' => $cloud->getUser(), - ], - ]; - } - } - continue; - } - - if ($exactEmailMatch || strtolower($contact['FN']) === $lowerSearch) { - if ($exactEmailMatch) { - $result['exactIdMatch'] = true; - } - $result['exact'][] = [ - 'label' => $contact['FN'] . " ($emailAddress)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_EMAIL, - 'shareWith' => $emailAddress, - ], - ]; - } else { - $result['results'][] = [ - 'label' => $contact['FN'] . " ($emailAddress)", - 'value' => [ - 'shareType' => Share::SHARE_TYPE_EMAIL, - 'shareWith' => $emailAddress, - ], - ]; - } - } - } - } - - if (!$this->shareeEnumeration) { - $result['results'] = []; - } - - if (!$result['exactIdMatch'] && filter_var($search, FILTER_VALIDATE_EMAIL)) { - $result['exact'][] = [ - 'label' => $search, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_EMAIL, - 'shareWith' => $search, - ], - ]; - } - - $this->reachedEndFor[] = 'emails'; - - return $result; - } - - protected function getLookup($search) { - $isEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no'); - $lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com'); - $lookupServerUrl = rtrim($lookupServerUrl, '/'); - $result = []; - - if($isEnabled === 'yes') { - try { - $client = $this->clientService->newClient(); - $response = $client->get( - $lookupServerUrl . '/users?search=' . urlencode($search), - [ - 'timeout' => 10, - 'connect_timeout' => 3, - ] - ); - - $body = json_decode($response->getBody(), true); - - $result = []; - foreach ($body as $lookup) { - $result[] = [ - 'label' => $lookup['federationId'], - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $lookup['federationId'], - ], - 'extra' => $lookup, - ]; - } - } catch (\Exception $e) {} - } - - $this->result['lookup'] = $result; - } - - /** - * Check if a given user is already part of the result - * - * @param string $userId - * @return bool - */ - protected function hasUserInResult($userId) { - foreach ($this->result['exact']['users'] as $result) { - if ($result['value']['shareWith'] === $userId) { - return true; - } - } - - foreach ($this->result['users'] as $result) { - if ($result['value']['shareWith'] === $userId) { - return true; - } - } - - return false; - } /** * Generates a bunch of pagination links for the current page diff --git a/lib/private/Collaboration/Collaborators/CirclePlugin.php b/lib/private/Collaboration/Collaborators/CirclePlugin.php new file mode 100644 index 0000000000..3d476644ec --- /dev/null +++ b/lib/private/Collaboration/Collaborators/CirclePlugin.php @@ -0,0 +1,49 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCA\Circles\Api\Sharees; +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; + +class CirclePlugin implements ISearchPlugin { + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + + if(\OC_App::isEnabled('circles')) { + $circles = Sharees::search($search); + if (array_key_exists('circles', $circles['exact'])) { + $result['exact'] = $circles['exact']['circles']; + } + if (array_key_exists('circles', $circles)) { + $result['wide'] = $circles['circles']; + } + + $searchResult->addResultSet('circles', $result['wide'], $result['exact']); + } + + return false; + } +} diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php new file mode 100644 index 0000000000..d156190eed --- /dev/null +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -0,0 +1,122 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUserSession; +use OCP\Share; + +class GroupPlugin implements ISearchPlugin { + protected $shareeEnumeration; + protected $shareWithGroupOnly; + + /** @var IGroupManager */ + private $groupManager; + /** @var IConfig */ + private $config; + /** @var IUserSession */ + private $userSession; + + public function __construct(IConfig $config, IGroupManager $groupManager, IUserSession $userSession) { + $this->groupManager = $groupManager; + $this->config = $config; + $this->userSession = $userSession; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $hasMoreResults = false; + $result = ['wide' => [], 'exact' => []]; + + $groups = $this->groupManager->search($search, $limit, $offset); + $groupIds = array_map(function (IGroup $group) { return $group->getGID(); }, $groups); + + if (!$this->shareeEnumeration || sizeof($groups) < $limit) { + $hasMoreResults = true; + } + + $userGroups = []; + if (!empty($groups) && $this->shareWithGroupOnly) { + // Intersect all the groups that match with the groups this user is a member of + $userGroups = $this->groupManager->getUserGroups($this->userSession->getUser()); + $userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups); + $groupIds = array_intersect($groupIds, $userGroups); + } + + $lowerSearch = strtolower($search); + foreach ($groups as $group) { + // FIXME: use a more efficient approach + $gid = $group->getGID(); + if (!in_array($gid, $groupIds)) { + continue; + } + if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) { + $result['exact'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } + } + + if ($offset === 0 && empty($result['exact'])) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $group = $this->groupManager->get($search); + if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) { + array_push($result['exact'], [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $group->getGID(), + ], + ]); + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + $searchResult->addResultSet('groups', $result['wide'], $result['exact']); + + return [$result, $hasMoreResults]; + } +} diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php new file mode 100644 index 0000000000..567b41ca76 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -0,0 +1,83 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\Share; + +class LookupPlugin implements ISearchPlugin { + + /** @var IConfig */ + private $config; + /** @var IClientService */ + private $clientService; + + public function __construct(IConfig $config, IClientService $clientService) { + $this->config = $config; + $this->clientService = $clientService; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + if ($this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no') !== 'yes') { + return false; + } + + $lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com'); + $lookupServerUrl = rtrim($lookupServerUrl, '/'); + $result = ['wide' => [], 'exact' => []]; + + try { + $client = $this->clientService->newClient(); + $response = $client->get( + $lookupServerUrl . '/users?search=' . urlencode($search), + [ + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + + $body = json_decode($response->getBody(), true); + + $result = []; + foreach ($body as $lookup) { + $result[] = [ + 'label' => $lookup['federationId'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $lookup['federationId'], + ], + 'extra' => $lookup, + ]; + } + } catch (\Exception $e) { + } + + $searchResult->addResultSet('lookup', $result, []); + + } +} diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php new file mode 100644 index 0000000000..bd4d70e41d --- /dev/null +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -0,0 +1,162 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\Share; + +class MailPlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + /** + * @param $search + * @param $limit + * @param $offset + * @param ISearchResult $searchResult + * @return bool + * @since 13.0.0 + */ + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + + // Search in contacts + //@todo Pagination missing + $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']); + $lowerSearch = strtolower($search); + foreach ($addressBookContacts as $contact) { + if (isset($contact['EMAIL'])) { + $emailAddresses = $contact['EMAIL']; + if (!is_array($emailAddresses)) { + $emailAddresses = [$emailAddresses]; + } + foreach ($emailAddresses as $emailAddress) { + $exactEmailMatch = strtolower($emailAddress) === $lowerSearch; + + if (isset($contact['isLocalSystemBook'])) { + if ($exactEmailMatch) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$searchResult->hasResult('users', $cloud->getUser())) { + $singleResult = [[ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $cloud->getUser(), + ], + ]]; + $searchResult->addResultSet('users', [], $singleResult); + $searchResult->markExactIdMatch('emails'); + } + return false; + } + + if ($this->shareeEnumeration) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$searchResult->hasResult('users', $cloud->getUser())) { + $singleResult = [[ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $cloud->getUser(), + ]], + ]; + $searchResult->addResultSet('users', $singleResult, []); + $result = []; + } + } + continue; + } + + if ($exactEmailMatch || strtolower($contact['FN']) === $lowerSearch) { + if ($exactEmailMatch) { + $searchResult->markExactIdMatch('emails'); + } + $result['exact'][] = [ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + if (!$searchResult->hasExactIdMatch('emails') && filter_var($search, FILTER_VALIDATE_EMAIL)) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $search, + ], + ]; + } + + $searchResult->addResultSet('emails', $result['wide'], $result['exact']); + + return false; + } +} diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php new file mode 100644 index 0000000000..ff075b8e42 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -0,0 +1,135 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\Share; + +class RemotePlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + + // Search in contacts + //@todo Pagination missing + $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); + foreach ($addressBookContacts as $contact) { + if (isset($contact['isLocalSystemBook'])) { + continue; + } + if (isset($contact['CLOUD'])) { + $cloudIds = $contact['CLOUD']; + if (!is_array($cloudIds)) { + $cloudIds = [$cloudIds]; + } + $lowerSearch = strtolower($search); + foreach ($cloudIds as $cloudId) { + try { + list(, $serverUrl) = $this->splitUserRemote($cloudId); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { + if (strtolower($cloudId) === $lowerSearch) { + $searchResult->hasExactIdMatch('remotes'); + } + $result['exact'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + if (!$searchResult->hasExactIdMatch('remotes') && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $search, + ], + ]; + } + + $searchResult->addResultSet('remotes', $result['wide'], $result['exact']); + + return false; + } + + /** + * split user and remote from federated cloud id + * + * @param string $address federated share address + * @return array [user, remoteURL] + * @throws \InvalidArgumentException + */ + public function splitUserRemote($address) { + try { + $cloudId = $this->cloudIdManager->resolveCloudId($address); + return [$cloudId->getUser(), $cloudId->getRemote()]; + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e); + } + } +} diff --git a/lib/private/Collaboration/Collaborators/Search.php b/lib/private/Collaboration/Collaborators/Search.php new file mode 100644 index 0000000000..abca13bcfe --- /dev/null +++ b/lib/private/Collaboration/Collaborators/Search.php @@ -0,0 +1,82 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearch; +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\IContainer; +use OCP\Share; + +class Search implements ISearch { + /** @var IContainer */ + private $c; + + public function __construct(IContainer $c) { + $this->c = $c; + } + + public function search($search, array $shareTypes, $lookup, $limit, $offset) { + $hasMoreResults = false; + + $pluginList = [ + Share::SHARE_TYPE_USER => UserPlugin::class, + Share::SHARE_TYPE_GROUP => GroupPlugin::class, + Share::SHARE_TYPE_CIRCLE => CirclePlugin::class, + Share::SHARE_TYPE_EMAIL => MailPlugin::class, + Share::SHARE_TYPE_REMOTE => RemotePlugin::class, + ]; + + /** @var ISearchResult $searchResult */ + $searchResult = $this->c->resolve(SearchResult::class); + + foreach ($shareTypes as $type) { + if(!isset($pluginList[$type])) { + continue; + } + /** @var ISearchPlugin $searchPlugin */ + $searchPlugin = $this->c->resolve($pluginList[$type]); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + + // Get from lookup server, not a separate share type + if ($lookup) { + $searchPlugin = $this->c->resolve(LookupPlugin::class); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + + // sanitizing, could go into the plugins as well + + // if we have a exact match, either for the federated cloud id or for the + // email address we only return the exact match. It is highly unlikely + // that the exact same email address and federated cloud id exists + if($searchResult->hasExactIdMatch('emails') && !$searchResult->hasExactIdMatch('remotes')) { + $searchResult->unsetResult('remotes'); + } elseif (!$searchResult->hasExactIdMatch('emails') && $searchResult->hasExactIdMatch('remotes')) { + $searchResult->unsetResult('emails'); + } + + return [$searchResult->asArray(), $hasMoreResults]; + } +} diff --git a/lib/private/Collaboration/Collaborators/SearchResult.php b/lib/private/Collaboration/Collaborators/SearchResult.php new file mode 100644 index 0000000000..f3559c9327 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/SearchResult.php @@ -0,0 +1,97 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchResult; + +class SearchResult implements ISearchResult { + + protected $result = [ + 'exact' => [ + 'users' => [], + 'groups' => [], + 'remotes' => [], + 'emails' => [], + 'circles' => [], + ], + 'users' => [], + 'groups' => [], + 'remotes' => [], + 'emails' => [], + 'lookup' => [], + 'circles' => [], + ]; + + protected $exactIdMatches = []; + + public function addResultSet($type, array $matches, array $exactMatches = null) { + if(!isset($this->result[$type])) { + throw new \InvalidArgumentException('Invalid type provided'); + } + + $this->result[$type] = array_merge($this->result[$type], $matches); + if(is_array($exactMatches)) { + $this->result['exact'][$type] = array_merge($this->result['exact'][$type], $exactMatches); + } + } + + public function markExactIdMatch($type) { + $this->exactIdMatches[$type] = 1; + } + + public function hasExactIdMatch($type) { + return isset($this->exactIdMatches[$type]); + } + + public function hasResult($type, $collaboratorId) { + if(!isset($this->result[$type])) { + throw new \InvalidArgumentException('Invalid type provided'); + } + + $resultArrays = [$this->result['exact'][$type], $this->result[$type]]; + foreach($resultArrays as $resultArray) { + if ($resultArray['value']['shareWith'] === $collaboratorId) { + return true; + } + } + + return false; + } + + public function asArray() { + return $this->result; + } + + public function unsetResult($type) { + if(!isset($this->result[$type])) { + throw new \InvalidArgumentException('Invalid type provided'); + } + + $this->result[$type] = []; + if(isset($this->$result['exact'][$type])) { + $this->result['exact'][$type] = []; + } + } +} diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php new file mode 100644 index 0000000000..815862b872 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -0,0 +1,147 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share; + +class UserPlugin implements ISearchPlugin { + /* @var bool */ + protected $shareWithGroupOnly; + protected $shareeEnumeration; + + /** @var IConfig */ + private $config; + /** @var IGroupManager */ + private $groupManager; + /** @var IUserSession */ + private $userSession; + /** @var IUserManager */ + private $userManager; + + public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, IUserSession $userSession) { + $this->config = $config; + + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + $this->userManager = $userManager; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $users = []; + $hasMoreResults = false; + + $userGroups = []; + if ($this->shareWithGroupOnly) { + // Search in all the groups this user is part of + $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + foreach ($userGroups as $userGroup) { + $usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $limit, $offset); + foreach ($usersTmp as $uid => $userDisplayName) { + $users[$uid] = $userDisplayName; + } + } + } else { + // Search in all users + $usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset); + + foreach ($usersTmp as $user) { + $users[$user->getUID()] = $user->getDisplayName(); + } + } + + if (!$this->shareeEnumeration || sizeof($users) < $limit) { + $hasMoreResults = true; + } + + $foundUserById = false; + $lowerSearch = strtolower($search); + foreach ($users as $uid => $userDisplayName) { + if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) { + if (strtolower($uid) === $lowerSearch) { + $foundUserById = true; + } + $result['exact'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } + } + + if ($offset === 0 && !$foundUserById) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $user = $this->userManager->get($search); + if ($user instanceof IUser) { + $addUser = true; + + if ($this->shareWithGroupOnly) { + // Only add, if we have a common group + $commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user)); + $addUser = !empty($commonGroups); + } + + if ($addUser) { + array_push($result['exact'], [ + 'label' => $user->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $user->getUID(), + ], + ]); + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + $searchResult->addResultSet('users', $result['wide'], $result['exact']); + + return $hasMoreResults; + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index a20d9ccfc0..88fc79e472 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -993,6 +993,11 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias('ShareManager', \OCP\Share\IManager::class); + $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function(Server $c) { + return new Collaboration\Collaborators\Search($c); + }); + $this->registerAlias('CollaboratorSearch', \OCP\Collaboration\Collaborators\ISearch::class); + $this->registerService('SettingsManager', function (Server $c) { $manager = new \OC\Settings\Manager( $c->getLogger(), @@ -1776,6 +1781,13 @@ class Server extends ServerContainer implements IServerContainer { return $this->query('ShareManager'); } + /** + * @return \OCP\Collaboration\Collaborators\ISearch + */ + public function getCollaboratorSearch() { + return $this->query('CollaboratorSearch'); + } + /** * Returns the LDAP Provider * diff --git a/lib/public/Collaboration/Collaborators/ISearch.php b/lib/public/Collaboration/Collaborators/ISearch.php new file mode 100644 index 0000000000..db2e5f7ac0 --- /dev/null +++ b/lib/public/Collaboration/Collaborators/ISearch.php @@ -0,0 +1,37 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCP\Collaboration\Collaborators; + +interface ISearch { + /** + * @param string $search + * @param array $shareTypes + * @param bool $lookup + * @param int $limit + * @param int $offset + * @return array with two elements, 1st ISearchResult as array, 2nd a bool indicating whether more result are available + * @since 13.0.0 + */ + public function search($search, array $shareTypes, $lookup, $limit, $offset); +} diff --git a/lib/public/Collaboration/Collaborators/ISearchPlugin.php b/lib/public/Collaboration/Collaborators/ISearchPlugin.php new file mode 100644 index 0000000000..cae93bfd73 --- /dev/null +++ b/lib/public/Collaboration/Collaborators/ISearchPlugin.php @@ -0,0 +1,37 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCP\Collaboration\Collaborators; + + +interface ISearchPlugin { + /** + * @param string $search + * @param int $limit + * @param int $offset + * @param ISearchResult $searchResult + * @return bool whether the plugin has more results + * @since 13.0.0 + */ + public function search($search, $limit, $offset, ISearchResult $searchResult); +} diff --git a/lib/public/Collaboration/Collaborators/ISearchResult.php b/lib/public/Collaboration/Collaborators/ISearchResult.php new file mode 100644 index 0000000000..b8d0a32cc0 --- /dev/null +++ b/lib/public/Collaboration/Collaborators/ISearchResult.php @@ -0,0 +1,68 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCP\Collaboration\Collaborators; + + +interface ISearchResult { + /** + * @param string $type one of: users, groups, remotes, email, circles, lookup + * @param array $matches + * @param array|null $exactMatches + * @since 13.0.0 + */ + public function addResultSet($type, array $matches, array $exactMatches = null); + + /** + * @param string $type one of: users, groups, remotes, email, circles, lookup + * @param string $collaboratorId + * @return bool + * @since 13.0.0 + */ + public function hasResult($type, $collaboratorId); + + /** + * @param string $type one of: users, groups, remotes, email, circles, lookup + * @since 13.0.0 + */ + public function unsetResult($type); + + /** + * @param string $type one of: users, groups, remotes, email, circles, lookup + * @since 13.0.0 + */ + public function markExactIdMatch($type); + + /** + * @param string $type one of: users, groups, remotes, email, circles, lookup + * @return bool + * @since 13.0.0 + */ + public function hasExactIdMatch($type); + + /** + * @return array + * @since 13.0.0 + */ + public function asArray(); +}