From ded63d21a84811c9e082c0fea0110a1b498265d6 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Tue, 29 Dec 2015 09:30:36 +0100 Subject: Send notifications on user mentions --- app/Api/Task.php | 4 +- app/Integration/BitbucketWebhook.php | 4 +- app/Integration/GithubWebhook.php | 4 +- app/Integration/GitlabWebhook.php | 2 +- app/Model/Action.php | 2 +- app/Model/Comment.php | 9 ++-- app/Model/Notification.php | 54 ++++++++----------- app/Model/ProjectPermission.php | 15 +++++- app/Model/Task.php | 1 + app/Model/TaskCreation.php | 11 ++-- app/Model/UserMention.php | 61 ++++++++++++++++++++++ app/Model/UserNotification.php | 19 +++---- app/Notification/Mail.php | 4 ++ app/ServiceProvider/ClassProvider.php | 1 + app/Subscriber/NotificationSubscriber.php | 41 +++++++++------ app/Template/notification/comment_user_mention.php | 7 +++ app/Template/notification/task_user_mention.php | 7 +++ 17 files changed, 172 insertions(+), 74 deletions(-) create mode 100644 app/Model/UserMention.php create mode 100644 app/Template/notification/comment_user_mention.php create mode 100644 app/Template/notification/task_user_mention.php (limited to 'app') diff --git a/app/Api/Task.php b/app/Api/Task.php index 4a7ee932..1491cd35 100644 --- a/app/Api/Task.php +++ b/app/Api/Task.php @@ -71,7 +71,7 @@ class Task extends Base { $this->checkProjectPermission($project_id); - if ($owner_id !== 0 && ! $this->projectPermission->isMember($project_id, $owner_id)) { + if ($owner_id !== 0 && ! $this->projectPermission->isAssignable($project_id, $owner_id)) { return false; } @@ -117,7 +117,7 @@ class Task extends Base return false; } - if ($owner_id !== null && ! $this->projectPermission->isMember($project_id, $owner_id)) { + if ($owner_id !== null && ! $this->projectPermission->isAssignable($project_id, $owner_id)) { return false; } diff --git a/app/Integration/BitbucketWebhook.php b/app/Integration/BitbucketWebhook.php index 97a39437..3814e35c 100644 --- a/app/Integration/BitbucketWebhook.php +++ b/app/Integration/BitbucketWebhook.php @@ -81,7 +81,7 @@ class BitbucketWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['actor']['username']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } @@ -213,7 +213,7 @@ class BitbucketWebhook extends \Kanboard\Core\Base return false; } - if (! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { return false; } diff --git a/app/Integration/GithubWebhook.php b/app/Integration/GithubWebhook.php index c8b53e37..6dd7a8d9 100644 --- a/app/Integration/GithubWebhook.php +++ b/app/Integration/GithubWebhook.php @@ -149,7 +149,7 @@ class GithubWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['comment']['user']['login']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } @@ -266,7 +266,7 @@ class GithubWebhook extends \Kanboard\Core\Base $user = $this->user->getByUsername($issue['assignee']['login']); $task = $this->taskFinder->getByReference($this->project_id, $issue['number']); - if (! empty($user) && ! empty($task) && $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! empty($task) && $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $event = array( 'project_id' => $this->project_id, 'task_id' => $task['id'], diff --git a/app/Integration/GitlabWebhook.php b/app/Integration/GitlabWebhook.php index 17b6da70..7ab4cedf 100644 --- a/app/Integration/GitlabWebhook.php +++ b/app/Integration/GitlabWebhook.php @@ -273,7 +273,7 @@ class GitlabWebhook extends \Kanboard\Core\Base if (! empty($task)) { $user = $this->user->getByUsername($payload['user']['username']); - if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) { + if (! empty($user) && ! $this->projectPermission->isAssignable($this->project_id, $user['id'])) { $user = array(); } diff --git a/app/Model/Action.php b/app/Model/Action.php index 289471f4..d3d18edb 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -427,7 +427,7 @@ class Action extends Base return $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false; case 'user_id': case 'owner_id': - return $this->projectPermission->isMember($project_id, $param['value']) ? $param['value'] : false; + return $this->projectPermission->isAssignable($project_id, $param['value']) ? $param['value'] : false; default: return $param['value']; } diff --git a/app/Model/Comment.php b/app/Model/Comment.php index f60a96e3..71e964dc 100644 --- a/app/Model/Comment.php +++ b/app/Model/Comment.php @@ -26,8 +26,9 @@ class Comment extends Base * * @var string */ - const EVENT_UPDATE = 'comment.update'; - const EVENT_CREATE = 'comment.create'; + const EVENT_UPDATE = 'comment.update'; + const EVENT_CREATE = 'comment.create'; + const EVENT_USER_MENTION = 'comment.user.mention'; /** * Get all comments for a given task @@ -110,7 +111,9 @@ class Comment extends Base $comment_id = $this->persist(self::TABLE, $values); if ($comment_id) { - $this->container['dispatcher']->dispatch(self::EVENT_CREATE, new CommentEvent(array('id' => $comment_id) + $values)); + $event = new CommentEvent(array('id' => $comment_id) + $values); + $this->dispatcher->dispatch(self::EVENT_CREATE, $event); + $this->userMention->fireEvents($values['comment'], self::EVENT_USER_MENTION, $event); } return $comment_id; diff --git a/app/Model/Notification.php b/app/Model/Notification.php index f1122993..87c1a796 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -74,6 +74,10 @@ class Notification extends Base return e('%s commented on the task #%d', $event_author, $event_data['task']['id']); case File::EVENT_CREATE: return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']); + case Task::EVENT_USER_MENTION: + return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']); + case Comment::EVENT_USER_MENTION: + return e('%s mentioned you in a comment on the task #%d', $event_author, $event_data['task']['id']); default: return e('Notification'); } @@ -91,52 +95,40 @@ class Notification extends Base { switch ($event_name) { case File::EVENT_CREATE: - $title = e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); - break; + return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); case Comment::EVENT_CREATE: - $title = e('New comment on task #%d', $event_data['comment']['task_id']); - break; + return e('New comment on task #%d', $event_data['comment']['task_id']); case Comment::EVENT_UPDATE: - $title = e('Comment updated on task #%d', $event_data['comment']['task_id']); - break; + return e('Comment updated on task #%d', $event_data['comment']['task_id']); case Subtask::EVENT_CREATE: - $title = e('New subtask on task #%d', $event_data['subtask']['task_id']); - break; + return e('New subtask on task #%d', $event_data['subtask']['task_id']); case Subtask::EVENT_UPDATE: - $title = e('Subtask updated on task #%d', $event_data['subtask']['task_id']); - break; + return e('Subtask updated on task #%d', $event_data['subtask']['task_id']); case Task::EVENT_CREATE: - $title = e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); - break; + return e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); case Task::EVENT_UPDATE: - $title = e('Task updated #%d', $event_data['task']['id']); - break; + return e('Task updated #%d', $event_data['task']['id']); case Task::EVENT_CLOSE: - $title = e('Task #%d closed', $event_data['task']['id']); - break; + return e('Task #%d closed', $event_data['task']['id']); case Task::EVENT_OPEN: - $title = e('Task #%d opened', $event_data['task']['id']); - break; + return e('Task #%d opened', $event_data['task']['id']); case Task::EVENT_MOVE_COLUMN: - $title = e('Column changed for task #%d', $event_data['task']['id']); - break; + return e('Column changed for task #%d', $event_data['task']['id']); case Task::EVENT_MOVE_POSITION: - $title = e('New position for task #%d', $event_data['task']['id']); - break; + return e('New position for task #%d', $event_data['task']['id']); case Task::EVENT_MOVE_SWIMLANE: - $title = e('Swimlane changed for task #%d', $event_data['task']['id']); - break; + return e('Swimlane changed for task #%d', $event_data['task']['id']); case Task::EVENT_ASSIGNEE_CHANGE: - $title = e('Assignee changed on task #%d', $event_data['task']['id']); - break; + return e('Assignee changed on task #%d', $event_data['task']['id']); case Task::EVENT_OVERDUE: $nb = count($event_data['tasks']); - $title = $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']); - break; + return $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']); + case Task::EVENT_USER_MENTION: + return e('You were mentioned in the task #%d', $event_data['task']['id']); + case Comment::EVENT_USER_MENTION: + return e('You were mentioned in a comment on the task #%d', $event_data['task']['id']); default: - $title = e('Notification'); + return e('Notification'); } - - return $title; } } diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index 4ad9bbf1..66f4091d 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -86,11 +86,24 @@ class ProjectPermission extends Base * @param integer $user_id * @return boolean */ - public function isMember($project_id, $user_id) + public function isAssignable($project_id, $user_id) { return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER)); } + /** + * Return true if the user is member + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function isMember($project_id, $user_id) + { + return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER, Role::PROJECT_VIEWER)); + } + /** * Get active project ids by user * diff --git a/app/Model/Task.php b/app/Model/Task.php index f1cd094f..7aa9e312 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -41,6 +41,7 @@ class Task extends Base const EVENT_CREATE_UPDATE = 'task.create_update'; const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change'; const EVENT_OVERDUE = 'task.overdue'; + const EVENT_USER_MENTION = 'task.user.mention'; /** * Recurrence: status diff --git a/app/Model/TaskCreation.php b/app/Model/TaskCreation.php index 5ef1a04b..88912d4d 100644 --- a/app/Model/TaskCreation.php +++ b/app/Model/TaskCreation.php @@ -86,8 +86,13 @@ class TaskCreation extends Base */ private function fireEvents($task_id, array $values) { - $values['task_id'] = $task_id; - $this->container['dispatcher']->dispatch(Task::EVENT_CREATE_UPDATE, new TaskEvent($values)); - $this->container['dispatcher']->dispatch(Task::EVENT_CREATE, new TaskEvent($values)); + $event = new TaskEvent(array('task_id' => $task_id) + $values); + + $this->dispatcher->dispatch(Task::EVENT_CREATE_UPDATE, $event); + $this->dispatcher->dispatch(Task::EVENT_CREATE, $event); + + if (! empty($values['description'])) { + $this->userMention->fireEvents($values['description'], Task::EVENT_USER_MENTION, $event); + } } } diff --git a/app/Model/UserMention.php b/app/Model/UserMention.php new file mode 100644 index 00000000..97a4e419 --- /dev/null +++ b/app/Model/UserMention.php @@ -0,0 +1,61 @@ +db->table(User::TABLE) + ->columns('id', 'username', 'name', 'email', 'language') + ->eq('notifications_enabled', 1) + ->neq('id', $this->userSession->getId()) + ->in('username', array_unique($matches[1])) + ->findAll(); + } + + return $users; + } + + /** + * Fire events for user mentions + * + * @access public + * @param string $content + * @param string $eventName + * @param GenericEvent $event + */ + public function fireEvents($content, $eventName, GenericEvent $event) + { + if (empty($event['project_id'])) { + $event['project_id'] = $this->taskFinder->getProjectId($event['task_id']); + } + + $users = $this->getMentionedUsers($content); + + foreach ($users as $user) { + if ($this->projectPermission->isMember($event['project_id'], $user['id'])) { + $event['mention'] = $user; + $this->dispatcher->dispatch($eventName, $event); + } + } + } +} diff --git a/app/Model/UserNotification.php b/app/Model/UserNotification.php index e00f23c5..8161288c 100644 --- a/app/Model/UserNotification.php +++ b/app/Model/UserNotification.php @@ -21,18 +21,12 @@ class UserNotification extends Base */ public function sendNotifications($event_name, array $event_data) { - $logged_user_id = $this->userSession->isLogged() ? $this->userSession->getId() : 0; - $users = $this->getUsersWithNotificationEnabled($event_data['task']['project_id'], $logged_user_id); - - if (! empty($users)) { - foreach ($users as $user) { - if ($this->userNotificationFilter->shouldReceiveNotification($user, $event_data)) { - $this->sendUserNotification($user, $event_name, $event_data); - } - } + $users = $this->getUsersWithNotificationEnabled($event_data['task']['project_id'], $this->userSession->getId()); - // Restore locales - $this->config->setupTranslations(); + foreach ($users as $user) { + if ($this->userNotificationFilter->shouldReceiveNotification($user, $event_data)) { + $this->sendUserNotification($user, $event_name, $event_data); + } } } @@ -58,6 +52,9 @@ class UserNotification extends Base foreach ($this->userNotificationType->getSelectedTypes($user['id']) as $type) { $this->userNotificationType->getType($type)->notifyUser($user, $event_name, $event_data); } + + // Restore locales + $this->config->setupTranslations(); } /** diff --git a/app/Notification/Mail.php b/app/Notification/Mail.php index 98bffef2..d05dbdf2 100644 --- a/app/Notification/Mail.php +++ b/app/Notification/Mail.php @@ -121,6 +121,10 @@ class Mail extends Base implements NotificationInterface case Task::EVENT_ASSIGNEE_CHANGE: $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); break; + case Task::EVENT_USER_MENTION: + case Comment::EVENT_USER_MENTION: + $subject = $this->getStandardMailSubject(e('Mentioned'), $event_data); + break; case Task::EVENT_OVERDUE: $subject = e('[%s] Overdue tasks', $event_data['project_name']); break; diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index fad73047..03257c07 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -67,6 +67,7 @@ class ClassProvider implements ServiceProviderInterface 'User', 'UserImport', 'UserLocking', + 'UserMention', 'UserNotification', 'UserNotificationFilter', 'UserUnreadNotification', diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index 394573e4..1bde24dd 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -2,6 +2,7 @@ namespace Kanboard\Subscriber; +use Kanboard\Core\Base; use Kanboard\Event\GenericEvent; use Kanboard\Model\Task; use Kanboard\Model\Comment; @@ -9,34 +10,40 @@ use Kanboard\Model\Subtask; use Kanboard\Model\File; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class NotificationSubscriber extends \Kanboard\Core\Base implements EventSubscriberInterface +class NotificationSubscriber extends Base implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( - Task::EVENT_CREATE => array('execute', 0), - Task::EVENT_UPDATE => array('execute', 0), - Task::EVENT_CLOSE => array('execute', 0), - Task::EVENT_OPEN => array('execute', 0), - Task::EVENT_MOVE_COLUMN => array('execute', 0), - Task::EVENT_MOVE_POSITION => array('execute', 0), - Task::EVENT_MOVE_SWIMLANE => array('execute', 0), - Task::EVENT_ASSIGNEE_CHANGE => array('execute', 0), - Subtask::EVENT_CREATE => array('execute', 0), - Subtask::EVENT_UPDATE => array('execute', 0), - Comment::EVENT_CREATE => array('execute', 0), - Comment::EVENT_UPDATE => array('execute', 0), - File::EVENT_CREATE => array('execute', 0), + Task::EVENT_USER_MENTION => 'handleEvent', + Task::EVENT_CREATE => 'handleEvent', + Task::EVENT_UPDATE => 'handleEvent', + Task::EVENT_CLOSE => 'handleEvent', + Task::EVENT_OPEN => 'handleEvent', + Task::EVENT_MOVE_COLUMN => 'handleEvent', + Task::EVENT_MOVE_POSITION => 'handleEvent', + Task::EVENT_MOVE_SWIMLANE => 'handleEvent', + Task::EVENT_ASSIGNEE_CHANGE => 'handleEvent', + Subtask::EVENT_CREATE => 'handleEvent', + Subtask::EVENT_UPDATE => 'handleEvent', + Comment::EVENT_CREATE => 'handleEvent', + Comment::EVENT_UPDATE => 'handleEvent', + Comment::EVENT_USER_MENTION => 'handleEvent', + File::EVENT_CREATE => 'handleEvent', ); } - public function execute(GenericEvent $event, $event_name) + public function handleEvent(GenericEvent $event, $event_name) { $event_data = $this->getEventData($event); if (! empty($event_data)) { - $this->userNotification->sendNotifications($event_name, $event_data); - $this->projectNotification->sendNotifications($event_data['task']['project_id'], $event_name, $event_data); + if (! empty($event['mention'])) { + $this->userNotification->sendUserNotification($event['mention'], $event_name, $event_data); + } else { + $this->userNotification->sendNotifications($event_name, $event_data); + $this->projectNotification->sendNotifications($event_data['task']['project_id'], $event_name, $event_data); + } } } diff --git a/app/Template/notification/comment_user_mention.php b/app/Template/notification/comment_user_mention.php new file mode 100644 index 00000000..59f5127e --- /dev/null +++ b/app/Template/notification/comment_user_mention.php @@ -0,0 +1,7 @@ +

+ +

e($task['title']) ?>

+ +text->markdown($comment['comment']) ?> + +render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> \ No newline at end of file diff --git a/app/Template/notification/task_user_mention.php b/app/Template/notification/task_user_mention.php new file mode 100644 index 00000000..40ddddca --- /dev/null +++ b/app/Template/notification/task_user_mention.php @@ -0,0 +1,7 @@ +

+

e($task['title']) ?>

+ +

+text->markdown($task['description']) ?> + +render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> \ No newline at end of file -- cgit v1.2.3