Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
SendDailyNotificationsCommand
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
6 / 6
20
100.00% covered (success)
100.00%
1 / 1
 getAllowedAppEnvs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getNotificationSubscriptions
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 sendNotifications
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 sendNotificationToSubscription
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2
3namespace Olz\Command;
4
5use Olz\Command\Common\OlzCommand;
6use Olz\Command\SendDailyNotificationsCommand\DailySummaryGetter;
7use Olz\Command\SendDailyNotificationsCommand\DeadlineWarningGetter;
8use Olz\Command\SendDailyNotificationsCommand\EmailConfigurationReminderGetter;
9use Olz\Command\SendDailyNotificationsCommand\MonthlyPreviewGetter;
10use Olz\Command\SendDailyNotificationsCommand\Notification;
11use Olz\Command\SendDailyNotificationsCommand\NotificationGetterInterface;
12use Olz\Command\SendDailyNotificationsCommand\RoleReminderGetter;
13use Olz\Command\SendDailyNotificationsCommand\TelegramConfigurationReminderGetter;
14use Olz\Command\SendDailyNotificationsCommand\WeeklyPreviewGetter;
15use Olz\Command\SendDailyNotificationsCommand\WeeklySummaryGetter;
16use Olz\Entity\NotificationSubscription;
17use Olz\Entity\TelegramLink;
18use Symfony\Component\Console\Attribute\AsCommand;
19use Symfony\Component\Console\Command\Command;
20use Symfony\Component\Console\Input\InputInterface;
21use Symfony\Component\Console\Output\OutputInterface;
22use Symfony\Component\Mime\Email;
23
24#[AsCommand(name: 'olz:send-daily-notifications')]
25class SendDailyNotificationsCommand extends OlzCommand {
26    /** @return array<string> */
27    protected function getAllowedAppEnvs(): array {
28        return ['dev', 'test', 'staging', 'prod'];
29    }
30
31    /** @var array<string, NotificationGetterInterface> */
32    protected array $notification_getter_by_type;
33
34    public function __construct() {
35        parent::__construct();
36        $this->notification_getter_by_type = [
37            NotificationSubscription::TYPE_DAILY_SUMMARY => new DailySummaryGetter(),
38            NotificationSubscription::TYPE_DEADLINE_WARNING => new DeadlineWarningGetter(),
39            NotificationSubscription::TYPE_EMAIL_CONFIG_REMINDER => new EmailConfigurationReminderGetter(),
40            NotificationSubscription::TYPE_MONTHLY_PREVIEW => new MonthlyPreviewGetter(),
41            NotificationSubscription::TYPE_ROLE_REMINDER => new RoleReminderGetter(),
42            NotificationSubscription::TYPE_TELEGRAM_CONFIG_REMINDER => new TelegramConfigurationReminderGetter(),
43            NotificationSubscription::TYPE_WEEKLY_PREVIEW => new WeeklyPreviewGetter(),
44            NotificationSubscription::TYPE_WEEKLY_SUMMARY => new WeeklySummaryGetter(),
45        ];
46    }
47
48    protected function handle(InputInterface $input, OutputInterface $output): int {
49        $this->log()->info("Autogenerating notification subscriptions...");
50        foreach ($this->notification_getter_by_type as $type => $getter) {
51            $getter->autogenerateSubscriptions();
52        }
53
54        $subscriptions_by_type_and_args = $this->getNotificationSubscriptions();
55        foreach ($subscriptions_by_type_and_args as $type => $subscriptions_by_args) {
56            $this->sendNotifications($type, $subscriptions_by_args);
57        }
58
59        return Command::SUCCESS;
60    }
61
62    /** @return array<string, array<?string, array<NotificationSubscription>>> */
63    private function getNotificationSubscriptions(): array {
64        $notification_subscription_repo = $this->entityManager()->getRepository(NotificationSubscription::class);
65        $subscriptions = $notification_subscription_repo->findAll();
66
67        $subscriptions_by_type_and_args = [];
68        foreach ($subscriptions as $subscription) {
69            $notification_type = $subscription->getNotificationType();
70            $notification_args = $subscription->getNotificationTypeArgs();
71            $subscriptions_by_args_of_type = $subscriptions_by_type_and_args[$notification_type] ?? [];
72            $subscriptions_of_type_and_args = $subscriptions_by_args_of_type[$notification_args] ?? [];
73            $subscriptions_of_type_and_args[] = $subscription;
74            $subscriptions_by_args_of_type[$notification_args] = $subscriptions_of_type_and_args;
75            $subscriptions_by_type_and_args[$notification_type] = $subscriptions_by_args_of_type;
76        }
77        return $subscriptions_by_type_and_args;
78    }
79
80    /** @param array<?string, array<NotificationSubscription>> $subscriptions_by_args */
81    private function sendNotifications(string $type, array $subscriptions_by_args): void {
82        $this->log()->info("Sending '{$type}' notifications...");
83
84        $notification_getter = $this->notification_getter_by_type[$type] ?? null;
85        if (!$notification_getter) {
86            $this->log()->critical("Unknown notification type '{$type}'");
87            return;
88        }
89        $notification_getter->setAllUtils($this->getAllUtils()); // TODO: Necessary?
90
91        foreach ($subscriptions_by_args as $args_json => $subscriptions) {
92            $this->log()->info("Getting notification for '{$args_json}'...");
93            $args = json_decode($args_json, true);
94            $notification = $notification_getter->getNotification($args);
95            if ($notification) {
96                foreach ($subscriptions as $subscription) {
97                    $this->sendNotificationToSubscription($notification, $subscription);
98                }
99            } else {
100                $this->log()->info("Nothing to send.");
101            }
102        }
103    }
104
105    private function sendNotificationToSubscription(Notification $notification, NotificationSubscription $subscription): void {
106        $user = $subscription->getUser();
107        $title = $notification->title;
108        $text = $notification->getTextForUser($user);
109        $config = $notification->config;
110        $subscription_id = $subscription->getId();
111        $delivery_type = $subscription->getDeliveryType();
112        $user_id = $user->getId();
113        $this->log()->info("Sending notification {$title} over {$delivery_type} to user ({$user_id})...");
114        switch ($delivery_type) {
115            case NotificationSubscription::DELIVERY_EMAIL:
116                try {
117                    $email = (new Email())->subject("[OLZ] {$title}");
118                    $email = $this->emailUtils()->buildOlzEmail($email, $user, $text, $config);
119                    $this->emailUtils()->send($email);
120                    $this->log()->info("Email sent to user ({$user_id}): {$title}");
121                } catch (\Throwable $th) {
122                    $th_class = get_class($th);
123                    $message = $th->getMessage();
124                    $this->log()->critical("Error sending email to user ({$user_id}): [{$th_class}{$message}", []);
125                }
126                break;
127            case NotificationSubscription::DELIVERY_TELEGRAM:
128                $telegram_link_repo = $this->entityManager()->getRepository(TelegramLink::class);
129                $telegram_link = $telegram_link_repo->findOneBy(['user' => $user]);
130                if (!$telegram_link) {
131                    $this->log()->notice("User ({$user_id}) has no telegram link, but a subscription ({$subscription_id})");
132                    return;
133                }
134                $user_chat_id = $telegram_link->getTelegramChatId();
135                if (!$user_chat_id) {
136                    $this->log()->critical("User ({$user_id}) has a telegram link without chat ID, but a subscription ({$subscription_id})");
137                    return;
138                }
139                $html_title = $this->telegramUtils()->renderMarkdown($title);
140                $html_text = $this->telegramUtils()->renderMarkdown($text);
141                try {
142                    $this->telegramUtils()->callTelegramApi('sendMessage', [
143                        'chat_id' => $user_chat_id,
144                        'parse_mode' => 'HTML',
145                        'text' => "<b>{$html_title}</b>\n\n{$html_text}",
146                        'disable_web_page_preview' => true,
147                    ]);
148                    $this->log()->info("Telegram sent to user ({$user_id}): {$title}");
149                } catch (\Throwable $th) {
150                    $th_class = get_class($th);
151                    $message = $th->getMessage();
152                    $this->log()->notice("Error sending telegram to user ({$user_id}): [{$th_class}{$message}", []);
153                }
154                break;
155            default:
156                $this->log()->critical("Unknown delivery type '{$delivery_type}'");
157                break;
158        }
159    }
160}