mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 16:50:55 -04:00
Merge pull request #29269 from nextcloud/feature/28751/provide-contactsmenu-as-ocs-simple
Add an OCS endpoint for the hovercard contact actions
This commit is contained in:
commit
0e951eb9dc
13 changed files with 178 additions and 43 deletions
84
core/Controller/HoverCardController.php
Normal file
84
core/Controller/HoverCardController.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright 2021 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OC\Core\Controller;
|
||||
|
||||
use OC\Contacts\ContactsMenu\Manager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\Contacts\ContactsMenu\IEntry;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
class HoverCardController extends \OCP\AppFramework\OCSController {
|
||||
|
||||
/** @var Manager */
|
||||
private $manager;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/**
|
||||
* @param IRequest $request
|
||||
* @param IUserSession $userSession
|
||||
* @param Manager $manager
|
||||
*/
|
||||
public function __construct(IRequest $request, IUserSession $userSession, Manager $manager) {
|
||||
parent::__construct('core', $request);
|
||||
$this->userSession = $userSession;
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param string $userId
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function getUser(string $userId): DataResponse {
|
||||
$contact = $this->manager->findOne($this->userSession->getUser(), IShare::TYPE_USER, $userId);
|
||||
|
||||
if (!$contact) {
|
||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = $this->entryToArray($contact);
|
||||
|
||||
$actions = $data['actions'];
|
||||
if ($data['topAction']) {
|
||||
array_unshift($actions, $data['topAction']);
|
||||
}
|
||||
|
||||
return new DataResponse([
|
||||
'userId' => $userId,
|
||||
'displayName' => $contact->getFullName(),
|
||||
'actions' => $actions,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function entryToArray(IEntry $entry): array {
|
||||
return json_decode(json_encode($entry), true);
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +110,8 @@ $application->registerRoutes($this, [
|
|||
['root' => '/core', 'name' => 'AppPassword#rotateAppPassword', 'url' => '/apppassword/rotate', 'verb' => 'POST'],
|
||||
['root' => '/core', 'name' => 'AppPassword#deleteAppPassword', 'url' => '/apppassword', 'verb' => 'DELETE'],
|
||||
|
||||
['root' => '/hovercard', 'name' => 'HoverCard#getUser', 'url' => '/v1/{userId}', 'verb' => 'GET'],
|
||||
|
||||
['root' => '/collaboration', 'name' => 'CollaborationResources#searchCollections', 'url' => '/resources/collections/search/{filter}', 'verb' => 'GET'],
|
||||
['root' => '/collaboration', 'name' => 'CollaborationResources#listCollection', 'url' => '/resources/collections/{collectionId}', 'verb' => 'GET'],
|
||||
['root' => '/collaboration', 'name' => 'CollaborationResources#renameCollection', 'url' => '/resources/collections/{collectionId}', 'verb' => 'PUT'],
|
||||
|
|
|
|||
|
|
@ -920,6 +920,7 @@ return array(
|
|||
'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php',
|
||||
'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php',
|
||||
'OC\\Core\\Controller\\GuestAvatarController' => $baseDir . '/core/Controller/GuestAvatarController.php',
|
||||
'OC\\Core\\Controller\\HoverCardController' => $baseDir . '/core/Controller/HoverCardController.php',
|
||||
'OC\\Core\\Controller\\JsController' => $baseDir . '/core/Controller/JsController.php',
|
||||
'OC\\Core\\Controller\\LoginController' => $baseDir . '/core/Controller/LoginController.php',
|
||||
'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php',
|
||||
|
|
|
|||
|
|
@ -949,6 +949,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php',
|
||||
'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php',
|
||||
'OC\\Core\\Controller\\GuestAvatarController' => __DIR__ . '/../../..' . '/core/Controller/GuestAvatarController.php',
|
||||
'OC\\Core\\Controller\\HoverCardController' => __DIR__ . '/../../..' . '/core/Controller/HoverCardController.php',
|
||||
'OC\\Core\\Controller\\JsController' => __DIR__ . '/../../..' . '/core/Controller/JsController.php',
|
||||
'OC\\Core\\Controller\\LoginController' => __DIR__ . '/../../..' . '/core/Controller/LoginController.php',
|
||||
'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php',
|
||||
|
|
|
|||
|
|
@ -29,26 +29,21 @@ use OCP\Contacts\ContactsMenu\ILinkAction;
|
|||
class ActionFactory implements IActionFactory {
|
||||
|
||||
/**
|
||||
* @param string $icon
|
||||
* @param string $name
|
||||
* @param string $href
|
||||
* @return ILinkAction
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function newLinkAction($icon, $name, $href) {
|
||||
public function newLinkAction(string $icon, string $name, string $href, string $appId = ''): ILinkAction {
|
||||
$action = new LinkAction();
|
||||
$action->setName($name);
|
||||
$action->setIcon($icon);
|
||||
$action->setHref($href);
|
||||
$action->setAppId($appId);
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $icon
|
||||
* @param string $name
|
||||
* @param string $email
|
||||
* @return ILinkAction
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function newEMailAction($icon, $name, $email) {
|
||||
return $this->newLinkAction($icon, $name, 'mailto:' . $email);
|
||||
public function newEMailAction(string $icon, string $name, string $email, string $appId = ''): ILinkAction {
|
||||
return $this->newLinkAction($icon, $name, 'mailto:' . $email, $appId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class LinkAction implements ILinkAction {
|
|||
/** @var int */
|
||||
private $priority = 10;
|
||||
|
||||
/** @var string */
|
||||
private $appId;
|
||||
|
||||
/**
|
||||
* @param string $icon absolute URI to an icon
|
||||
*/
|
||||
|
|
@ -87,6 +90,22 @@ class LinkAction implements ILinkAction {
|
|||
return $this->href;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @since 23.0.0
|
||||
*/
|
||||
public function setAppId(string $appId) {
|
||||
$this->appId = $appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 23.0.0
|
||||
*/
|
||||
public function getAppId(): string {
|
||||
return $this->appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
|
@ -95,6 +114,7 @@ class LinkAction implements ILinkAction {
|
|||
'title' => $this->name,
|
||||
'icon' => $this->icon,
|
||||
'hyperlink' => $this->href,
|
||||
'appId' => $this->appId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,9 +113,18 @@ class ContactsStore implements IContactsStore {
|
|||
$options
|
||||
);
|
||||
|
||||
$userId = $user->getUID();
|
||||
$contacts = array_filter($allContacts, function ($contact) use ($userId) {
|
||||
// When searching for multiple results, we strip out the current user
|
||||
if (array_key_exists('UID', $contact)) {
|
||||
return $contact['UID'] !== $userId;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$entries = array_map(function (array $contact) {
|
||||
return $this->contactArrayToEntry($contact);
|
||||
}, $allContacts);
|
||||
}, $contacts);
|
||||
return $this->filterContacts(
|
||||
$user,
|
||||
$entries,
|
||||
|
|
@ -125,12 +134,11 @@ class ContactsStore implements IContactsStore {
|
|||
|
||||
/**
|
||||
* Filters the contacts. Applied filters:
|
||||
* 1. filter the current user
|
||||
* 2. if the `shareapi_allow_share_dialog_user_enumeration` config option is
|
||||
* 1. if the `shareapi_allow_share_dialog_user_enumeration` config option is
|
||||
* enabled it will filter all local users
|
||||
* 3. if the `shareapi_exclude_groups` config option is enabled and the
|
||||
* 2. if the `shareapi_exclude_groups` config option is enabled and the
|
||||
* current user is in an excluded group it will filter all local users.
|
||||
* 4. if the `shareapi_only_share_with_group_members` config option is
|
||||
* 3. if the `shareapi_only_share_with_group_members` config option is
|
||||
* enabled it will filter all users which doens't have a common group
|
||||
* with the current user.
|
||||
*
|
||||
|
|
@ -171,10 +179,6 @@ class ContactsStore implements IContactsStore {
|
|||
$selfUID = $self->getUID();
|
||||
|
||||
return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
|
||||
if ($entry->getProperty('UID') === $selfUID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entry->getProperty('isLocalSystemBook')) {
|
||||
if ($skipLocal) {
|
||||
return false;
|
||||
|
|
@ -266,11 +270,7 @@ class ContactsStore implements IContactsStore {
|
|||
return null;
|
||||
}
|
||||
|
||||
$userId = $user->getUID();
|
||||
$allContacts = $this->contactsManager->search($shareWith, $filter);
|
||||
$contacts = array_filter($allContacts, function ($contact) use ($userId) {
|
||||
return $contact['UID'] !== $userId;
|
||||
});
|
||||
$contacts = $this->contactsManager->search($shareWith, $filter);
|
||||
$match = null;
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class EMailProvider implements IProvider {
|
|||
// Skip
|
||||
continue;
|
||||
}
|
||||
$action = $this->actionFactory->newEMailAction($iconUrl, $address, $address);
|
||||
$action = $this->actionFactory->newEMailAction($iconUrl, $address, $address, 'email');
|
||||
$entry->addAction($action);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class ProfileProvider implements IProvider {
|
|||
$iconUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/profile.svg'));
|
||||
$profileActionText = $this->l10nFactory->get('core')->t('View profile');
|
||||
$profileUrl = $this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId]);
|
||||
$action = $this->actionFactory->newLinkAction($iconUrl, $profileActionText, $profileUrl);
|
||||
$action = $this->actionFactory->newLinkAction($iconUrl, $profileActionText, $profileUrl, 'profile');
|
||||
// Set highest priority (by descending order), other actions have the default priority 10 as defined in lib/private/Contacts/ContactsMenu/Actions/LinkAction.php
|
||||
$action->setPriority(20);
|
||||
$entry->addAction($action);
|
||||
|
|
|
|||
|
|
@ -60,4 +60,16 @@ interface IAction extends JsonSerializable {
|
|||
* @since 12.0
|
||||
*/
|
||||
public function getPriority();
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @since 23.0.0
|
||||
*/
|
||||
public function setAppId(string $appId);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 23.0.0
|
||||
*/
|
||||
public function getAppId(): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ interface IActionFactory {
|
|||
* @param string $icon full path to the action's icon
|
||||
* @param string $name localized name of the action
|
||||
* @param string $href target URL
|
||||
* @param string $appId the app ID registering the action
|
||||
* @return ILinkAction
|
||||
*/
|
||||
public function newLinkAction($icon, $name, $href);
|
||||
public function newLinkAction(string $icon, string $name, string $href, string $appId = ''): ILinkAction;
|
||||
|
||||
/**
|
||||
* Construct and return a new email action for the contacts menu
|
||||
|
|
@ -47,7 +48,8 @@ interface IActionFactory {
|
|||
* @param string $icon full path to the action's icon
|
||||
* @param string $name localized name of the action
|
||||
* @param string $email target e-mail address
|
||||
* @param string $appId the appName registering the action
|
||||
* @return ILinkAction
|
||||
*/
|
||||
public function newEMailAction($icon, $name, $email);
|
||||
public function newEMailAction(string $icon, string $name, string $email, string $appId = ''): ILinkAction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,10 +75,29 @@ class LinkActionTest extends TestCase {
|
|||
$this->action->setName('Nickie Works');
|
||||
$this->action->setPriority(33);
|
||||
$this->action->setHref('example.com');
|
||||
$this->action->setAppId('contacts');
|
||||
$expected = [
|
||||
'title' => 'Nickie Works',
|
||||
'icon' => 'icon-contacts',
|
||||
'hyperlink' => 'example.com',
|
||||
'appId' => 'contacts',
|
||||
];
|
||||
|
||||
$json = $this->action->jsonSerialize();
|
||||
|
||||
$this->assertEquals($expected, $json);
|
||||
}
|
||||
|
||||
public function testJsonSerializeNoAppName() {
|
||||
$this->action->setIcon('icon-contacts');
|
||||
$this->action->setName('Nickie Works');
|
||||
$this->action->setPriority(33);
|
||||
$this->action->setHref('example.com');
|
||||
$expected = [
|
||||
'title' => 'Nickie Works',
|
||||
'icon' => 'icon-contacts',
|
||||
'hyperlink' => 'example.com',
|
||||
'appId' => '',
|
||||
];
|
||||
|
||||
$json = $this->action->jsonSerialize();
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class ContactsStoreTest extends TestCase {
|
|||
],
|
||||
],
|
||||
]);
|
||||
$user->expects($this->once())
|
||||
$user->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user123');
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ class ContactsStoreTest extends TestCase {
|
|||
],
|
||||
],
|
||||
]);
|
||||
$user->expects($this->once())
|
||||
$user->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user123');
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ class ContactsStoreTest extends TestCase {
|
|||
'PHOTO' => base64_encode('photophotophoto'),
|
||||
],
|
||||
]);
|
||||
$user->expects($this->once())
|
||||
$user->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user123');
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ class ContactsStoreTest extends TestCase {
|
|||
'PHOTO' => 'VALUE=uri:https://photo',
|
||||
],
|
||||
]);
|
||||
$user->expects($this->once())
|
||||
$user->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user123');
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -332,7 +332,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -411,7 +411,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -469,7 +469,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -555,7 +555,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -624,7 +624,7 @@ class ContactsStoreTest extends TestCase {
|
|||
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $currentUser */
|
||||
$currentUser = $this->createMock(IUser::class);
|
||||
$currentUser->expects($this->once())
|
||||
$currentUser->expects($this->exactly(2))
|
||||
->method('getUID')
|
||||
->willReturn('user001');
|
||||
|
||||
|
|
@ -963,9 +963,8 @@ class ContactsStoreTest extends TestCase {
|
|||
'isLocalSystemBook' => false
|
||||
],
|
||||
]);
|
||||
$user->expects($this->once())
|
||||
->method('getUID')
|
||||
->willReturn('user123');
|
||||
$user->expects($this->never())
|
||||
->method('getUID');
|
||||
|
||||
$entry = $this->contactsStore->findOne($user, 0, 'a567');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue