diff options
author | Arthur Schiwon <blizzz@owncloud.com> | 2016-05-09 10:02:07 +0200 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2016-10-07 17:11:19 +0200 |
commit | e1073cf442613ac92878c8ded30a33db35b30e14 (patch) | |
tree | 43bf514650829b6a4684cb564c62b3785f60b188 /apps/comments/lib | |
parent | 9e7824f3edcb498447915a396e93678ddb6f5768 (diff) | |
download | nextcloud-server-e1073cf442613ac92878c8ded30a33db35b30e14.tar.gz nextcloud-server-e1073cf442613ac92878c8ded30a33db35b30e14.zip |
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 <blizzz@arthur-schiwon.de>
update comment
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
Diffstat (limited to 'apps/comments/lib')
-rw-r--r-- | apps/comments/lib/AppInfo/Application.php | 35 | ||||
-rw-r--r-- | apps/comments/lib/Controller/Notifications.php | 138 | ||||
-rw-r--r-- | apps/comments/lib/EventHandler.php | 78 | ||||
-rw-r--r-- | apps/comments/lib/Notification/Listener.php | 130 | ||||
-rw-r--r-- | apps/comments/lib/Notification/Notifier.php | 115 |
5 files changed, 496 insertions, 0 deletions
diff --git a/apps/comments/lib/AppInfo/Application.php b/apps/comments/lib/AppInfo/Application.php new file mode 100644 index 00000000000..f168779cd0d --- /dev/null +++ b/apps/comments/lib/AppInfo/Application.php @@ -0,0 +1,35 @@ +<?php + +/** + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @license GNU AGPL version 3 or any later version + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\Comments\AppInfo; + +use OCA\Comments\Controller\Notifications; +use OCP\AppFramework\App; + +class Application extends App { + + public function __construct (array $urlParams = array()) { + parent::__construct('comments', $urlParams); + $container = $this->getContainer(); + + $container->registerAlias('NotificationsController', Notifications::class); + } +} diff --git a/apps/comments/lib/Controller/Notifications.php b/apps/comments/lib/Controller/Notifications.php new file mode 100644 index 00000000000..f76f1605345 --- /dev/null +++ b/apps/comments/lib/Controller/Notifications.php @@ -0,0 +1,138 @@ +<?php + +/** + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @license GNU AGPL version 3 or any later version + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Comments\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\RedirectResponse; +use OCP\AppFramework\Http\Response; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\Files\Folder; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\Notification\IManager; + +/** + * Class Notifications + * + * @package OCA\Comments\Controller + */ +class Notifications extends Controller { + /** @var Folder */ + protected $folder; + + /** @var ICommentsManager */ + protected $commentsManager; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var IManager */ + protected $notificationManager; + + /** @var IUserSession */ + protected $userSession; + + /** + * Notifications constructor. + * + * @param string $appName + * @param IRequest $request + * @param ICommentsManager $commentsManager + * @param Folder $folder + * @param IURLGenerator $urlGenerator + * @param IManager $notificationManager + * @param IUserSession $userSession + */ + public function __construct( + $appName, + IRequest $request, + ICommentsManager $commentsManager, + Folder $folder, + IURLGenerator $urlGenerator, + IManager $notificationManager, + IUserSession $userSession + ) { + parent::__construct($appName, $request); + $this->commentsManager = $commentsManager; + $this->folder = $folder; + $this->urlGenerator = $urlGenerator; + $this->notificationManager = $notificationManager; + $this->userSession = $userSession; + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $id the comment ID + * @return Response + */ + public function view($id) { + try { + $comment = $this->commentsManager->get($id); + if($comment->getObjectType() !== 'files') { + return new NotFoundResponse(); + } + $files = $this->folder->getById($comment->getObjectId()); + if(count($files) === 0) { + $this->markProcessed($comment); + return new NotFoundResponse(); + } + + $dir = $this->folder->getRelativePath($files[0]->getParent()->getPath()); + $url = $this->urlGenerator->linkToRouteAbsolute( + 'files.view.index', + [ + 'dir' => $dir, + 'scrollto' => $files[0]->getName() + ] + ); + + $this->markProcessed($comment); + + return new RedirectResponse($url); + } catch (\Exception $e) { + return new NotFoundResponse(); + } + } + + /** + * Marks the notification about a comment as processed + * @param IComment $comment + */ + protected function markProcessed(IComment $comment) { + $user = $this->userSession->getUser(); + if(is_null($user)) { + return; + } + $notification = $this->notificationManager->createNotification(); + $notification->setApp('comments') + ->setObject('comment', $comment->getId()) + ->setSubject('mention') + ->setUser($user->getUID()); + $this->notificationManager->markProcessed($notification); + } +} diff --git a/apps/comments/lib/EventHandler.php b/apps/comments/lib/EventHandler.php new file mode 100644 index 00000000000..a32bb8ffa71 --- /dev/null +++ b/apps/comments/lib/EventHandler.php @@ -0,0 +1,78 @@ +<?php + +/** + * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 OCA\Comments; + +use OCA\Comments\Activity\Listener as ActivityListener; +use OCA\Comments\AppInfo\Application; +use OCA\Comments\Notification\Listener as NotificationListener; +use OCP\Comments\CommentsEvent; +use OCP\Comments\ICommentsEventHandler; + +/** + * Class EventHandler + * + * @package OCA\Comments + */ +class EventHandler implements ICommentsEventHandler { + + /** @var Application */ + protected $app; + + public function __construct(Application $app) { + $this->app = $app; + } + + /** + * @param CommentsEvent $event + */ + public function handle(CommentsEvent $event) { + if($event->getComment()->getObjectType() !== 'files') { + // this is a 'files'-specific Handler + return; + } + + if( $event->getEvent() === CommentsEvent::EVENT_ADD + && $event instanceof CommentsEvent + ) { + $this->onAdd($event); + return; + } + } + + /** + * @param CommentsEvent $event + */ + private function onAdd(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); + } +} diff --git a/apps/comments/lib/Notification/Listener.php b/apps/comments/lib/Notification/Listener.php new file mode 100644 index 00000000000..5e979fd9bf0 --- /dev/null +++ b/apps/comments/lib/Notification/Listener.php @@ -0,0 +1,130 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Comments\Notification; + +use OCP\Comments\CommentsEvent; +use OCP\Comments\IComment; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\Notification\IManager; + +class Listener { + /** @var IManager */ + protected $notificationManager; + + /** @var IUserManager */ + protected $userManager; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** + * Listener constructor. + * + * @param IManager $notificationManager + * @param IUserManager $userManager + * @param IURLGenerator $urlGenerator + */ + public function __construct( + IManager $notificationManager, + IUserManager $userManager, + IURLGenerator $urlGenerator + ) { + + $this->notificationManager = $notificationManager; + $this->userManager = $userManager; + $this->urlGenerator = $urlGenerator; + } + + /** + * @param CommentsEvent $event + */ + public function evaluate(CommentsEvent $event) { + $comment = $event->getComment(); + + if($comment->getObjectType() !== 'files') { + // comments App serves files only, other object types/apps need to + // register their own ICommentsEventHandler and trigger notifications + return; + } + + $mentions = $this->extractMentions($comment->getMessage()); + if(empty($mentions)) { + // no one to notify + return; + } + + $notification = $this->instantiateNotification($comment); + + foreach($mentions as $mention) { + $user = substr($mention, 1); // @username → username + if( ($comment->getActorType() === 'users' && $user === $comment->getActorId()) + || !$this->userManager->userExists($user) + ) { + // do not notify unknown users or yourself + continue; + } + + $notification->setUser($user); + if($event->getEvent() === CommentsEvent::EVENT_DELETE) { + $this->notificationManager->markProcessed($notification); + } else { + $this->notificationManager->notify($notification); + } + } + } + + /** + * creates a notification instance and fills it with comment data + * + * @param IComment $comment + * @return \OCP\Notification\INotification + */ + public function instantiateNotification(IComment $comment) { + $notification = $this->notificationManager->createNotification(); + $notification + ->setApp('comments') + ->setObject('comment', $comment->getId()) + ->setSubject('mention', [ $comment->getObjectType(), $comment->getObjectId() ]) + ->setDateTime($comment->getCreationDateTime()) + ->setLink($this->urlGenerator->linkToRouteAbsolute( + 'comments.Notifications.view', + ['id' => $comment->getId()]) + ); + + return $notification; + } + + /** + * extracts @-mentions out of a message body. + * + * @param string $message + * @return string[] containing the mentions, e.g. ['@alice', '@bob'] + */ + public function extractMentions($message) { + $ok = preg_match_all('/\B@[a-z0-9_\-@\.\']+/i', $message, $mentions); + if(!$ok || !isset($mentions[0]) || !is_array($mentions[0])) { + return []; + } + return array_unique($mentions[0]); + } +} diff --git a/apps/comments/lib/Notification/Notifier.php b/apps/comments/lib/Notification/Notifier.php new file mode 100644 index 00000000000..df05f297301 --- /dev/null +++ b/apps/comments/lib/Notification/Notifier.php @@ -0,0 +1,115 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Comments\Notification; + +use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; +use OCP\Files\Folder; +use OCP\IUserManager; +use OCP\L10N\IFactory; +use OCP\Notification\INotification; +use OCP\Notification\INotifier; + +class Notifier implements INotifier { + + /** @var IFactory */ + protected $l10nFactory; + + /** @var Folder */ + protected $userFolder; + + /** @var ICommentsManager */ + protected $commentsManager; + + /** @var IUserManager */ + protected $userManager; + + public function __construct( + IFactory $l10nFactory, + Folder $userFolder, + ICommentsManager $commentsManager, + IUserManager $userManager + ) { + $this->l10nFactory = $l10nFactory; + $this->userFolder = $userFolder; + $this->commentsManager = $commentsManager; + $this->userManager = $userManager; + } + + /** + * @param INotification $notification + * @param string $languageCode The code of the language that should be used to prepare the notification + * @return INotification + * @throws \InvalidArgumentException When the notification was not prepared by a notifier + */ + public function prepare(INotification $notification, $languageCode) { + if($notification->getApp() !== 'comments') { + throw new \InvalidArgumentException(); + } + try { + $comment = $this->commentsManager->get($notification->getObjectId()); + } catch(NotFoundException $e) { + // needs to be converted to InvalidArgumentException, otherwise none Notifications will be shown at all + throw new \InvalidArgumentException('Comment not found', 0, $e); + } + $l = $this->l10nFactory->get('comments', $languageCode); + $displayName = $comment->getActorId(); + $isDeletedActor = $comment->getActorType() === ICommentsManager::DELETED_USER; + if($comment->getActorType() === 'users') { + $commenter = $this->userManager->get($comment->getActorId()); + if(!is_null($commenter)) { + $displayName = $commenter->getDisplayName(); + } + } + switch($notification->getSubject()) { + case 'mention': + $parameters = $notification->getSubjectParameters(); + if($parameters[0] !== 'files') { + throw new \InvalidArgumentException('Unsupported comment object'); + } + $nodes = $this->userFolder->getById($parameters[1]); + if(empty($nodes)) { + throw new \InvalidArgumentException('Cannot resolve file id to Node instance'); + } + $fileName = $nodes[0]->getName(); + if($isDeletedActor) { + $subject = (string) $l->t( + 'You were mentioned in a comment on "%s" by a now deleted user.', + [ $fileName ] + ); + } else { + $subject = (string) $l->t( + 'You were mentioned in a comment on "%s" by %s.', + [ $fileName, $displayName ] + ); + } + $notification->setParsedSubject($subject); + + return $notification; + break; + + default: + throw new \InvalidArgumentException('Invalid subject'); + } + + } +} |