From e1073cf442613ac92878c8ded30a33db35b30e14 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 May 2016 10:02:07 +0200 Subject: Notificacations for simple @-mentioning in comments (WIP) notify user when mentioned in comments Fix doc, and create absolute URL for as notification link. PSR-4 compatibility changes also move notification creation to comments app Do not notify yourself unit test for controller and application smaller fixes - translatable app name - remove doubles in mention array - micro perf optimization - display name: special label for deleted users, keep user id for users that could not be fetched from userManager Comment Notification-Listener Unit Test fix email adresses remove notification when triggering comment was deleted add and adjust tests add missing @license tags simplify NotificationsController registration appinfo simplification, php docs make string easier to translate adjust test replace dispatcher-based listeners with a registration method and interface safer to not pass optional data parameter to setSubject for marking as processed. ID and mention suffices Signed-off-by: Arthur Schiwon update comment Signed-off-by: Arthur Schiwon --- .../DependencyInjection/DIContainer.php | 4 ++ lib/private/Comments/Manager.php | 79 ++++++++++++++++------ 2 files changed, 62 insertions(+), 21 deletions(-) (limited to 'lib/private') diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index b6f8d8f458d..3f2212a7f24 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -161,6 +161,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { return $this->getServer()->getRootFolder(); }); + $this->registerService('OCP\\Files\\Folder', function() { + return $this->getServer()->getUserFolder(); + }); + $this->registerService('OCP\\Http\\Client\\IClientService', function($c) { return $this->getServer()->getHTTPClientService(); }); diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 59678775d88..f7b23dd5f58 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -26,6 +26,7 @@ namespace OC\Comments; use Doctrine\DBAL\Exception\DriverException; use OCP\Comments\CommentsEvent; use OCP\Comments\IComment; +use OCP\Comments\ICommentsEventHandler; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -33,7 +34,6 @@ use OCP\IDBConnection; use OCP\IConfig; use OCP\ILogger; use OCP\IUser; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Manager implements ICommentsManager { @@ -46,30 +46,30 @@ class Manager implements ICommentsManager { /** @var IConfig */ protected $config; - /** @var EventDispatcherInterface */ - protected $dispatcher; - /** @var IComment[] */ protected $commentsCache = []; + /** @var \Closure[] */ + protected $eventHandlerClosures = []; + + /** @var ICommentsEventHandler[] */ + protected $eventHandlers = []; + /** * Manager constructor. * * @param IDBConnection $dbConn * @param ILogger $logger * @param IConfig $config - * @param EventDispatcherInterface $dispatcher */ public function __construct( IDBConnection $dbConn, ILogger $logger, - IConfig $config, - EventDispatcherInterface $dispatcher + IConfig $config ) { $this->dbConn = $dbConn; $this->logger = $logger; $this->config = $config; - $this->dispatcher = $dispatcher; } /** @@ -455,10 +455,7 @@ class Manager implements ICommentsManager { } if ($affectedRows > 0 && $comment instanceof IComment) { - $this->dispatcher->dispatch(CommentsEvent::EVENT_DELETE, new CommentsEvent( - CommentsEvent::EVENT_DELETE, - $comment - )); + $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment); } return ($affectedRows > 0); @@ -525,13 +522,9 @@ class Manager implements ICommentsManager { if ($affectedRows > 0) { $comment->setId(strval($qb->getLastInsertId())); + $this->sendEvent(CommentsEvent::EVENT_ADD, $comment); } - $this->dispatcher->dispatch(CommentsEvent::EVENT_ADD, new CommentsEvent( - CommentsEvent::EVENT_ADD, - $comment - )); - return $affectedRows > 0; } @@ -565,10 +558,7 @@ class Manager implements ICommentsManager { throw new NotFoundException('Comment to update does ceased to exist'); } - $this->dispatcher->dispatch(CommentsEvent::EVENT_UPDATE, new CommentsEvent( - CommentsEvent::EVENT_UPDATE, - $comment - )); + $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment); return $affectedRows > 0; } @@ -751,4 +741,51 @@ class Manager implements ICommentsManager { } return ($affectedRows > 0); } + + /** + * registers an Entity to the manager, so event notifications can be send + * to consumers of the comments infrastructure + * + * @param \Closure $closure + */ + public function registerEventHandler(\Closure $closure) { + $this->eventHandlerClosures[] = $closure; + $this->eventHandlers = []; + } + + /** + * returns valid, registered entities + * + * @return \OCP\Comments\ICommentsEventHandler[] + */ + private function getEventHandlers() { + if(!empty($this->eventHandlers)) { + return $this->eventHandlers; + } + + $this->eventHandlers = []; + foreach ($this->eventHandlerClosures as $name => $closure) { + $entity = $closure(); + if (!($entity instanceof ICommentsEventHandler)) { + throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface'); + } + $this->eventHandlers[$name] = $entity; + } + + return $this->eventHandlers; + } + + /** + * sends notifications to the registered entities + * + * @param $eventType + * @param IComment $comment + */ + private function sendEvent($eventType, IComment $comment) { + $entities = $this->getEventHandlers(); + $event = new CommentsEvent($eventType, $comment); + foreach ($entities as $entity) { + $entity->handle($event); + } + } } -- cgit v1.2.3 From 1bcd2ca8e35dca6e68e5f06506ade0a78a2beae8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 12 Oct 2016 18:06:22 +0200 Subject: emit pre-update event for comments * notifications can be cleaned up, no polluted DB * updating comments will re-notify users or remove notifications, depending on the message Signed-off-by: Arthur Schiwon --- apps/comments/lib/EventHandler.php | 27 +++++++++++----- apps/comments/lib/Notification/Listener.php | 4 ++- apps/comments/tests/Unit/EventHandlerTest.php | 37 ++-------------------- .../tests/Unit/Notification/ListenerTest.php | 2 ++ apps/dav/lib/Comments/CommentNode.php | 2 +- lib/private/Comments/Manager.php | 6 ++++ lib/public/Comments/CommentsEvent.php | 7 ++-- tests/lib/Comments/ManagerTest.php | 4 +-- 8 files changed, 40 insertions(+), 49 deletions(-) (limited to 'lib/private') diff --git a/apps/comments/lib/EventHandler.php b/apps/comments/lib/EventHandler.php index 6692ccc520d..a5f312617e5 100644 --- a/apps/comments/lib/EventHandler.php +++ b/apps/comments/lib/EventHandler.php @@ -57,14 +57,29 @@ class EventHandler implements ICommentsEventHandler { if( $eventType === CommentsEvent::EVENT_ADD && $event instanceof CommentsEvent ) { - $this->onAdd($event); + $this->notificationHandler($event); + $this->activityHandler($event); + return; + } + + if( $eventType === CommentsEvent::EVENT_PRE_UPDATE + && $event instanceof CommentsEvent + ) { + $this->notificationHandler($event); + return; + } + + if( $eventType === CommentsEvent::EVENT_UPDATE + && $event instanceof CommentsEvent + ) { + $this->notificationHandler($event); return; } if( $eventType === CommentsEvent::EVENT_DELETE && $event instanceof CommentsEvent ) { - $this->onDelete($event); + $this->notificationHandler($event); return; } } @@ -72,13 +87,9 @@ class EventHandler implements ICommentsEventHandler { /** * @param CommentsEvent $event */ - private function onAdd(CommentsEvent $event) { + private function activityHandler(CommentsEvent $event) { $c = $this->app->getContainer(); - /** @var NotificationListener $notificationListener */ - $notificationListener = $c->query(NotificationListener::class); - $notificationListener->evaluate($event); - /** @var ActivityListener $listener */ $activityListener = $c->query(ActivityListener::class); $activityListener->commentEvent($event); @@ -87,7 +98,7 @@ class EventHandler implements ICommentsEventHandler { /** * @param CommentsEvent $event */ - private function onDelete(CommentsEvent $event) { + private function notificationHandler(CommentsEvent $event) { $c = $this->app->getContainer(); /** @var NotificationListener $notificationListener */ diff --git a/apps/comments/lib/Notification/Listener.php b/apps/comments/lib/Notification/Listener.php index 5e979fd9bf0..68705085023 100644 --- a/apps/comments/lib/Notification/Listener.php +++ b/apps/comments/lib/Notification/Listener.php @@ -85,7 +85,9 @@ class Listener { } $notification->setUser($user); - if($event->getEvent() === CommentsEvent::EVENT_DELETE) { + if( $event->getEvent() === CommentsEvent::EVENT_DELETE + || $event->getEvent() === CommentsEvent::EVENT_PRE_UPDATE) + { $this->notificationManager->markProcessed($notification); } else { $this->notificationManager->notify($notification); diff --git a/apps/comments/tests/Unit/EventHandlerTest.php b/apps/comments/tests/Unit/EventHandlerTest.php index 21b701ea8cc..f377c01b3c9 100644 --- a/apps/comments/tests/Unit/EventHandlerTest.php +++ b/apps/comments/tests/Unit/EventHandlerTest.php @@ -69,42 +69,11 @@ class EventHandlerTest extends TestCase { $this->eventHandler->handle($event); } - public function notHandledProvider() { - return [ - [CommentsEvent::EVENT_UPDATE] - ]; - } - - /** - * @dataProvider notHandledProvider - * @param string $eventType - */ - public function testNotHandled($eventType) { - /** @var IComment|\PHPUnit_Framework_MockObject_MockObject $comment */ - $comment = $this->getMockBuilder(IComment::class)->getMock(); - $comment->expects($this->once()) - ->method('getObjectType') - ->willReturn('files'); - - /** @var CommentsEvent|\PHPUnit_Framework_MockObject_MockObject $event */ - $event = $this->getMockBuilder(CommentsEvent::class) - ->disableOriginalConstructor() - ->getMock(); - $event->expects($this->once()) - ->method('getComment') - ->willReturn($comment); - $event->expects($this->once()) - ->method('getEvent') - ->willReturn($eventType); - - // further processing does not happen, because $event methods cannot be - // access more than once. - $this->eventHandler->handle($event); - } - public function handledProvider() { return [ [CommentsEvent::EVENT_DELETE], + [CommentsEvent::EVENT_UPDATE], + [CommentsEvent::EVENT_PRE_UPDATE], [CommentsEvent::EVENT_ADD] ]; } @@ -152,7 +121,7 @@ class EventHandlerTest extends TestCase { ->withConsecutive([NotificationListener::class], [ActivityListener::class]) ->willReturnOnConsecutiveCalls($notificationListener, $activityListener); - $this->app->expects($this->once()) + $this->app->expects($this->atLeastOnce()) ->method('getContainer') ->willReturn($c); diff --git a/apps/comments/tests/Unit/Notification/ListenerTest.php b/apps/comments/tests/Unit/Notification/ListenerTest.php index 98a0375e49a..5926264fa08 100644 --- a/apps/comments/tests/Unit/Notification/ListenerTest.php +++ b/apps/comments/tests/Unit/Notification/ListenerTest.php @@ -60,6 +60,8 @@ class ListenerTest extends TestCase { public function eventProvider() { return [ [CommentsEvent::EVENT_ADD, 'notify'], + [CommentsEvent::EVENT_UPDATE, 'notify'], + [CommentsEvent::EVENT_PRE_UPDATE, 'markProcessed'], [CommentsEvent::EVENT_DELETE, 'markProcessed'] ]; } diff --git a/apps/dav/lib/Comments/CommentNode.php b/apps/dav/lib/Comments/CommentNode.php index 101e3d87721..f247921be79 100644 --- a/apps/dav/lib/Comments/CommentNode.php +++ b/apps/dav/lib/Comments/CommentNode.php @@ -173,7 +173,7 @@ class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties { * @param $propertyValue * @return bool * @throws BadRequest - * @throws Forbidden + * @throws \Exception */ public function updateComment($propertyValue) { $this->checkWriteAccessOnComment(); diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index f7b23dd5f58..b3ecab731e1 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -536,6 +536,12 @@ class Manager implements ICommentsManager { * @throws NotFoundException */ protected function update(IComment $comment) { + // for properly working preUpdate Events we need the old comments as is + // in the DB and overcome caching. Also avoid that outdated information stays. + $this->uncache($comment->getId()); + $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId())); + $this->uncache($comment->getId()); + $qb = $this->dbConn->getQueryBuilder(); $affectedRows = $qb ->update('comments') diff --git a/lib/public/Comments/CommentsEvent.php b/lib/public/Comments/CommentsEvent.php index a0bff349fb4..0d8a783c107 100644 --- a/lib/public/Comments/CommentsEvent.php +++ b/lib/public/Comments/CommentsEvent.php @@ -32,9 +32,10 @@ use Symfony\Component\EventDispatcher\Event; */ class CommentsEvent extends Event { - const EVENT_ADD = 'OCP\Comments\ICommentsManager::addComment'; - const EVENT_UPDATE = 'OCP\Comments\ICommentsManager::updateComment'; - const EVENT_DELETE = 'OCP\Comments\ICommentsManager::deleteComment'; + const EVENT_ADD = 'OCP\Comments\ICommentsManager::addComment'; + const EVENT_PRE_UPDATE = 'OCP\Comments\ICommentsManager::preUpdateComment'; + const EVENT_UPDATE = 'OCP\Comments\ICommentsManager::updateComment'; + const EVENT_DELETE = 'OCP\Comments\ICommentsManager::deleteComment'; /** @var string */ protected $event; diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php index 9c0f791c0ca..71c918af6c7 100644 --- a/tests/lib/Comments/ManagerTest.php +++ b/tests/lib/Comments/ManagerTest.php @@ -635,11 +635,11 @@ class ManagerTest extends TestCase { public function testSendEvent() { $handler1 = $this->getMockBuilder(ICommentsEventHandler::class)->getMock(); - $handler1->expects($this->exactly(3)) + $handler1->expects($this->exactly(4)) ->method('handle'); $handler2 = $this->getMockBuilder(ICommentsEventHandler::class)->getMock(); - $handler1->expects($this->exactly(3)) + $handler1->expects($this->exactly(4)) ->method('handle'); $manager = $this->getManager(); -- cgit v1.2.3