Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.02% covered (warning)
89.02%
73 / 82
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseSendNotificationsCommand
89.02% covered (warning)
89.02%
73 / 82
83.33% covered (warning)
83.33%
5 / 6
22.64
0.00% covered (danger)
0.00%
0 / 1
 getAllowedAppEnvs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNotificationSubscriptionType
n/a
0 / 0
n/a
0 / 0
0
 autogenerateSubscriptions
n/a
0 / 0
n/a
0 / 0
0
 getNotifications
n/a
0 / 0
n/a
0 / 0
0
 getNonReminderNotificationTypes
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 handle
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getNotificationSubscriptions
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 sendNotifications
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 sendNotificationToSubscription
81.25% covered (warning)
81.25%
39 / 48
0.00% covered (danger)
0.00%
0 / 1
10.66
1<?php
2
3namespace Olz\Command\Notifications\Common;
4
5use Olz\Command\Common\OlzCommand;
6use Olz\Constants\NotificationDeliveryType;
7use Olz\Constants\NotificationType;
8use Olz\Entity\NotificationSubscription;
9use Olz\Entity\TelegramLink;
10use Symfony\Component\Console\Command\Command;
11use Symfony\Component\Console\Input\InputInterface;
12use Symfony\Component\Console\Output\OutputInterface;
13use Symfony\Component\Mime\Email;
14
15abstract class BaseSendNotificationsCommand extends OlzCommand {
16    /** @return array<string> */
17    protected function getAllowedAppEnvs(): array {
18        return ['dev', 'test', 'staging', 'prod'];
19    }
20
21    abstract public function getNotificationSubscriptionType(): NotificationType;
22
23    abstract public function autogenerateSubscriptions(): void;
24
25    /**
26     * @param array<string, mixed> $args
27     *
28     * @return array<Notification>
29     */
30    abstract public function getNotifications(array $args): array;
31
32    /** @return array<NotificationType> */
33    protected function getNonReminderNotificationTypes(): array {
34        return array_values(array_filter(
35            NotificationType::cases(),
36            function ($notification_type): bool {
37                return
38                    $notification_type !== NotificationType::EMAIL_CONFIG_REMINDER
39                    && $notification_type !== NotificationType::TELEGRAM_CONFIG_REMINDER
40                    && $notification_type !== NotificationType::ROLE_REMINDER;
41            }
42        ));
43    }
44
45    protected function handle(InputInterface $input, OutputInterface $output): int {
46        $this->autogenerateSubscriptions();
47
48        $subscriptions_by_args = $this->getNotificationSubscriptions();
49        $this->sendNotifications($subscriptions_by_args);
50
51        return Command::SUCCESS;
52    }
53
54    /** @return array<string, array<NotificationSubscription>> */
55    private function getNotificationSubscriptions(): array {
56        $type = $this->getNotificationSubscriptionType();
57        $notification_subscription_repo = $this->entityManager()->getRepository(NotificationSubscription::class);
58        $subscriptions = $notification_subscription_repo->findBy(['notification_type' => $type]);
59
60        $subscriptions_by_args = [];
61        foreach ($subscriptions as $subscription) {
62            $notification_args = $subscription->getNotificationTypeArgs() ?? '';
63            $subscriptions_of_args = $subscriptions_by_args[$notification_args] ?? [];
64            $subscriptions_of_args[] = $subscription;
65            $subscriptions_by_args[$notification_args] = $subscriptions_of_args;
66        }
67        return $subscriptions_by_args;
68    }
69
70    /** @param array<string, array<NotificationSubscription>> $subscriptions_by_args */
71    private function sendNotifications(array $subscriptions_by_args): void {
72        $this->log()->info("Sending '{$this->getNotificationSubscriptionType()->value}' notifications...");
73        foreach ($subscriptions_by_args as $args_json => $subscriptions) {
74            $this->log()->info("Getting notifications for '{$args_json}'...");
75            $args = json_decode($args_json, true);
76            $notifications = $this->getNotifications($args);
77            if ($notifications === []) {
78                $this->log()->info("Nothing to send.");
79            }
80            foreach ($notifications as $notification) {
81                foreach ($subscriptions as $subscription) {
82                    $this->sendNotificationToSubscription($notification, $subscription);
83                }
84            }
85        }
86    }
87
88    private function sendNotificationToSubscription(Notification $notification, NotificationSubscription $subscription): void {
89        $user = $subscription->getUser();
90        $title = $notification->title;
91        $text = $notification->getTextForUser($user);
92        $config = $notification->config;
93        $subscription_id = $subscription->getId();
94        $delivery_type = $subscription->getDeliveryType();
95        $user_id = $user->getId();
96        $this->log()->info("Sending notification {$title} over {$delivery_type->value} to user ({$user_id})...");
97        if (($config['recipient_user_ids'] ?? null) !== null) {
98            $enc_recipient_user_ids = json_encode($config['recipient_user_ids']) ?: '[]';
99            $this->log()->notice("DRY RUN recipient_user_ids = {$enc_recipient_user_ids}", []);
100            return;
101        }
102        switch ($delivery_type) {
103            case NotificationDeliveryType::EMAIL:
104                try {
105                    $email = (new Email())->subject("[OLZ] {$title}");
106                    $email = $this->emailUtils()->buildOlzEmail($email, $user, $text, $config);
107                    $this->emailUtils()->send($email);
108                    $this->log()->info("Email sent to user ({$user_id}): {$title}");
109                } catch (\Throwable $th) {
110                    $th_class = get_class($th);
111                    $message = $th->getMessage();
112                    $this->log()->critical("Error sending email to user ({$user_id}): [{$th_class}] {$message}", []);
113                }
114                break;
115            case NotificationDeliveryType::TELEGRAM:
116                $telegram_link_repo = $this->entityManager()->getRepository(TelegramLink::class);
117                $telegram_link = $telegram_link_repo->findOneBy(['user' => $user]);
118                if (!$telegram_link) {
119                    $this->log()->notice("User ({$user_id}) has no telegram link, but a subscription ({$subscription_id})");
120                    return;
121                }
122                $user_chat_id = $telegram_link->getTelegramChatId();
123                if (!$user_chat_id) {
124                    $this->log()->critical("User ({$user_id}) has a telegram link without chat ID, but a subscription ({$subscription_id})");
125                    return;
126                }
127                $html_title = $this->telegramUtils()->renderMarkdown($title);
128                $html_text = $this->telegramUtils()->renderMarkdown($text);
129                try {
130                    $this->telegramUtils()->callTelegramApi('sendMessage', [
131                        'chat_id' => $user_chat_id,
132                        'parse_mode' => 'HTML',
133                        'text' => "<b>{$html_title}</b>\n\n{$html_text}",
134                        'disable_web_page_preview' => true,
135                    ]);
136                    $this->log()->info("Telegram sent to user ({$user_id}): {$title}");
137                } catch (\Throwable $th) {
138                    $th_class = get_class($th);
139                    $message = $th->getMessage();
140                    $this->log()->notice("Error sending telegram to user ({$user_id}): [{$th_class}] {$message}", []);
141                }
142                break;
143            default:
144                $this->log()->critical("Unknown delivery type '{$delivery_type->value}'");
145                break;
146        }
147    }
148}