From 5d98ab83e9c0b69acae7dc66c5ad9a4fe4af546c Mon Sep 17 00:00:00 2001
From: Arthur Schiwon <blizzz@arthur-schiwon.de>
Date: Sun, 16 Oct 2016 20:28:36 +0200
Subject: [PATCH] resolve displayname via manager and registerable resolvers

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
---
 apps/comments/appinfo/app.php                 | 11 +++
 apps/dav/lib/Comments/CommentNode.php         | 15 +++-
 .../tests/unit/Comments/CommentsNodeTest.php  | 23 +++++-
 lib/private/Comments/Manager.php              | 47 +++++++++++
 lib/public/Comments/ICommentsManager.php      | 28 +++++++
 tests/lib/Comments/ManagerTest.php            | 78 +++++++++++++++++++
 6 files changed, 198 insertions(+), 4 deletions(-)

diff --git a/apps/comments/appinfo/app.php b/apps/comments/appinfo/app.php
index 771b35d9c6a..66e60dbbd85 100644
--- a/apps/comments/appinfo/app.php
+++ b/apps/comments/appinfo/app.php
@@ -71,3 +71,14 @@ $commentsManager->registerEventHandler(function () {
 	$handler = $application->getContainer()->query(\OCA\Comments\EventHandler::class);
 	return $handler;
 });
+$commentsManager->registerDisplayNameResolver('user', function($id) {
+	$manager = \OC::$server->getUserManager();
+	$user = $manager->get($id);
+	if(is_null($user)) {
+		$l = \OC::$server->getL10N('comments');
+		$displayName = $l->t('Unknown user');
+	} else {
+		$displayName = $user->getDisplayName();
+	}
+	return $displayName;
+});
diff --git a/apps/dav/lib/Comments/CommentNode.php b/apps/dav/lib/Comments/CommentNode.php
index 2f4490ac08a..1fa8e057b99 100644
--- a/apps/dav/lib/Comments/CommentNode.php
+++ b/apps/dav/lib/Comments/CommentNode.php
@@ -45,6 +45,7 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
 	const PROPERTY_NAME_MENTION = '{http://owncloud.org/ns}mention';
 	const PROPERTY_NAME_MENTION_TYPE = '{http://owncloud.org/ns}mentionType';
 	const PROPERTY_NAME_MENTION_ID = '{http://owncloud.org/ns}mentionId';
+	const PROPERTY_NAME_MENTION_DISPLAYNAME = '{http://owncloud.org/ns}mentionDisplayName';
 
 	/** @var  IComment */
 	public $comment;
@@ -125,6 +126,7 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
 			self::PROPERTY_NAME_MENTION,
 			self::PROPERTY_NAME_MENTION_TYPE,
 			self::PROPERTY_NAME_MENTION_ID,
+			self::PROPERTY_NAME_MENTION_DISPLAYNAME,
 		];
 	}
 
@@ -283,10 +285,19 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties {
 	 */
 	protected function composeMentionsPropertyValue() {
 		return array_map(function($mention) {
+			try {
+				$displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']);
+			} catch (\OutOfBoundsException $e) {
+				$this->logger->logException($e);
+				// No displayname, upon client's discretion what to display.
+				$displayName = '';
+			}
+
 			return [
 				self::PROPERTY_NAME_MENTION => [
-					self::PROPERTY_NAME_MENTION_TYPE => $mention['type'],
-					self::PROPERTY_NAME_MENTION_ID   => $mention['id'],
+					self::PROPERTY_NAME_MENTION_TYPE        => $mention['type'],
+					self::PROPERTY_NAME_MENTION_ID          => $mention['id'],
+					self::PROPERTY_NAME_MENTION_DISPLAYNAME => $displayName,
 				]
 			];
 		}, $this->comment->getMentions());
diff --git a/apps/dav/tests/unit/Comments/CommentsNodeTest.php b/apps/dav/tests/unit/Comments/CommentsNodeTest.php
index d5a60051452..94eaea01d56 100644
--- a/apps/dav/tests/unit/Comments/CommentsNodeTest.php
+++ b/apps/dav/tests/unit/Comments/CommentsNodeTest.php
@@ -27,11 +27,14 @@ namespace OCA\DAV\Tests\unit\Comments;
 
 use OCA\DAV\Comments\CommentNode;
 use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
 use OCP\Comments\MessageTooLongException;
 
 class CommentsNodeTest extends \Test\TestCase {
 
+	/** @var  ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */
 	protected $commentsManager;
+
 	protected $comment;
 	protected $node;
 	protected $userManager;
@@ -374,8 +377,16 @@ class CommentsNodeTest extends \Test\TestCase {
 			$ns . 'childrenCount' => 3,
 			$ns . 'message' => 'such a nice file you have…',
 			$ns . 'mentions' => [
-				[ $ns . 'mention' => [ $ns . 'mentionType' => 'user', $ns . 'mentionId' => 'alice'] ],
-				[ $ns . 'mention' => [ $ns . 'mentionType' => 'user', $ns . 'mentionId' => 'bob'] ],
+				[ $ns . 'mention' => [
+					$ns . 'mentionType' => 'user',
+					$ns . 'mentionId' => 'alice',
+					$ns . 'mentionDisplayName' => 'Alice Al-Isson',
+				] ],
+				[ $ns . 'mention' => [
+					$ns . 'mentionType' => 'user',
+					$ns . 'mentionId' => 'bob',
+					$ns . 'mentionDisplayName' => 'Unknown user',
+				] ],
 			],
 			$ns . 'verb' => 'comment',
 			$ns . 'actorType' => 'users',
@@ -388,6 +399,14 @@ class CommentsNodeTest extends \Test\TestCase {
 			$ns . 'isUnread' => null,
 		];
 
+		$this->commentsManager->expects($this->exactly(2))
+			->method('resolveDisplayName')
+			->withConsecutive(
+				[$this->equalTo('user'), $this->equalTo('alice')],
+				[$this->equalTo('user'), $this->equalTo('bob')]
+			)
+			->willReturnOnConsecutiveCalls('Alice Al-Isson', 'Unknown user');
+
 		$this->comment->expects($this->once())
 			->method('getId')
 			->will($this->returnValue($expected[$ns . 'id']));
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index b3ecab731e1..001f4f9441c 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -55,6 +55,9 @@ class Manager implements ICommentsManager {
 	/** @var  ICommentsEventHandler[] */
 	protected $eventHandlers = [];
 
+	/** @var \Closure[] */
+	protected $displayNameResolvers = [];
+
 	/**
 	 * Manager constructor.
 	 *
@@ -759,6 +762,50 @@ class Manager implements ICommentsManager {
 		$this->eventHandlers = [];
 	}
 
+	/**
+	 * registers a method that resolves an ID to a display name for a given type
+	 *
+	 * @param string $type
+	 * @param \Closure $closure
+	 * @throws \OutOfBoundsException
+	 * @since 9.2.0
+	 *
+	 * Only one resolver shall be registered per type. Otherwise a
+	 * \OutOfBoundsException has to thrown.
+	 */
+	public function registerDisplayNameResolver($type, \Closure $closure) {
+		if(!is_string($type)) {
+			throw new \InvalidArgumentException('String expected.');
+		}
+		if(isset($this->displayNameResolvers[$type])) {
+			throw new \OutOfBoundsException('Displayname resolver for this type already registered');
+		}
+		$this->displayNameResolvers[$type] = $closure;
+	}
+
+	/**
+	 * resolves a given ID of a given Type to a display name.
+	 *
+	 * @param string $type
+	 * @param string $id
+	 * @return string
+	 * @throws \OutOfBoundsException
+	 * @since 9.2.0
+	 *
+	 * If a provided type was not registered, an \OutOfBoundsException shall
+	 * be thrown. It is upon the resolver discretion what to return of the
+	 * provided ID is unknown. It must be ensured that a string is returned.
+	 */
+	public function resolveDisplayName($type, $id) {
+		if(!is_string($type)) {
+			throw new \InvalidArgumentException('String expected.');
+		}
+		if(!isset($this->displayNameResolvers[$type])) {
+			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
+		}
+		return (string)$this->displayNameResolvers[$type]($id);
+	}
+
 	/**
 	 * returns valid, registered entities
 	 *
diff --git a/lib/public/Comments/ICommentsManager.php b/lib/public/Comments/ICommentsManager.php
index 98169fb335f..6a32cfd803d 100644
--- a/lib/public/Comments/ICommentsManager.php
+++ b/lib/public/Comments/ICommentsManager.php
@@ -246,4 +246,32 @@ interface ICommentsManager {
 	 */
 	public function registerEventHandler(\Closure $closure);
 
+	/**
+	 * registers a method that resolves an ID to a display name for a given type
+	 *
+	 * @param string $type
+	 * @param \Closure $closure
+	 * @throws \OutOfBoundsException
+	 * @since 9.2.0
+	 *
+	 * Only one resolver shall be registered per type. Otherwise a
+	 * \OutOfBoundsException has to thrown.
+	 */
+	public function registerDisplayNameResolver($type, \Closure $closure);
+
+	/**
+	 * resolves a given ID of a given Type to a display name.
+	 *
+	 * @param string $type
+	 * @param string $id
+	 * @return string
+	 * @throws \OutOfBoundsException
+	 * @since 9.2.0
+	 *
+	 * If a provided type was not registered, an \OutOfBoundsException shall
+	 * be thrown. It is upon the resolver discretion what to return of the
+	 * provided ID is unknown. It must be ensured that a string is returned.
+	 */
+	public function resolveDisplayName($type, $id);
+
 }
diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php
index 71c918af6c7..be466ec2843 100644
--- a/tests/lib/Comments/ManagerTest.php
+++ b/tests/lib/Comments/ManagerTest.php
@@ -664,4 +664,82 @@ class ManagerTest extends TestCase {
 		$manager->delete($comment->getId());
 	}
 
+	public function testResolveDisplayName() {
+		$manager = $this->getManager();
+
+		$planetClosure = function($name) {
+			return ucfirst($name);
+		};
+
+		$galaxyClosure = function($name) {
+			return strtoupper($name);
+		};
+
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+		$manager->registerDisplayNameResolver('galaxy', $galaxyClosure);
+
+		$this->assertSame('Neptune', $manager->resolveDisplayName('planet', 'neptune'));
+		$this->assertSame('SOMBRERO', $manager->resolveDisplayName('galaxy', 'sombrero'));
+	}
+
+	/**
+	 * @expectedException \OutOfBoundsException
+	 */
+	public function testRegisterResolverDuplicate() {
+		$manager = $this->getManager();
+
+		$planetClosure = function($name) {
+			return ucfirst($name);
+		};
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testRegisterResolverInvalidType() {
+		$manager = $this->getManager();
+
+		$planetClosure = function($name) {
+			return ucfirst($name);
+		};
+		$manager->registerDisplayNameResolver(1337, $planetClosure);
+	}
+
+	/**
+	 * @expectedException \OutOfBoundsException
+	 */
+	public function testResolveDisplayNameUnregisteredType() {
+		$manager = $this->getManager();
+
+		$planetClosure = function($name) {
+			return ucfirst($name);
+		};
+
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+		$manager->resolveDisplayName('galaxy', 'sombrero');
+	}
+
+	public function testResolveDisplayNameDirtyResolver() {
+		$manager = $this->getManager();
+
+		$planetClosure = function() { return null; };
+
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+		$this->assertTrue(is_string($manager->resolveDisplayName('planet', 'neptune')));
+	}
+
+	/**
+	 * @expectedException \InvalidArgumentException
+	 */
+	public function testResolveDisplayNameInvalidType() {
+		$manager = $this->getManager();
+
+		$planetClosure = function() { return null; };
+
+		$manager->registerDisplayNameResolver('planet', $planetClosure);
+		$this->assertTrue(is_string($manager->resolveDisplayName(1337, 'neptune')));
+	}
+
 }
-- 
GitLab