1<?php
2
3declare(strict_types=1);
4/**
5 * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
6 *
7 * @license GNU AGPL version 3 or any later version
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24namespace OCA\Talk\Notification;
25
26use OCA\FederatedFileSharing\AddressHandler;
27use OCA\Talk\Chat\CommentsManager;
28use OCA\Talk\Chat\MessageParser;
29use OCA\Talk\Config;
30use OCA\Talk\Exceptions\ParticipantNotFoundException;
31use OCA\Talk\Exceptions\RoomNotFoundException;
32use OCA\Talk\GuestManager;
33use OCA\Talk\Manager;
34use OCA\Talk\Model\Attendee;
35use OCA\Talk\Participant;
36use OCA\Talk\Room;
37use OCA\Talk\Service\ParticipantService;
38use OCP\Comments\ICommentsManager;
39use OCP\Comments\NotFoundException;
40use OCP\HintException;
41use OCP\IL10N;
42use OCP\IURLGenerator;
43use OCP\IUser;
44use OCP\IUserManager;
45use OCP\L10N\IFactory;
46use OCP\Notification\AlreadyProcessedException;
47use OCP\Notification\IAction;
48use OCP\Notification\IManager as INotificationManager;
49use OCP\Notification\INotification;
50use OCP\Notification\INotifier;
51use OCP\RichObjectStrings\Definitions;
52use OCP\Share\Exceptions\ShareNotFound;
53use OCP\Share\IManager as IShareManager;
54use OCP\Share\IShare;
55
56class Notifier implements INotifier {
57
58	/** @var IFactory */
59	protected $lFactory;
60	/** @var IURLGenerator */
61	protected $url;
62	/** @var Config */
63	protected $config;
64	/** @var IUserManager */
65	protected $userManager;
66	/** @var GuestManager */
67	protected $guestManager;
68	/** @var IShareManager */
69	private $shareManager;
70	/** @var Manager */
71	protected $manager;
72	/** @var ParticipantService */
73	protected $participantService;
74	/** @var INotificationManager */
75	protected $notificationManager;
76	/** @var ICommentsManager */
77	protected $commentManager;
78	/** @var MessageParser */
79	protected $messageParser;
80	/** @var Definitions */
81	protected $definitions;
82	/** @var AddressHandler */
83	protected $addressHandler;
84
85	/** @var Room[] */
86	protected $rooms = [];
87	/** @var Participant[][] */
88	protected $participants = [];
89
90	public function __construct(IFactory $lFactory,
91								IURLGenerator $url,
92								Config $config,
93								IUserManager $userManager,
94								GuestManager $guestManager,
95								IShareManager $shareManager,
96								Manager $manager,
97								ParticipantService $participantService,
98								INotificationManager $notificationManager,
99								CommentsManager $commentManager,
100								MessageParser $messageParser,
101								Definitions $definitions,
102								AddressHandler $addressHandler) {
103		$this->lFactory = $lFactory;
104		$this->url = $url;
105		$this->config = $config;
106		$this->userManager = $userManager;
107		$this->guestManager = $guestManager;
108		$this->shareManager = $shareManager;
109		$this->manager = $manager;
110		$this->participantService = $participantService;
111		$this->notificationManager = $notificationManager;
112		$this->commentManager = $commentManager;
113		$this->messageParser = $messageParser;
114		$this->definitions = $definitions;
115		$this->addressHandler = $addressHandler;
116	}
117
118	/**
119	 * Identifier of the notifier, only use [a-z0-9_]
120	 *
121	 * @return string
122	 * @since 17.0.0
123	 */
124	public function getID(): string {
125		return 'talk';
126	}
127
128	/**
129	 * Human readable name describing the notifier
130	 *
131	 * @return string
132	 * @since 17.0.0
133	 */
134	public function getName(): string {
135		return $this->lFactory->get('spreed')->t('Talk');
136	}
137
138	/**
139	 * @param string $objectId
140	 * @param string $userId
141	 * @return Room
142	 * @throws RoomNotFoundException
143	 */
144	protected function getRoom(string $objectId, string $userId): Room {
145		if (array_key_exists($objectId, $this->rooms)) {
146			if ($this->rooms[$objectId] === null) {
147				throw new RoomNotFoundException('Room does not exist');
148			}
149
150			return $this->rooms[$objectId];
151		}
152
153		try {
154			$room = $this->manager->getRoomByToken($objectId, $userId);
155			$this->rooms[$objectId] = $room;
156			return $room;
157		} catch (RoomNotFoundException $e) {
158			if (!is_numeric($objectId)) {
159				// Room does not exist
160				$this->rooms[$objectId] = null;
161				throw $e;
162			}
163
164			try {
165				// Before 3.2.3 the id was passed in notifications
166				$room = $this->manager->getRoomById((int) $objectId);
167				$this->rooms[$objectId] = $room;
168				return $room;
169			} catch (RoomNotFoundException $e) {
170				// Room does not exist
171				$this->rooms[$objectId] = null;
172				throw $e;
173			}
174		}
175	}
176
177	/**
178	 * @param Room $room
179	 * @param string $userId
180	 * @return Participant
181	 * @throws ParticipantNotFoundException
182	 */
183	protected function getParticipant(Room $room, string $userId): Participant {
184		$roomId = $room->getId();
185		if (array_key_exists($roomId, $this->participants) && array_key_exists($userId, $this->participants[$roomId])) {
186			if ($this->participants[$roomId][$userId] === null) {
187				throw new ParticipantNotFoundException('Participant does not exist');
188			}
189
190			return $this->participants[$roomId][$userId];
191		}
192
193		try {
194			$participant = $room->getParticipant($userId, false);
195			$this->participants[$roomId][$userId] = $participant;
196			return $participant;
197		} catch (ParticipantNotFoundException $e) {
198			// Participant does not exist
199			$this->participants[$roomId][$userId] = null;
200			throw $e;
201		}
202	}
203
204	/**
205	 * @param INotification $notification
206	 * @param string $languageCode The code of the language that should be used to prepare the notification
207	 * @return INotification
208	 * @throws \InvalidArgumentException When the notification was not prepared by a notifier
209	 * @since 9.0.0
210	 */
211	public function prepare(INotification $notification, string $languageCode): INotification {
212		if ($notification->getApp() !== 'spreed') {
213			throw new \InvalidArgumentException('Incorrect app');
214		}
215
216		$userId = $notification->getUser();
217		$user = $this->userManager->get($userId);
218		if (!$user instanceof IUser || $this->config->isDisabledForUser($user)) {
219			throw new AlreadyProcessedException();
220		}
221
222		$l = $this->lFactory->get('spreed', $languageCode);
223
224		if ($notification->getObjectType() === 'hosted-signaling-server') {
225			return $this->parseHostedSignalingServer($notification, $l);
226		}
227
228		try {
229			$room = $this->getRoom($notification->getObjectId(), $userId);
230		} catch (RoomNotFoundException $e) {
231			// Room does not exist
232			throw new AlreadyProcessedException();
233		}
234
235		if ($this->notificationManager->isPreparingPushNotification() && $notification->getSubject() === 'call') {
236			// Skip the participant check when we generate push notifications
237			// we just looped over the participants to create the notification,
238			// they can not be removed between these 2 steps, but we can save
239			// n queries.
240		} else {
241			try {
242				$participant = $this->getParticipant($room, $userId);
243			} catch (ParticipantNotFoundException $e) {
244				// Room does not exist
245				throw new AlreadyProcessedException();
246			}
247		}
248
249		$notification
250			->setIcon($this->url->getAbsoluteURL($this->url->imagePath('spreed', 'app-dark.svg')))
251			->setLink($this->url->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken()]));
252
253		$subject = $notification->getSubject();
254		if ($subject === 'invitation') {
255			return $this->parseInvitation($notification, $room, $l);
256		}
257		if ($subject === 'call') {
258			if ($room->getObjectType() === 'share:password') {
259				return $this->parsePasswordRequest($notification, $room, $l);
260			}
261			return $this->parseCall($notification, $room, $l);
262		}
263		if ($subject === 'reply' || $subject === 'mention' || $subject === 'chat') {
264			return $this->parseChatMessage($notification, $room, $participant, $l);
265		}
266
267		if ($subject === 'remote_talk_share') {
268			return $this->parseRemoteInvitationMessage($notification, $l);
269		}
270
271		$this->notificationManager->markProcessed($notification);
272		throw new \InvalidArgumentException('Unknown subject');
273	}
274
275	protected function shortenJsonEncodedMultibyteSave(string $subject, int $dataLength): string {
276		$temp = mb_substr($subject, 0, $dataLength);
277		while (strlen(json_encode($temp)) > $dataLength) {
278			$temp = mb_substr($temp, 0, -5);
279		}
280		return $temp;
281	}
282
283	/**
284	 * @throws HintException
285	 */
286	protected function parseRemoteInvitationMessage(INotification $notification, IL10N $l): INotification {
287		$subjectParameters = $notification->getSubjectParameters();
288
289		[$sharedById, $sharedByServer] = $this->addressHandler->splitUserRemote($subjectParameters['sharedByFederatedId']);
290
291		$message = $l->t('{user1} shared room {roomName} on {remoteServer} with you');
292
293		$rosParameters = [
294			'user1' => [
295				'type' => 'user',
296				'id' => $sharedById,
297				'name' => $subjectParameters['sharedByDisplayName'],
298				'server' => $sharedByServer,
299			],
300			'roomName' => [
301				'type' => 'highlight',
302				'id' => $subjectParameters['serverUrl'] . '::' . $subjectParameters['roomToken'],
303				'name' => $subjectParameters['roomName'],
304			],
305			'remoteServer' => [
306				'type' => 'highlight',
307				'id' => $subjectParameters['serverUrl'],
308				'name' => $subjectParameters['serverUrl'],
309			]
310		];
311
312		$placeholders = $replacements = [];
313		foreach ($rosParameters as $placeholder => $parameter) {
314			$placeholders[] = '{' . $placeholder .'}';
315			$replacements[] = $parameter['name'];
316		}
317
318		$notification->setParsedMessage(str_replace($placeholders, $replacements, $message));
319		$notification->setRichMessage($message, $rosParameters);
320
321		return $notification;
322	}
323
324	/**
325	 * @param INotification $notification
326	 * @param Room $room
327	 * @param Participant $participant
328	 * @param IL10N $l
329	 * @return INotification
330	 * @throws \InvalidArgumentException
331	 */
332	protected function parseChatMessage(INotification $notification, Room $room, Participant $participant, IL10N $l): INotification {
333		if ($notification->getObjectType() !== 'chat') {
334			throw new \InvalidArgumentException('Unknown object type');
335		}
336
337		$subjectParameters = $notification->getSubjectParameters();
338
339		$richSubjectUser = null;
340		$isGuest = false;
341		if ($subjectParameters['userType'] === 'users') {
342			$userId = $subjectParameters['userId'];
343			$user = $this->userManager->get($userId);
344
345			if ($user instanceof IUser) {
346				$richSubjectUser = [
347					'type' => 'user',
348					'id' => $userId,
349					'name' => $user->getDisplayName(),
350				];
351			}
352		} else {
353			$isGuest = true;
354		}
355
356		$richSubjectCall = [
357			'type' => 'call',
358			'id' => $room->getId(),
359			'name' => $room->getDisplayName($notification->getUser()),
360			'call-type' => $this->getRoomType($room),
361		];
362
363		$messageParameters = $notification->getMessageParameters();
364		if (!isset($messageParameters['commentId'])) {
365			throw new AlreadyProcessedException();
366		}
367
368		try {
369			$comment = $this->commentManager->get($messageParameters['commentId']);
370		} catch (NotFoundException $e) {
371			throw new AlreadyProcessedException();
372		}
373
374		$message = $this->messageParser->createMessage($room, $participant, $comment, $l);
375		$this->messageParser->parseMessage($message);
376
377		if (!$message->getVisibility()) {
378			throw new AlreadyProcessedException();
379		}
380
381		$placeholders = $replacements = [];
382		foreach ($message->getMessageParameters() as $placeholder => $parameter) {
383			$placeholders[] = '{' . $placeholder . '}';
384			if ($parameter['type'] === 'user' || $parameter['type'] === 'guest') {
385				$replacements[] = '@' . $parameter['name'];
386			} else {
387				$replacements[] = $parameter['name'];
388			}
389		}
390
391		$parsedMessage = str_replace($placeholders, $replacements, $message->getMessage());
392		if (!$this->notificationManager->isPreparingPushNotification()) {
393			$notification->setParsedMessage($parsedMessage);
394			$notification->setRichMessage($message->getMessage(), $message->getMessageParameters());
395		}
396
397		$richSubjectParameters = [
398			'user' => $richSubjectUser,
399			'call' => $richSubjectCall,
400		];
401
402		if ($this->notificationManager->isPreparingPushNotification()) {
403			$shortenMessage = $this->shortenJsonEncodedMultibyteSave($parsedMessage, 100);
404			if ($shortenMessage !== $parsedMessage) {
405				$shortenMessage .= '…';
406			}
407			$richSubjectParameters['message'] = [
408				'type' => 'highlight',
409				'id' => $message->getComment()->getId(),
410				'name' => $shortenMessage,
411			];
412			if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
413				$subject = "{user}\n{message}";
414			} elseif ($richSubjectUser) {
415				$subject = $l->t('{user} in {call}') . "\n{message}";
416			} elseif (!$isGuest) {
417				$subject = $l->t('Deleted user in {call}') . "\n{message}";
418			} else {
419				try {
420					$richSubjectParameters['guest'] = $this->getGuestParameter($room, $comment->getActorId());
421					$subject = $l->t('{guest} (guest) in {call}') . "\n{message}";
422				} catch (ParticipantNotFoundException $e) {
423					$subject = $l->t('Guest in {call}') . "\n{message}";
424				}
425			}
426		} elseif ($notification->getSubject() === 'chat') {
427			if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
428				$subject = $l->t('{user} sent you a private message');
429			} elseif ($richSubjectUser) {
430				$subject = $l->t('{user} sent a message in conversation {call}');
431			} elseif (!$isGuest) {
432				$subject = $l->t('A deleted user sent a message in conversation {call}');
433			} else {
434				try {
435					$richSubjectParameters['guest'] = $this->getGuestParameter($room, $comment->getActorId());
436					$subject = $l->t('{guest} (guest) sent a message in conversation {call}');
437				} catch (ParticipantNotFoundException $e) {
438					$subject = $l->t('A guest sent a message in conversation {call}');
439				}
440			}
441		} elseif ($notification->getSubject() === 'reply') {
442			if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
443				$subject = $l->t('{user} replied to your private message');
444			} elseif ($richSubjectUser) {
445				$subject = $l->t('{user} replied to your message in conversation {call}');
446			} elseif (!$isGuest) {
447				$subject = $l->t('A deleted user replied to your message in conversation {call}');
448			} else {
449				try {
450					$richSubjectParameters['guest'] = $this->getGuestParameter($room, $comment->getActorId());
451					$subject = $l->t('{guest} (guest) replied to your message in conversation {call}');
452				} catch (ParticipantNotFoundException $e) {
453					$subject = $l->t('A guest replied to your message in conversation {call}');
454				}
455			}
456		} elseif ($room->getType() === Room::TYPE_ONE_TO_ONE) {
457			$subject = $l->t('{user} mentioned you in a private conversation');
458		} elseif ($richSubjectUser) {
459			$subject = $l->t('{user} mentioned you in conversation {call}');
460		} elseif (!$isGuest) {
461			$subject = $l->t('A deleted user mentioned you in conversation {call}');
462		} else {
463			try {
464				$richSubjectParameters['guest'] = $this->getGuestParameter($room, $comment->getActorId());
465				$subject = $l->t('{guest} (guest) mentioned you in conversation {call}');
466			} catch (ParticipantNotFoundException $e) {
467				$subject = $l->t('A guest mentioned you in conversation {call}');
468			}
469		}
470		$notification = $this->addActionButton($notification, $l->t('View chat'), false);
471
472		if ($richSubjectParameters['user'] === null) {
473			unset($richSubjectParameters['user']);
474		}
475
476		$placeholders = $replacements = [];
477		foreach ($richSubjectParameters as $placeholder => $parameter) {
478			$placeholders[] = '{' . $placeholder . '}';
479			$replacements[] = $parameter['name'];
480		}
481
482		$notification->setParsedSubject(str_replace($placeholders, $replacements, $subject))
483			->setRichSubject($subject, $richSubjectParameters);
484
485		return $notification;
486	}
487
488	/**
489	 * @param Room $room
490	 * @param string $actorId
491	 * @return array
492	 * @throws ParticipantNotFoundException
493	 */
494	protected function getGuestParameter(Room $room, string $actorId): array {
495		$participant = $room->getParticipantByActor(Attendee::ACTOR_GUESTS, $actorId, false);
496		$name = $participant->getAttendee()->getDisplayName();
497		if (trim($name) === '') {
498			throw new ParticipantNotFoundException('Empty name');
499		}
500
501		return [
502			'type' => 'guest',
503			'id' => $actorId,
504			'name' => $name,
505		];
506	}
507
508	/**
509	 * @param Room $room
510	 * @return string
511	 * @throws \InvalidArgumentException
512	 */
513	protected function getRoomType(Room $room): string {
514		switch ($room->getType()) {
515			case Room::TYPE_ONE_TO_ONE:
516				return 'one2one';
517			case Room::TYPE_GROUP:
518				return 'group';
519			case Room::TYPE_PUBLIC:
520				return 'public';
521			default:
522				throw new \InvalidArgumentException('Unknown room type');
523		}
524	}
525
526	/**
527	 * @param INotification $notification
528	 * @param Room $room
529	 * @param IL10N $l
530	 * @return INotification
531	 * @throws \InvalidArgumentException
532	 * @throws AlreadyProcessedException
533	 */
534	protected function parseInvitation(INotification $notification, Room $room, IL10N $l): INotification {
535		if ($notification->getObjectType() !== 'room') {
536			throw new \InvalidArgumentException('Unknown object type');
537		}
538
539		$parameters = $notification->getSubjectParameters();
540		$uid = $parameters['actorId'] ?? $parameters[0];
541
542		$user = $this->userManager->get($uid);
543		if (!$user instanceof IUser) {
544			throw new AlreadyProcessedException();
545		}
546
547		$roomName = $room->getDisplayName($notification->getUser());
548		if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
549			$subject = $l->t('{user} invited you to a private conversation');
550			if ($this->participantService->hasActiveSessionsInCall($room)) {
551				$notification = $this->addActionButton($notification, $l->t('Join call'));
552			} else {
553				$notification = $this->addActionButton($notification, $l->t('View chat'), false);
554			}
555
556			$notification
557				->setParsedSubject(str_replace('{user}', $user->getDisplayName(), $subject))
558				->setRichSubject(
559					$subject, [
560						'user' => [
561							'type' => 'user',
562							'id' => $uid,
563							'name' => $user->getDisplayName(),
564						],
565						'call' => [
566							'type' => 'call',
567							'id' => $room->getId(),
568							'name' => $roomName,
569							'call-type' => $this->getRoomType($room),
570						],
571					]
572				);
573		} elseif (\in_array($room->getType(), [Room::TYPE_GROUP, Room::TYPE_PUBLIC], true)) {
574			$subject = $l->t('{user} invited you to a group conversation: {call}');
575			if ($this->participantService->hasActiveSessionsInCall($room)) {
576				$notification = $this->addActionButton($notification, $l->t('Join call'));
577			} else {
578				$notification = $this->addActionButton($notification, $l->t('View chat'), false);
579			}
580
581			$notification
582				->setParsedSubject(str_replace(['{user}', '{call}'], [$user->getDisplayName(), $roomName], $subject))
583				->setRichSubject(
584					$subject, [
585						'user' => [
586							'type' => 'user',
587							'id' => $uid,
588							'name' => $user->getDisplayName(),
589						],
590						'call' => [
591							'type' => 'call',
592							'id' => $room->getId(),
593							'name' => $roomName,
594							'call-type' => $this->getRoomType($room),
595						],
596					]
597				);
598		} else {
599			throw new AlreadyProcessedException();
600		}
601
602		return $notification;
603	}
604
605	/**
606	 * @param INotification $notification
607	 * @param Room $room
608	 * @param IL10N $l
609	 * @return INotification
610	 * @throws \InvalidArgumentException
611	 * @throws AlreadyProcessedException
612	 */
613	protected function parseCall(INotification $notification, Room $room, IL10N $l): INotification {
614		if ($notification->getObjectType() !== 'call') {
615			throw new \InvalidArgumentException('Unknown object type');
616		}
617
618		$roomName = $room->getDisplayName($notification->getUser());
619		if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
620			$parameters = $notification->getSubjectParameters();
621			$calleeId = $parameters['callee'];
622			$user = $this->userManager->get($calleeId);
623			if ($user instanceof IUser) {
624				if ($this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room)) {
625					$notification = $this->addActionButton($notification, $l->t('Answer call'));
626					$subject = $l->t('{user} would like to talk with you');
627				} else {
628					$notification = $this->addActionButton($notification, $l->t('Call back'));
629					$subject = $l->t('You missed a call from {user}');
630				}
631
632				$notification
633					->setParsedSubject(str_replace('{user}', $user->getDisplayName(), $subject))
634					->setRichSubject(
635						$subject, [
636							'user' => [
637								'type' => 'user',
638								'id' => $calleeId,
639								'name' => $user->getDisplayName(),
640							],
641							'call' => [
642								'type' => 'call',
643								'id' => $room->getId(),
644								'name' => $roomName,
645								'call-type' => $this->getRoomType($room),
646							],
647						]
648					);
649			} else {
650				throw new AlreadyProcessedException();
651			}
652		} elseif (\in_array($room->getType(), [Room::TYPE_GROUP, Room::TYPE_PUBLIC], true)) {
653			if ($this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room)) {
654				$notification = $this->addActionButton($notification, $l->t('Join call'));
655				$subject = $l->t('A group call has started in {call}');
656			} else {
657				$notification = $this->addActionButton($notification, $l->t('View chat'), false);
658				$subject = $l->t('You missed a group call in {call}');
659			}
660
661			$notification
662				->setParsedSubject(str_replace('{call}', $roomName, $subject))
663				->setRichSubject(
664					$subject, [
665						'call' => [
666							'type' => 'call',
667							'id' => $room->getId(),
668							'name' => $roomName,
669							'call-type' => $this->getRoomType($room),
670						],
671					]
672				);
673		} else {
674			throw new AlreadyProcessedException();
675		}
676
677		return $notification;
678	}
679
680	/**
681	 * @param INotification $notification
682	 * @param Room $room
683	 * @param IL10N $l
684	 * @return INotification
685	 * @throws \InvalidArgumentException
686	 * @throws AlreadyProcessedException
687	 */
688	protected function parsePasswordRequest(INotification $notification, Room $room, IL10N $l): INotification {
689		if ($notification->getObjectType() !== 'call') {
690			throw new \InvalidArgumentException('Unknown object type');
691		}
692
693		try {
694			$share = $this->shareManager->getShareByToken($room->getObjectId());
695		} catch (ShareNotFound $e) {
696			throw new AlreadyProcessedException();
697		}
698
699		try {
700			$file = [
701				'type' => 'highlight',
702				'id' => $share->getNodeId(),
703				'name' => $share->getNode()->getName(),
704			];
705		} catch (\OCP\Files\NotFoundException $e) {
706			throw new AlreadyProcessedException();
707		}
708
709		$callIsActive = $this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room);
710		if ($callIsActive) {
711			$notification = $this->addActionButton($notification, $l->t('Answer call'));
712		} else {
713			$notification = $this->addActionButton($notification, $l->t('Call back'));
714		}
715
716		if ($share->getShareType() === IShare::TYPE_EMAIL) {
717			$sharedWith = $share->getSharedWith();
718			if ($callIsActive) {
719				$subject = $l->t('{email} is requesting the password to access {file}');
720			} else {
721				$subject = $l->t('{email} tried to request the password to access {file}');
722			}
723
724			$notification
725				->setParsedSubject(str_replace(['{email}', '{file}'], [$sharedWith, $file['name']], $subject))
726				->setRichSubject($subject, [
727					'email' => [
728						'type' => 'email',
729						'id' => $sharedWith,
730						'name' => $sharedWith,
731					],
732					'file' => $file,
733				]
734				);
735		} else {
736			if ($callIsActive) {
737				$subject = $l->t('Someone is requesting the password to access {file}');
738			} else {
739				$subject = $l->t('Someone tried to request the password to access {file}');
740			}
741
742			$notification
743				->setParsedSubject(str_replace('{file}', $file['name'], $subject))
744				->setRichSubject($subject, ['file' => $file]);
745		}
746
747		return $notification;
748	}
749
750	protected function addActionButton(INotification $notification, string $label, bool $primary = true): INotification {
751		$action = $notification->createAction();
752		$action->setLabel($label)
753			->setParsedLabel($label)
754			->setLink($notification->getLink(), IAction::TYPE_WEB)
755			->setPrimary($primary);
756
757		$notification->addParsedAction($action);
758
759		return $notification;
760	}
761
762	protected function parseHostedSignalingServer(INotification $notification, IL10N $l): INotification {
763		$action = $notification->createAction();
764		$action->setLabel('open_settings')
765			->setParsedLabel($l->t('Open settings'))
766			->setLink($notification->getLink(), IAction::TYPE_WEB)
767			->setPrimary(true);
768
769		switch ($notification->getSubject()) {
770			case 'added':
771				$subject = $l->t('The hosted signaling server is now configured and will be used.');
772				break;
773			case 'removed':
774				$subject = $l->t('The hosted signaling server was removed and will not be used anymore.');
775				break;
776			case 'changed-status':
777				$subject = $l->t('The hosted signaling server account has changed the status from "{oldstatus}" to "{newstatus}".');
778
779				$parameters = $notification->getSubjectParameters();
780				$subject = str_replace(
781					['{oldstatus}', '{newstatus}'],
782					[$parameters['oldstatus'], $parameters['newstatus']],
783					$subject
784				);
785				break;
786			default:
787				throw new \InvalidArgumentException('Unknown subject');
788		}
789
790		return $notification
791			->setParsedSubject($subject)
792			->setIcon($notification->getIcon())
793			->addParsedAction($action);
794	}
795}
796