aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLukas Reschke <lukas@statuscode.ch>2017-04-26 18:31:17 +0200
committerGitHub <noreply@github.com>2017-04-26 18:31:17 +0200
commitd89c76049fbc7061bfa45e87c46f08802242d5f2 (patch)
tree0aa8bdbad51349e6e6081fd3fe24945950433e03
parentedd9444209ec8f0737821870fa9a25b7fe5dc0e6 (diff)
parent6bbc682c4b24212d36ef595d3692653dca1c67b1 (diff)
downloadnextcloud-server-d89c76049fbc7061bfa45e87c46f08802242d5f2.tar.gz
nextcloud-server-d89c76049fbc7061bfa45e87c46f08802242d5f2.zip
Merge pull request #4374 from nextcloud/contactsmenu_popover
add contacts popover
-rw-r--r--apps/comments/css/comments.css6
-rw-r--r--apps/comments/js/commentstabview.js20
-rw-r--r--apps/comments/tests/js/commentstabviewSpec.js2
-rw-r--r--core/Controller/ContactsMenuController.php17
-rw-r--r--core/css/share.scss19
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/jquery.contactsmenu.js117
-rw-r--r--core/js/merged-template-prepend.json3
-rw-r--r--core/js/sharedialogresharerinfoview.js5
-rw-r--r--core/js/sharedialogshareelistview.js15
-rw-r--r--core/js/tests/specs/jquery.contactsmenuSpec.js213
-rw-r--r--core/routes.php1
-rw-r--r--lib/private/Contacts/ContactsMenu/ContactsStore.php44
-rw-r--r--lib/private/Contacts/ContactsMenu/Manager.php17
-rw-r--r--tests/Core/Controller/ContactsMenuControllerTest.php31
-rw-r--r--tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php95
-rw-r--r--tests/lib/Contacts/ContactsMenu/ManagerTest.php45
17 files changed, 643 insertions, 8 deletions
diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css
index 796a550227b..2d794d52708 100644
--- a/apps/comments/css/comments.css
+++ b/apps/comments/css/comments.css
@@ -54,7 +54,6 @@
#commentsTabView .comment {
position: relative;
- z-index: 1;
margin-bottom: 30px;
}
@@ -108,6 +107,11 @@
vertical-align: middle;
}
+#commentsTabView .authorRow>div.hidden {
+ display: none !important;
+}
+
+#commentsTabView .comments li .message .avatar-name-wrapper,
#commentsTabView .comment .authorRow {
position: relative;
}
diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js
index 2256bea943e..ace0862ad2e 100644
--- a/apps/comments/js/commentstabview.js
+++ b/apps/comments/js/commentstabview.js
@@ -232,6 +232,21 @@
var $this = $(this);
$this.avatar($this.attr('data-username'), 32);
});
+
+ var username = $el.find('.avatar').data('username');
+ if (username !== oc_current_user) {
+ $el.find('.authorRow .avatar, .authorRow .author').contactsMenu(
+ username, 0, $el.find('.authorRow'));
+ }
+
+ var message = $el.find('.message');
+ message.find('.avatar').each(function() {
+ var avatar = $(this);
+ var strong = $(this).next();
+ var appendTo = $(this).parent();
+
+ $.merge(avatar, strong).contactsMenu(avatar.data('user'), 0, appendTo);
+ });
},
/**
@@ -251,7 +266,10 @@
// escape possible regex characters in the name
mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- var displayName = avatar + ' <strong>'+ _.escape(mentions[i].mentionDisplayName)+'</strong>';
+ var displayName = ''
+ + '<span class="avatar-name-wrapper">'
+ + avatar + ' <strong>'+ _.escape(mentions[i].mentionDisplayName)+'</strong>'
+ + '</span>';
// replace every mention either at the start of the input or after a whitespace
// followed by a non-word character.
diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js
index 0bbfaa1f295..c961548d806 100644
--- a/apps/comments/tests/js/commentstabviewSpec.js
+++ b/apps/comments/tests/js/commentstabviewSpec.js
@@ -153,7 +153,7 @@ describe('OCA.Comments.CommentsTabView tests', function() {
expect($comment.find('strong:first').text()).toEqual('Thane of Cawdor');
expect($comment.find('.avatar[data-user=banquo]').length).toEqual(1);
- expect($comment.find('strong:last-child').text()).toEqual('Lord Banquo');
+ expect($comment.find('.avatar-name-wrapper:last-child strong').text()).toEqual('Lord Banquo');
});
});
diff --git a/core/Controller/ContactsMenuController.php b/core/Controller/ContactsMenuController.php
index b0e0e0c6a77..bbb990f1a4f 100644
--- a/core/Controller/ContactsMenuController.php
+++ b/core/Controller/ContactsMenuController.php
@@ -26,6 +26,7 @@ namespace OC\Core\Controller;
use OC\Contacts\ContactsMenu\Manager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
@@ -59,4 +60,20 @@ class ContactsMenuController extends Controller {
return $this->manager->getEntries($this->userSession->getUser(), $filter);
}
+ /**
+ * @NoAdminRequired
+ *
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return JSONResponse
+ */
+ public function findOne($shareType, $shareWith) {
+ $contact = $this->manager->findOne($this->userSession->getUser(), $shareType, $shareWith);
+
+ if ($contact) {
+ return $contact;
+ } else {
+ return new JSONResponse([], Http::STATUS_NOT_FOUND);
+ }
+ }
}
diff --git a/core/css/share.scss b/core/css/share.scss
index 552e20c80cc..2e1c99b6f41 100644
--- a/core/css/share.scss
+++ b/core/css/share.scss
@@ -87,6 +87,7 @@
list-style-type: none;
padding: 8px;
> li {
+ position: relative;
padding-top: 10px;
padding-bottom: 10px;
font-weight: bold;
@@ -103,6 +104,7 @@
padding: 3px 6px;
}
}
+
.shareOption {
white-space: nowrap;
display: inline-block;
@@ -159,6 +161,10 @@ a {
padding: 6px 4px;
}
+.resharerInfoView.subView {
+ position: relative;
+}
+
#defaultExpireMessage, .reshare {
/* fix shared by text going out of box */
white-space: normal;
@@ -185,6 +191,19 @@ a {
color: rgba($color-main-text, .4);
}
+.contactsmenu-popover {
+ left: -8px;
+ right: auto;
+ padding: 3px 6px;
+ li.hidden {
+ display: none !important;
+ }
+ &:after {
+ left: 8px;
+ right: auto;
+ }
+}
+
.popovermenu .datepicker {
margin-left: 35px;
}
diff --git a/core/js/core.json b/core/js/core.json
index aadd66a0558..15e406bf2d2 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -20,6 +20,7 @@
"libraries": [
"jquery-showpassword.js",
"jquery.avatar.js",
+ "jquery.contactsmenu.js",
"placeholder.js"
],
"modules": [
diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js
new file mode 100644
index 00000000000..1ea9f732f79
--- /dev/null
+++ b/core/js/jquery.contactsmenu.js
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+(function ($) {
+ var ENTRY = ''
+ + '<li>'
+ + ' <a href="{{hyperlink}}">'
+ + ' {{#if icon}}<img src="{{icon}}">{{/if}}'
+ + ' <span>{{title}}</span>'
+ + ' </a>'
+ + '</li>';
+
+ var LIST = ''
+ + '<div class="menu popovermenu bubble hidden contactsmenu-popover">'
+ + ' <ul>'
+ + ' <li>'
+ + ' <a>'
+ + ' <span class="icon-loading-small"></span>'
+ + ' </a>'
+ + ' </li>'
+ + ' </ul>'
+ + '</div>';
+
+ $.fn.contactsMenu = function(shareWith, shareType, appendTo) {
+ // 0 - user, 4 - email, 6 - remote
+ var allowedTypes = [0, 4, 6];
+ if (allowedTypes.indexOf(shareType) === -1) {
+ return;
+ }
+
+ var $div = this;
+ appendTo.append(LIST);
+ var $list = appendTo.find('div.contactsmenu-popover');
+
+ $div.click(function() {
+ if (!$list.hasClass('hidden')) {
+ $list.addClass('hidden');
+ $list.hide();
+ return;
+ }
+
+ $list.removeClass('hidden');
+ $list.show();
+
+ if ($list.hasClass('loaded')) {
+ return;
+ }
+
+ $list.addClass('loaded');
+ $.ajax(OC.generateUrl('/contactsmenu/findOne'), {
+ method: 'POST',
+ data: {
+ shareType: shareType,
+ shareWith: shareWith
+ }
+ }).then(function(data) {
+ $list.find('ul').find('li').addClass('hidden');
+
+ var actions;
+ if (!data.topAction) {
+ actions = [{
+ hyperlink: '#',
+ title: t('core', 'No action available')
+ }];
+ } else {
+ actions = [data.topAction].concat(data.actions);
+ }
+
+ actions.forEach(function(action) {
+ var template = Handlebars.compile(ENTRY);
+ $list.find('ul').append(template(action));
+ });
+
+ if (actions.length === 0) {
+
+ }
+ }, function(jqXHR) {
+ $list.find('ul').find('li').addClass('hidden');
+
+ var title;
+ if (jqXHR.status === 404) {
+ title = t('core', 'No action available');
+ } else {
+ title = t('core', 'Error fetching contact actions');
+ }
+
+ var template = Handlebars.compile(ENTRY);
+ $list.find('ul').append(template({
+ hyperlink: '#',
+ title: title
+ }));
+ });
+ });
+
+ $(document).click(function(event) {
+ var clickedList = ($list.has(event.target).length > 0);
+ var clickedTarget = ($div.has(event.target).length > 0);
+
+ $div.each(function() {
+ if ($(this).is(event.target)) {
+ clickedTarget = true;
+ }
+ });
+
+ if (clickedList || clickedTarget) {
+ return;
+ }
+
+ $list.addClass('hidden');
+ $list.hide();
+ });
+ };
+}(jQuery));
diff --git a/core/js/merged-template-prepend.json b/core/js/merged-template-prepend.json
index 12b7ca8faa3..0dd6bed5329 100644
--- a/core/js/merged-template-prepend.json
+++ b/core/js/merged-template-prepend.json
@@ -13,5 +13,6 @@
"mimetypelist.js",
"oc-backbone.js",
"placeholder.js",
- "jquery.avatar.js"
+ "jquery.avatar.js",
+ "jquery.contactsmenu.js"
]
diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js
index a82b495bdcc..201484c52a8 100644
--- a/core/js/sharedialogresharerinfoview.js
+++ b/core/js/sharedialogresharerinfoview.js
@@ -100,6 +100,11 @@
$this.avatar($this.data('username'), 32);
});
+ this.$el.find('.reshare').contactsMenu(
+ this.model.getReshareOwner(),
+ OC.Share.SHARE_TYPE_USER,
+ this.$el);
+
return this;
},
diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js
index 3a481e53dde..f513eb75848 100644
--- a/core/js/sharedialogshareelistview.js
+++ b/core/js/sharedialogshareelistview.js
@@ -26,7 +26,7 @@
'{{#each sharees}}' +
'<li data-share-id="{{shareId}}" data-share-type="{{shareType}}" data-share-with="{{shareWith}}">' +
'<div class="avatar {{#if modSeed}}imageplaceholderseed{{/if}}" data-username="{{shareWith}}" data-displayname="{{shareWithDisplayName}}" {{#if modSeed}}data-seed="{{shareWith}} {{shareType}}"{{/if}}></div>' +
- '<span class="has-tooltip username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span>' +
+ '<span class="username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span>' +
'<span class="sharingOptionsGroup">' +
'{{#if editPermissionPossible}}' +
'<span class="shareOption">' +
@@ -361,6 +361,15 @@
this.$('.has-tooltip').tooltip({
placement: 'bottom'
});
+
+ this.$('ul.shareWithList > li').each(function() {
+ var $this = $(this);
+
+ var shareWith = $this.data('share-with');
+ var shareType = $this.data('share-type');
+
+ $this.find('div.avatar, span.username').contactsMenu(shareWith, shareType, $this);
+ })
} else {
var permissionChangeShareId = parseInt(this._renderPermissionChange, 10);
var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId);
@@ -399,7 +408,7 @@
var shareId = parseInt(this._menuOpen, 10);
if(!_.isNaN(shareId)) {
var liSelector = 'li[data-share-id=' + shareId + ']';
- OC.showMenu(null, this.$(liSelector + ' .popovermenu'));
+ OC.showMenu(null, this.$(liSelector + '.sharingOptionsGroup .popovermenu'));
}
}
@@ -476,7 +485,7 @@
event.stopPropagation();
var $element = $(event.target);
var $li = $element.closest('li[data-share-id]');
- var $menu = $li.find('.popovermenu');
+ var $menu = $li.find('.sharingOptionsGroup .popovermenu');
OC.showMenu(null, $menu);
this._menuOpen = $li.data('share-id');
diff --git a/core/js/tests/specs/jquery.contactsmenuSpec.js b/core/js/tests/specs/jquery.contactsmenuSpec.js
new file mode 100644
index 00000000000..7287648f5a2
--- /dev/null
+++ b/core/js/tests/specs/jquery.contactsmenuSpec.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('jquery.contactsMenu tests', function() {
+
+ var $selector1, $selector2, $appendTo;
+
+ beforeEach(function() {
+ $('#testArea').append($('<div id="selector1">'));
+ $('#testArea').append($('<div id="selector2">'));
+ $('#testArea').append($('<div id="appendTo">'));
+ $selector1 = $('#selector1');
+ $selector2 = $('#selector2');
+ $appendTo = $('#appendTo');
+ });
+
+ afterEach(function() {
+ $selector1.remove();
+ $selector2.remove();
+ $appendTo.remove();
+ });
+
+ describe('shareType', function() {
+ it('stops if type not supported', function() {
+ $selector1.contactsMenu('user', 1, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 2, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 3, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+
+ $selector1.contactsMenu('user', 5, $appendTo);
+ expect($appendTo.children().length).toEqual(0);
+ });
+
+ it('append list if shareType supported', function() {
+ $selector1.contactsMenu('user', 0, $appendTo);
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble hidden contactsmenu-popover"> <ul> <li> <a> <span class="icon-loading-small"></span> </a> </li> </ul></div>');
+ });
+ });
+
+ describe('open on click', function() {
+ it('with one selector', function() {
+ $selector1.contactsMenu('user', 0, $appendTo);
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it('with multiple selectors - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it('with multiple selectors - 2', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true);
+ $selector2.click();
+ expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false);
+ });
+
+ it ('should close when clicking the selector again - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+
+ it ('should close when clicking the selector again - 1', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.children().length).toEqual(1);
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $selector2.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+ });
+
+ describe('send requests to the server and render', function() {
+ it('load a topaction only', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": {
+ "title": "bar@baz.wtf",
+ "icon": "foo.svg",
+ "hyperlink": "mailto:bar%40baz.wtf"},
+ "actions": []
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="mailto:bar%40baz.wtf"> <img src="foo.svg"> <span>bar@baz.wtf</span> </a></li></ul></div>');
+ });
+
+ it('load topaction and more actions', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": {
+ "title": "bar@baz.wtf",
+ "icon": "foo.svg",
+ "hyperlink": "mailto:bar%40baz.wtf"},
+ "actions": [{
+ "title": "Details",
+ "icon": "details.svg",
+ "hyperlink": "http:\/\/localhost\/index.php\/apps\/contacts"
+ }]
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="mailto:bar%40baz.wtf"> <img src="foo.svg"> <span>bar@baz.wtf</span> </a></li><li> <a href="http://localhost/index.php/apps/contacts"> <img src="details.svg"> <span>Details</span> </a></li></ul></div>');
+ });
+
+ it('load no actions', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify({
+ "id": null,
+ "fullName": "Name 123",
+ "topAction": null,
+ "actions": []
+ })
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>No action available</span> </a></li></ul></div>');
+ });
+
+ it('should throw an error', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify([])
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>Error fetching contact actions</span> </a></li></ul></div>');
+ });
+
+ it('should handle 404', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+ $selector1.click();
+
+ fakeServer.requests[0].respond(
+ 404,
+ { 'Content-Type': 'application/json; charset=utf-8' },
+ JSON.stringify([])
+ );
+ expect(fakeServer.requests[0].method).toEqual('POST');
+ expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne');
+
+ expect($appendTo.html()).toEqual('<div class="menu popovermenu bubble contactsmenu-popover loaded" style="display: block;"> <ul> <li class="hidden"> <a> <span class="icon-loading-small"></span> </a> </li> <li> <a href="#"> <span>No action available</span> </a></li></ul></div>');
+ });
+ });
+
+ it('click anywhere else to close the menu', function() {
+ $('#selector1, #selector2').contactsMenu('user', 0, $appendTo);
+
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ $selector1.click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(false);
+ $(document).click();
+ expect($appendTo.find('div').hasClass('hidden')).toEqual(true);
+ });
+});
diff --git a/core/routes.php b/core/routes.php
index 37db2642c1b..c167dad2f9f 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -61,6 +61,7 @@ $application->registerRoutes($this, [
['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'],
['name' => 'Js#getJs', 'url' => '/js/{appName}/{fileName}', 'verb' => 'GET'],
['name' => 'contactsMenu#index', 'url' => '/contactsmenu/contacts', 'verb' => 'POST'],
+ ['name' => 'contactsMenu#findOne', 'url' => '/contactsmenu/findOne', 'verb' => 'POST'],
],
'ocs' => [
['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],
diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php
index 1cdb5d6fc5f..40a0bf87031 100644
--- a/lib/private/Contacts/ContactsMenu/ContactsStore.php
+++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php
@@ -60,6 +60,50 @@ class ContactsStore {
}
/**
+ * @param IUser $user
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return IEntry|null
+ */
+ public function findOne(IUser $user, $shareType, $shareWith) {
+ switch($shareType) {
+ case 0:
+ case 6:
+ $filter = ['UID'];
+ break;
+ case 4:
+ $filter = ['EMAIL'];
+ break;
+ default:
+ return null;
+ }
+
+ $userId = $user->getUID();
+ $allContacts = $this->contactsManager->search($shareWith, $filter);
+ $contacts = array_filter($allContacts, function($contact) use ($userId) {
+ return $contact['UID'] !== $userId;
+ });
+ $match = null;
+
+ foreach ($contacts as $contact) {
+ if ($shareType === 4 && isset($contact['EMAIL'])) {
+ if (in_array($shareWith, $contact['EMAIL'])) {
+ $match = $contact;
+ break;
+ }
+ }
+ if ($shareType === 0 || $shareType === 6) {
+ if ($contact['UID'] === $shareWith && $contact['isLocalSystemBook'] === true) {
+ $match = $contact;
+ break;
+ }
+ }
+ }
+
+ return $match ? $this->contactArrayToEntry($match) : null;
+ }
+
+ /**
* @param array $contact
* @return Entry
*/
diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php
index 16d77c2df08..766b4623253 100644
--- a/lib/private/Contacts/ContactsMenu/Manager.php
+++ b/lib/private/Contacts/ContactsMenu/Manager.php
@@ -51,7 +51,7 @@ class Manager {
}
/**
- * @param string $user
+ * @param IUser $user
* @param string $filter
* @return array
*/
@@ -70,6 +70,21 @@ class Manager {
}
/**
+ * @param IUser $user
+ * @param integer $shareType
+ * @param string $shareWith
+ * @return IEntry
+ */
+ public function findOne(IUser $user, $shareType, $shareWith) {
+ $entry = $this->store->findOne($user, $shareType, $shareWith);
+ if ($entry) {
+ $this->processEntries([$entry], $user);
+ }
+
+ return $entry;
+ }
+
+ /**
* @param IEntry[] $entries
* @return IEntry[]
*/
diff --git a/tests/Core/Controller/ContactsMenuControllerTest.php b/tests/Core/Controller/ContactsMenuControllerTest.php
index bf6188e9097..92a185cf2ad 100644
--- a/tests/Core/Controller/ContactsMenuControllerTest.php
+++ b/tests/Core/Controller/ContactsMenuControllerTest.php
@@ -76,4 +76,35 @@ class ContactsMenuControllerTest extends TestCase {
$this->assertEquals($entries, $response);
}
+ public function testFindOne() {
+ $user = $this->createMock(IUser::class);
+ $entry = $this->createMock(IEntry::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->contactsManager->expects($this->once())
+ ->method('findOne')
+ ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase'))
+ ->willReturn($entry);
+
+ $response = $this->controller->findOne(42, 'test-search-phrase');
+
+ $this->assertEquals($entry, $response);
+ }
+
+ public function testFindOne404() {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->contactsManager->expects($this->once())
+ ->method('findOne')
+ ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase'))
+ ->willReturn(null);
+
+ $response = $this->controller->findOne(42, 'test-search-phrase');
+
+ $this->assertEquals([], $response->getData());
+ $this->assertEquals(404, $response->getStatus());
+ }
}
diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
index 80c26a9078e..08da360388f 100644
--- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
+++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php
@@ -157,4 +157,99 @@ class ContactsStoreTest extends TestCase {
$this->assertEquals('https://photo', $entries[1]->getAvatar());
}
+ public function testFindOneUser() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('a567'), $this->equalTo(['UID']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au'
+ ],
+ 'isLocalSystemBook' => true
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 0, 'a567');
+
+ $this->assertEquals([
+ 'darren@roner.au'
+ ], $entry->getEMailAddresses());
+ }
+
+ public function testFindOneEMail() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('darren@roner.au'), $this->equalTo(['EMAIL']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au'
+ ],
+ 'isLocalSystemBook' => false
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 4, 'darren@roner.au');
+
+ $this->assertEquals([
+ 'darren@roner.au'
+ ], $entry->getEMailAddresses());
+ }
+
+ public function testFindOneNotSupportedType() {
+ $user = $this->createMock(IUser::class);
+
+ $entry = $this->contactsStore->findOne($user, 42, 'darren@roner.au');
+
+ $this->assertEquals(null, $entry);
+ }
+
+ public function testFindOneNoMatches() {
+ $user = $this->createMock(IUser::class);
+ $this->contactsManager->expects($this->once())
+ ->method('search')
+ ->with($this->equalTo('a567'), $this->equalTo(['UID']))
+ ->willReturn([
+ [
+ 'UID' => 123,
+ 'isLocalSystemBook' => false
+ ],
+ [
+ 'UID' => 'a567',
+ 'FN' => 'Darren Roner',
+ 'EMAIL' => [
+ 'darren@roner.au123'
+ ],
+ 'isLocalSystemBook' => false
+ ],
+ ]);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('user123');
+
+ $entry = $this->contactsStore->findOne($user, 0, 'a567');
+
+ $this->assertEquals(null, $entry);
+ }
}
diff --git a/tests/lib/Contacts/ContactsMenu/ManagerTest.php b/tests/lib/Contacts/ContactsMenu/ManagerTest.php
index 9c92ec54b9f..783e5590a29 100644
--- a/tests/lib/Contacts/ContactsMenu/ManagerTest.php
+++ b/tests/lib/Contacts/ContactsMenu/ManagerTest.php
@@ -99,4 +99,49 @@ class ManagerTest extends TestCase {
$this->assertEquals($expected, $data);
}
+ public function testFindOne() {
+ $shareTypeFilter = 42;
+ $shareWithFilter = 'foobar';
+
+ $user = $this->createMock(IUser::class);
+ $entry = current($this->generateTestEntries());
+ $provider = $this->createMock(IProvider::class);
+ $this->contactsStore->expects($this->once())
+ ->method('findOne')
+ ->with($user, $shareTypeFilter, $shareWithFilter)
+ ->willReturn($entry);
+ $this->actionProviderStore->expects($this->once())
+ ->method('getProviders')
+ ->with($user)
+ ->willReturn([$provider]);
+ $provider->expects($this->once())
+ ->method('process');
+
+ $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter);
+
+ $this->assertEquals($entry, $data);
+ }
+
+ public function testFindOne404() {
+ $shareTypeFilter = 42;
+ $shareWithFilter = 'foobar';
+
+ $user = $this->createMock(IUser::class);
+ $provider = $this->createMock(IProvider::class);
+ $this->contactsStore->expects($this->once())
+ ->method('findOne')
+ ->with($user, $shareTypeFilter, $shareWithFilter)
+ ->willReturn(null);
+ $this->actionProviderStore->expects($this->never())
+ ->method('getProviders')
+ ->with($user)
+ ->willReturn([$provider]);
+ $provider->expects($this->never())
+ ->method('process');
+
+ $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter);
+
+ $this->assertEquals(null, $data);
+ }
+
}