1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
7 *
8 * @author Julius Härtl <jus@bitgrid.net>
9 *
10 * @license GNU AGPL version 3 or any later version
11 *
12 *  This program is free software: you can redistribute it and/or modify
13 *  it under the terms of the GNU Affero General Public License as
14 *  published by the Free Software Foundation, either version 3 of the
15 *  License, or (at your option) any later version.
16 *
17 *  This program is distributed in the hope that it will be useful,
18 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 *  GNU Affero General Public License for more details.
21 *
22 *  You should have received a copy of the GNU Affero General Public License
23 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 *
25 */
26
27namespace OCA\Deck\Notification;
28
29use DateTime;
30use Exception;
31use OCA\Deck\AppInfo\Application;
32use OCA\Deck\Db\Acl;
33use OCA\Deck\Db\AssignmentMapper;
34use OCA\Deck\Db\Board;
35use OCA\Deck\Db\BoardMapper;
36use OCA\Deck\Db\Card;
37use OCA\Deck\Db\CardMapper;
38use OCA\Deck\Db\User;
39use OCA\Deck\Service\ConfigService;
40use OCA\Deck\Service\PermissionService;
41use OCP\AppFramework\Db\DoesNotExistException;
42use OCP\AppFramework\Db\MultipleObjectsReturnedException;
43use OCP\Comments\IComment;
44use OCP\IConfig;
45use OCP\IGroupManager;
46use OCP\Notification\IManager;
47use OCP\Notification\INotification;
48
49class NotificationHelper {
50
51	/** @var CardMapper */
52	protected $cardMapper;
53	/** @var BoardMapper */
54	protected $boardMapper;
55	/** @var AssignmentMapper */
56	protected $assignmentMapper;
57	/** @var PermissionService */
58	protected $permissionService;
59	/** @var IConfig */
60	protected $config;
61	/** @var IManager */
62	protected $notificationManager;
63	/** @var IGroupManager */
64	protected $groupManager;
65	/** @var string */
66	protected $currentUser;
67	/** @var array */
68	private $boards = [];
69
70	public function __construct(
71		CardMapper $cardMapper,
72		BoardMapper $boardMapper,
73		AssignmentMapper $assignmentMapper,
74		PermissionService $permissionService,
75		IConfig $config,
76		IManager $notificationManager,
77		IGroupManager $groupManager,
78		$userId
79	) {
80		$this->cardMapper = $cardMapper;
81		$this->boardMapper = $boardMapper;
82		$this->assignmentMapper = $assignmentMapper;
83		$this->permissionService = $permissionService;
84		$this->config = $config;
85		$this->notificationManager = $notificationManager;
86		$this->groupManager = $groupManager;
87		$this->currentUser = $userId;
88	}
89
90	/**
91	 * @throws DoesNotExistException
92	 * @throws Exception thrown on invalid due date
93	 */
94	public function sendCardDuedate(Card $card): void {
95		// check if notification has already been sent
96		// ideally notifications should not be deleted once seen by the user so we can
97		// also deliver due date notifications for users who have been added later to a board
98		// this should maybe be addressed in nextcloud/server
99		if ($card->getNotified()) {
100			return;
101		}
102
103		$boardId = $this->cardMapper->findBoardId($card->getId());
104		$board = $this->getBoard($boardId, false, true);
105		/** @var User $user */
106		foreach ($this->permissionService->findUsers($boardId) as $user) {
107			$notificationSetting = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'board:' . $boardId . ':notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_DEFAULT);
108
109			if ($notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_OFF) {
110				continue;
111			}
112
113			$shouldNotify = $notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL;
114
115			if ($user->getUID() === $board->getOwner() && count($board->getAcl()) === 0) {
116				// Notify if all or assigned is configured for unshared boards
117				$shouldNotify = true;
118			} elseif ($notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED && $this->assignmentMapper->isUserAssigned($card->getId(), $user->getUID())) {
119				// Notify if the user is assigned and has the assigned setting selected
120				$shouldNotify = true;
121			}
122
123			if ($shouldNotify) {
124				$notification = $this->notificationManager->createNotification();
125				$notification
126					->setApp('deck')
127					->setUser((string)$user->getUID())
128					->setObject('card', (string)$card->getId())
129					->setSubject('card-overdue', [
130						$card->getTitle(), $board->getTitle()
131					])
132					->setDateTime(new DateTime($card->getDuedate()));
133				$this->notificationManager->notify($notification);
134			}
135		}
136		$this->cardMapper->markNotified($card);
137	}
138
139	public function markDuedateAsRead(Card $card): void {
140		$notification = $this->notificationManager->createNotification();
141		$notification
142			->setApp('deck')
143			->setObject('card', (string)$card->getId())
144			->setSubject('card-overdue', []);
145		$this->notificationManager->markProcessed($notification);
146	}
147
148	public function sendCardAssigned(Card $card, string $userId): void {
149		$boardId = $this->cardMapper->findBoardId($card->getId());
150		try {
151			$board = $this->getBoard($boardId);
152		} catch (Exception $e) {
153			return;
154		}
155
156		$notification = $this->notificationManager->createNotification();
157		$notification
158			->setApp('deck')
159			->setUser($userId)
160			->setDateTime(new DateTime())
161			->setObject('card', (string)$card->getId())
162				->setSubject('card-assigned', [
163					$card->getTitle(),
164					$board->getTitle(),
165					$this->currentUser
166				]);
167		$this->notificationManager->notify($notification);
168	}
169
170	public function markCardAssignedAsRead(Card $card, string $userId): void {
171		$notification = $this->notificationManager->createNotification();
172		$notification
173			->setApp('deck')
174			->setUser($userId)
175			->setObject('card', (string)$card->getId())
176			->setSubject('card-assigned', []);
177		$this->notificationManager->markProcessed($notification);
178	}
179
180	/**
181	 * Send notifications that a board was shared with a user/group
182	 */
183	public function sendBoardShared(int $boardId, Acl $acl, bool $markAsRead = false): void {
184		try {
185			$board = $this->getBoard($boardId);
186		} catch (Exception $e) {
187			return;
188		}
189
190		if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
191			$notification = $this->generateBoardShared($board, $acl->getParticipant());
192			if ($markAsRead) {
193				$this->notificationManager->markProcessed($notification);
194			} else {
195				$notification->setDateTime(new DateTime());
196				$this->notificationManager->notify($notification);
197			}
198		}
199		if ($acl->getType() === Acl::PERMISSION_TYPE_GROUP) {
200			$group = $this->groupManager->get($acl->getParticipant());
201			if ($group === null) {
202				return;
203			}
204			foreach ($group->getUsers() as $user) {
205				if ($user->getUID() === $this->currentUser) {
206					continue;
207				}
208				$notification = $this->generateBoardShared($board, $user->getUID());
209				if ($markAsRead) {
210					$this->notificationManager->markProcessed($notification);
211				} else {
212					$notification->setDateTime(new DateTime());
213					$this->notificationManager->notify($notification);
214				}
215			}
216		}
217	}
218
219	public function sendMention(IComment $comment): void {
220		foreach ($comment->getMentions() as $mention) {
221			$card = $this->cardMapper->find($comment->getObjectId());
222			$boardId = $this->cardMapper->findBoardId($card->getId());
223			$notification = $this->notificationManager->createNotification();
224			$notification
225				->setApp('deck')
226				->setUser((string) $mention['id'])
227				->setDateTime(new DateTime())
228				->setObject('card', (string) $card->getId())
229				->setSubject('card-comment-mentioned', [$card->getTitle(), $boardId, $this->currentUser])
230				->setMessage('{message}', ['message' => $comment->getMessage()]);
231			$this->notificationManager->notify($notification);
232		}
233	}
234
235	/**
236	 * @throws DoesNotExistException
237	 * @throws MultipleObjectsReturnedException
238	 */
239	private function getBoard(int $boardId, bool $withLabels = false, bool $withAcl = false): Board {
240		if (!array_key_exists($boardId, $this->boards)) {
241			$this->boards[$boardId] = $this->boardMapper->find($boardId, $withLabels, $withAcl);
242		}
243		return $this->boards[$boardId];
244	}
245
246	private function generateBoardShared(Board $board, string $userId): INotification {
247		$notification = $this->notificationManager->createNotification();
248		$notification
249			->setApp('deck')
250			->setUser($userId)
251			->setObject('board', (string)$board->getId())
252			->setSubject('board-shared', [$board->getTitle(), $this->currentUser]);
253		return $notification;
254	}
255}
256