diff --git a/components/ILIAS/Forum/Forum.php b/components/ILIAS/Forum/Forum.php index 0d79bcdc6cc0..1821c782628a 100644 --- a/components/ILIAS/Forum/Forum.php +++ b/components/ILIAS/Forum/Forum.php @@ -40,5 +40,7 @@ public function init( new Component\Resource\ComponentJS($this, "autosave_forum.js"); $contribute[Component\Resource\PublicAsset::class] = fn() => new Component\Resource\ComponentCSS($this, "forum_table.css"); + $contribute[\ILIAS\Notifications\Interfaces\PushProviderInterface::class] = static fn() => + new \ilForumPushProvider(); } } diff --git a/components/ILIAS/Forum/PRIVACY.md b/components/ILIAS/Forum/PRIVACY.md index 0d9df3cbd9bd..2495c8362055 100644 --- a/components/ILIAS/Forum/PRIVACY.md +++ b/components/ILIAS/Forum/PRIVACY.md @@ -91,6 +91,7 @@ If an **entire forum** is deleted and the ILIAS "Trash" is active, all personal ## Data being exported If a **notification about new or changed posts** is set, it will be sent via the ILIAS mail system. For users who have set forwarding to an external e-mail address, the notification will be forwarded to this address. The notification contains the date, title and content of the post, as well as the username or pseudonym of the creator. Depending on the setting, file attachments are also forwarded. +- If push notifcations are active and enabled for the forum a push notification is send instead of a mail. The Notification contains the title of the forum, thread and (if part of the context) post and a link to the thread or post. If the push notification couldnt be delivered a mail is send instead. New posts can be subscribed to via an **RSS feed** in suitable programs. In the forum, you can set whether this feed is password-protected or public. A public feed contains the title of the forum, the **title, content, creation date** and a link to the post in ILIAS for each post. If the forum itself is not public, you will be taken to the ILIAS login page when you use the link. @@ -98,4 +99,4 @@ To support the **full-text search**, a separate server with the Apache Lucene se ILIAS users can create a **print view** in a forum topic and print it out via the browser. The personal data contained on it corresponds to the normal browser view of the forum topic. ILIAS users with the "Edit Settings" permission for a forum can export an **HTML page** with the content of all forum topics. The personal data contained in it corresponds to the normal browser views of the forum topics. -A forum can be exported from ILIAS in **XML format** by users with the "Edit Settings" permission. This creates a ZIP archive that can then be downloaded and imported into another ILIAS platform. The archive contains all content of the forum, the user IDs of the moderators and authors of posts, as well as the usernames or pseudonyms of the authors. \ No newline at end of file +A forum can be exported from ILIAS in **XML format** by users with the "Edit Settings" permission. This creates a ZIP archive that can then be downloaded and imported into another ILIAS platform. The archive contains all content of the forum, the user IDs of the moderators and authors of posts, as well as the usernames or pseudonyms of the authors. diff --git a/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumNotificationTaskProcessor.php b/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumNotificationTaskProcessor.php new file mode 100644 index 000000000000..e9dcd234c116 --- /dev/null +++ b/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumNotificationTaskProcessor.php @@ -0,0 +1,77 @@ +task_manager = $DIC->backgroundTasks()->taskManager(); + $this->task_factory = $DIC->backgroundTasks()->taskFactory(); + $this->lng = $DIC->language(); + $this->user = $DIC->user(); + $this->logger = $logger ; + } + + /** + * @param int[] $recipients + */ + public function run( + ilForumNotificationMailData $provider, + int $notification_type, + array $recipients, + ): void { + if ($recipients === []) { + return; + } + + $push_job = $this->task_factory->createTask(ilForumPushNotificationJob::class, [ + json_encode([ + 'post_id' => $provider->getPostId(), + 'ref_id' => $provider->getRefId(), + 'notification_type' => $notification_type, + 'recipients' => array_values($recipients), + ], JSON_THROW_ON_ERROR), + ]); + $interaction = $this->task_factory->createTask( + ilMailDeliveryJobUserInteraction::class, + [$push_job, $this->user->getId()], + ); + + $bucket = new BasicBucket(); + $bucket->setUserId($this->user->getId()); + $bucket->setTask($interaction); + $this->lng->loadLanguageModule('forum'); + $bucket->setTitle($this->lng->txt('frm_notification_bg_task_title')); + + $this->logger->info('Delegated forum notification delivery to background task.'); + $this->task_manager->run($bucket); + } +} diff --git a/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumPushNotificationJob.php b/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumPushNotificationJob.php new file mode 100644 index 000000000000..b3e29777fc90 --- /dev/null +++ b/components/ILIAS/Forum/classes/Notification/BackgroundTask/class.ilForumPushNotificationJob.php @@ -0,0 +1,86 @@ +getValue(), true, 512, JSON_THROW_ON_ERROR); + + $provider = new ilForumNotificationDataProvider( + new ilForumPost((int) $data['post_id']), + (int) $data['ref_id'], + new ilForumNotificationCache() + ); + + $push_provider = new ilForumPushProvider(); + $mail_recipients = $push_provider->filterMailRecipients( + $data['recipients'], + $provider, + (int) $data['notification_type'], + $DIC->logger()->forComponent('forum') + ); + + $output = new BooleanValue(); + $output->setValue(true); + + if ($mail_recipients !== []) { + $mail_notification = new ilForumMailEventNotificationSender($provider, $DIC->logger()->forComponent('forum')); + $mail_notification->setType((int) $data['notification_type']); + $mail_notification->setRecipients($mail_recipients); + $mail_notification->send(); + } + + return $output; + } + + public function getInputTypes(): array + { + return [ + new SingleType(StringValue::class), + ]; + } + + public function getOutputType(): Type + { + return new SingleType(BooleanValue::class); + } + + public function isStateless(): bool + { + return true; + } + + public function getExpectedTimeOfTaskInSeconds(): int + { + return 30; + } +} diff --git a/components/ILIAS/Forum/classes/Notification/class.ilForumPushProvider.php b/components/ILIAS/Forum/classes/Notification/class.ilForumPushProvider.php new file mode 100644 index 000000000000..2c594743da72 --- /dev/null +++ b/components/ILIAS/Forum/classes/Notification/class.ilForumPushProvider.php @@ -0,0 +1,220 @@ +loadLanguageModule('forum'); + + return $lng->txt('forum'); + } + + public function getDescription(ilLanguage $lng): string + { + $lng->loadLanguageModule('forum'); + + return $lng->txt('frm_forum_push_notification_info'); + } + + /** + * @param int[] $recipients + * @return int[] + */ + public function filterMailRecipients( + array $recipients, + ilForumNotificationMailData $provider, + int $notification_type, + ilLogger $logger + ): array { + global $DIC; + + if ( + !$DIC->settings()->get('forum_notification', '0') || + new ilSetting('notifications')->get('enable_push') !== '1' + ) { + return $recipients; + } + + $mail_recipients = []; + + foreach ($recipients as $recipient_id) { + if ($this->sendPush($recipient_id, $provider, $notification_type)) { + $logger->debug(sprintf('Push notification sent to user "%s".', $recipient_id)); + } else { + $logger->debug(sprintf('Push notification not sent to user "%s". Fallback to mail if possible.', $recipient_id)); + $mail_recipients[] = $recipient_id; + } + } + + return $mail_recipients; + } + + private function sendPush( + int $recipient_id, + ilForumNotificationMailData $provider, + int $notification_type + ): bool { + $user = ilObjectFactory::getInstanceByObjId($recipient_id, false); + if (!$user instanceof ilObjUser) { + return false; + } + + if (isset($this->lang_cache[$user->getLanguage()])) { + $lng = $this->lang_cache[$user->getLanguage()]; + } else { + $lng = ilLanguageFactory::_getLanguageOfUser($recipient_id); + $lng->loadLanguageModule('forum'); + $this->lang_cache[$user->getLanguage()] = $lng; + } + + $content = $this->buildPushContent($provider, $notification_type, $lng); + if ($content === null) { + return false; + } + + return $this->getPushProvider()->push( + $user, + $content['title'], + $content['description'], + $this->buildPushLink($provider, $lng) + ); + } + + /** + * @return array{title: string, description: string}|null + */ + private function buildPushContent( + ilForumNotificationMailData $provider, + int $notification_type, + ilLanguage $lng + ): ?array { + $subject_key = $this->getSubjectLanguageKey($notification_type); + if ($subject_key === null) { + return null; + } + + $container_text = ''; + if ($provider->providesClosestContainer()) { + $container_text = ' (' . + $lng->txt('frm_noti_obj_' . $provider->closestContainer()->getType()) . + ' "' . $provider->closestContainer()->getTitle() . '")'; + } + + $title = sprintf( + $lng->txt($subject_key), + $provider->getForumTitle(), + $container_text, + $provider->getThreadTitle() + ); + + $description = match ($notification_type) { + ilForumMailNotification::TYPE_THREAD_DELETED => sprintf( + $lng->txt('thread_deleted_by'), + $provider->getDeletedBy(), + $provider->getForumTitle() + ), + ilForumMailNotification::TYPE_POST_NEW => sprintf( + $lng->txt('frm_noti_new_post'), + $provider->getForumTitle() + ), + ilForumMailNotification::TYPE_POST_ACTIVATION => $lng->txt('forums_post_activation_mail'), + ilForumMailNotification::TYPE_POST_ANSWERED => $lng->txt('forum_post_replied'), + ilForumMailNotification::TYPE_POST_UPDATED => sprintf( + $lng->txt('post_updated_by'), + $provider->getPostUpdateUserName($lng), + $provider->getForumTitle() + ), + ilForumMailNotification::TYPE_POST_CENSORED => sprintf( + $lng->txt('post_censored_by'), + $provider->getPostUpdateUserName($lng), + $provider->getForumTitle() + ), + ilForumMailNotification::TYPE_POST_UNCENSORED => sprintf( + $lng->txt('post_uncensored_by'), + $provider->getPostUpdateUserName($lng) + ), + ilForumMailNotification::TYPE_POST_DELETED => sprintf( + $lng->txt('post_deleted_by'), + $provider->getDeletedBy(), + $provider->getForumTitle() + ), + default => null, + }; + + if ($description === null) { + return null; + } + + return [ + 'title' => $title, + 'description' => $description, + ]; + } + + private function getSubjectLanguageKey(int $notification_type): ?string + { + return match ($notification_type) { + ilForumMailNotification::TYPE_THREAD_DELETED => 'frm_noti_subject_del_thread', + ilForumMailNotification::TYPE_POST_NEW => 'frm_noti_subject_new_post', + ilForumMailNotification::TYPE_POST_ACTIVATION => 'frm_noti_subject_act_post', + ilForumMailNotification::TYPE_POST_ANSWERED => 'frm_noti_subject_answ_post', + ilForumMailNotification::TYPE_POST_UPDATED => 'frm_noti_subject_upt_post', + ilForumMailNotification::TYPE_POST_CENSORED => 'frm_noti_subject_cens_post', + ilForumMailNotification::TYPE_POST_UNCENSORED => 'frm_noti_subject_uncens_post', + ilForumMailNotification::TYPE_POST_DELETED => 'frm_noti_subject_del_post', + default => null, + }; + } + + private function buildPushLink(ilForumNotificationMailData $provider, ilLanguage $lng): ilNotificationLink + { + $url = rtrim(ilUtil::_getHttpPath(), '/') . '/goto.php?target=frm_' . implode('_', [ + $provider->getRefId(), + $provider->getThreadId(), + $provider->getPostId(), + ]) . '&client_id=' . CLIENT_ID; + + return new ilNotificationLink( + new ilNotificationParameter(sprintf($lng->txt('forums_notification_show_post'), $provider->getPostTitle()), [], 'forum'), + $url + ); + } + + private function getPushProvider(): NotificationsPushProvider + { + return $this->push_provider ??= new NotificationsPushProvider($this); + } +} diff --git a/components/ILIAS/Forum/classes/class.ilForumAppEventListener.php b/components/ILIAS/Forum/classes/class.ilForumAppEventListener.php index f5a6380be8bc..37e068fbb155 100755 --- a/components/ILIAS/Forum/classes/class.ilForumAppEventListener.php +++ b/components/ILIAS/Forum/classes/class.ilForumAppEventListener.php @@ -546,10 +546,8 @@ public static function sendNotification( ); } - $mailNotification = new ilForumMailEventNotificationSender($provider, $logger); - $mailNotification->setType($notificationTypes); - $mailNotification->setRecipients($recipients); - $mailNotification->send(); + $processor = new ilForumNotificationTaskProcessor($logger); + $processor->run($provider, $notificationTypes, $recipients); } else { $logger->debug('No recipients found, skipped notification delivery.'); } diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index dcb66a5c1591..19eed1e25b6d 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -10164,6 +10164,7 @@ forum#:#frm_default_view#:#Standardansicht forum#:#frm_edit_title#:#Titel bearbeiten forum#:#frm_enable_print_option#:#Druckoption aktivieren forum#:#frm_enable_print_option_desc#:#Wenn diese Funktion deaktiviert ist, kann niemand die Druckoption im Forum auswählen. +forum#:#frm_forum_push_notification_info#:#Foren-Benachrichtigungen werden als Push-Benachrichtigungen statt per E-Mail versendet. Wenn die Push-Benachrichtigung nicht zugestellt werden kann, wird stattdessen eine E-Mail versendet. forum#:#frm_mark_as_read#:#Als gelesen markieren forum#:#frm_mark_as_unread#:#Als ungelesen markieren forum#:#frm_max_notification_age#:#Schwellenwert für Benachrichtigungen @@ -10194,6 +10195,7 @@ forum#:#frm_noti_subject_new_post#:#Neuer Beitrag im Forum "%1$s"%2$s zum Thema forum#:#frm_noti_subject_uncens_post#:#Zensur aufgehoben für einen Beitrag im Forum "%1$s"%2$s zum Thema "%3$s" forum#:#frm_noti_subject_upt_post#:#Beitrag im Forum "%1$s"%2$s zum Thema "%3$s" geändert forum#:#frm_notification_activated#:#Benachrichtigung aktiviert +forum#:#frm_notification_bg_task_title#:#Foren-Benachrichtigungen forum#:#frm_notification_deactivated#:#Benachrichtigung deaktiviert forum#:#frm_post_not_activated_yet#:#Der Beitrag wurde noch nicht freigeschaltet. forum#:#frm_posts#:#Forenbeiträge diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index b88393331fde..c4ebef32fc8e 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -10138,6 +10138,7 @@ forum#:#frm_default_view#:#Default View forum#:#frm_edit_title#:#Edit Title forum#:#frm_enable_print_option#:#Enable print option forum#:#frm_enable_print_option_desc#:#If disabled, nobody is able to select the print option in Forum. +forum#:#frm_forum_push_notification_info#:#Forum notifications are sent as push notifications instead of e-mail. If the push notification cannot be delivered, an e-mail will be sent instead. forum#:#frm_mark_as_read#:#Mark as read forum#:#frm_mark_as_unread#:#Mark as unread forum#:#frm_max_notification_age#:#Max. Notification Age @@ -10168,6 +10169,7 @@ forum#:#frm_noti_subject_new_post#:#New post in forum "%1$s"%2$s, thread "%3$s" forum#:#frm_noti_subject_uncens_post#:#Post uncensored in forum "%1$s"%2$s, thread "%3$s" forum#:#frm_noti_subject_upt_post#:#Post modified in forum "%1$s"%2$s, thread "%3$s" forum#:#frm_notification_activated#:#Notification Activated +forum#:#frm_notification_bg_task_title#:#Forum notification delivery forum#:#frm_notification_deactivated#:#Notification Deactivated forum#:#frm_post_not_activated_yet#:#Not activated yet. forum#:#frm_posts#:#Forum Posts