Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
NotificationsSendCommand
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 6
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 configure
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
462
 getNextTimeToSendFromPreferences
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 getEnglishDayFromNumber
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getEnglishNumber
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Domain\Notification\Command;
4
5use App\Domain\Notification\Model\Notification;
6use App\Domain\Notification\Model\NotificationUser;
7use App\Domain\User\Model\EmailNotificationPreference;
8use App\Domain\User\Model\User;
9use App\Domain\User\Repository\User as UserRepository;
10use App\Infrastructure\ORM\Notification\Repository\NotificationUser as NotificationUserRepository;
11use Doctrine\Common\Collections\ArrayCollection;
12use Symfony\Bridge\Twig\Mime\TemplatedEmail;
13use Symfony\Component\Console\Command\Command;
14use Symfony\Component\Console\Input\InputInterface;
15use Symfony\Component\Console\Output\OutputInterface;
16use Symfony\Component\Console\Style\SymfonyStyle;
17use Symfony\Component\Mailer\MailerInterface;
18use Symfony\Component\Mime\Address;
19use Symfony\Contracts\Translation\TranslatorInterface;
20
21class NotificationsSendCommand extends Command
22{
23    protected static $defaultName        = 'notifications:send';
24    protected static $defaultDescription = 'Send notifications by email to users that have requested it.';
25
26    protected UserRepository $userRepository;
27    protected NotificationUserRepository $notificationUserRepository;
28
29    protected MailerInterface $mailer;
30    protected TranslatorInterface $translator;
31
32    private bool $activeNotifications;
33
34    public function __construct(
35        UserRepository $userRepository,
36        NotificationUserRepository $notificationUserRepository,
37        MailerInterface $mailer,
38        TranslatorInterface $translator,
39        bool $activeNotifications,
40        ?string $name = null,
41    ) {
42        parent::__construct($name);
43        $this->userRepository             = $userRepository;
44        $this->mailer                     = $mailer;
45        $this->notificationUserRepository = $notificationUserRepository;
46        $this->translator                 = $translator;
47        $this->activeNotifications        = $activeNotifications;
48    }
49
50    protected function configure(): void
51    {
52        $this
53            ->setDescription(self::$defaultDescription)
54        ;
55    }
56
57    protected function execute(InputInterface $input, OutputInterface $output): int
58    {
59        $io = new SymfonyStyle($input, $output);
60
61        if (!$this->activeNotifications) {
62            $io->warning('Notifications are not active. Exiting now');
63            exit(0);
64        }
65
66        // Get all users
67        $users = new ArrayCollection($this->userRepository->findAll());
68        // Foreach user that has email notifications enabled
69
70        // Get all user notifications that have not been sent yet
71
72        $qb = $this->notificationUserRepository->getQueryBuilder();
73        $qb->where('o.active = 0');
74        $qb->andWhere('o.sent = 0');
75        $qb->andWhere('o.mail IS NOT NULL');
76
77        $notificationUsers = $qb->getQuery()->getResult();
78
79        $output->writeln(count($notificationUsers) . ' notifications to send');
80
81        $groupedByMail = [];
82
83        foreach ($notificationUsers as $notificationUser) {
84            if (!isset($groupedByMail[$notificationUser->getMail()])) {
85                $groupedByMail[$notificationUser->getMail()] = [];
86            }
87            $groupedByMail[$notificationUser->getMail()][] = $notificationUser;
88        }
89
90        foreach ($groupedByMail as $mail => $notifUsers) {
91            $notifications = [];
92            /**
93             * @var User $user
94             */
95            $user = $notifUsers[0]->getUser();
96
97            $output->writeln('Prepare notifications for ' . $mail);
98
99            // If not a registered user, everything will be sent by email
100            $prefs = new EmailNotificationPreference();
101            $prefs->setFrequency(EmailNotificationPreference::FREQUENCY_EACH);
102            $prefs->setEnabled(true);
103            $prefs->setLastSent(new \DateTime('2012-01-01 00:00:00'));
104            $prefs->setNotificationMask(0x7FF);
105
106            if ($user && $user->getEmailNotificationPreference()) {
107                // if a registered user, respect their preferences.
108                $prefs = $user->getEmailNotificationPreference();
109            }
110
111            if (
112                !$prefs
113                || !$prefs->getEnabled()
114                || EmailNotificationPreference::FREQUENCY_NONE == $prefs->getFrequency()
115                || 0 === $prefs->getNotificationMask()
116            ) {
117                // Exit if user has email notifications disabled
118                $output->writeln('User ' . $mail . ' has disabled notifications');
119                continue;
120            }
121
122            $nextTimeToSend = $this->getNextTimeToSendFromPreferences($prefs);
123
124            if ($nextTimeToSend->format('Ymdhi') !== date('Ymdhi')) {
125                $output->writeln('Not the time to send. Programmed time is ' . $nextTimeToSend->format('YmdHi') . ' and current time is ' . date('YmdHi'));
126                // Now is not the time to send for this user, abort
127                continue;
128            }
129
130            $lastSent = $prefs->getLastSent();
131            foreach ($notifUsers as $nu) {
132                /*
133                 * @var NotificationUser $notifUser
134                 */
135
136                if ($nu->getCreatedAt() > $lastSent) {
137                    /**
138                     * @var Notification $notif
139                     */
140                    $notif = $nu->getNotification();
141                    // Check if the notification module is is the user preferences mask
142
143                    // Get the raw name of the module
144                    $mod = str_replace('notification.modules.', '', $notif->getModule());
145
146                    $availableModules = array_keys(EmailNotificationPreference::MODULES);
147                    if (in_array($mod, $availableModules) && (EmailNotificationPreference::MODULES[$mod] & $prefs->getNotificationMask())) {
148                        // Current notification module is enabled in user preferences
149                        $notifications[] = $nu->getNotification();
150                    } else {
151                        $output->writeln('User ' . $mail . ' does not have notifications active for module ' . $mod);
152                    }
153                }
154            }
155
156            if (0 === count($notifications)) {
157                $output->writeln('No notifications to send to ' . $mail);
158                continue;
159            }
160
161            $email = new TemplatedEmail();
162            $email->subject($this->translator->trans('notifications.label.email_subject'));
163            $email->to(new Address($mail));
164            $email->htmlTemplate('Notification/Mail/notifications.html.twig');
165            $email->context([
166                'notifications' => $notifications,
167                'user'          => $user,
168            ]);
169            try {
170                $this->mailer->send($email);
171
172                if ($user) {
173                    $prefs->setLastSent(new \DateTime());
174                    $user->setEmailNotificationPreference($prefs);
175                    $this->userRepository->update($user);
176                }
177
178                foreach ($notifUsers as $nu) {
179                    /*
180                     * @var NotificationUser $notifUser
181                     */
182                    if ($nu->getCreatedAt() > $lastSent) {
183                        $nu->setSent(true);
184                        $this->notificationUserRepository->update($nu);
185                    }
186                }
187
188                $output->writeln('Notifications email sent to ' . $mail);
189            } catch (\Exception $e) {
190                // dump($e);
191                $output->writeln('Could not send email to ' . $mail);
192            }
193        }
194
195        // check the date of the last email they were sent
196        // Get all notifications generated after that date
197        // Send them an email with those notifications
198
199        return 0;
200    }
201
202    private function getNextTimeToSendFromPreferences(EmailNotificationPreference $prefs): \DateTime
203    {
204        switch ($prefs->getFrequency()) {
205            case EmailNotificationPreference::FREQUENCY_EACH:
206                return new \DateTime();
207            case EmailNotificationPreference::FREQUENCY_HOUR:
208                $lastSent = $prefs->getLastSent();
209
210                return $lastSent->add(new \DateInterval('PT' . $prefs->getHour() . 'H'));
211            case EmailNotificationPreference::FREQUENCY_DAY:
212                // set cutoff date to today at the specified time
213                return (new \DateTime())->setTime($prefs->getHour(), 0);
214            case EmailNotificationPreference::FREQUENCY_WEEK:
215                $now = new \DateTime();
216
217                // set cutoff date to this week at the specified day and time
218                return (new \DateTime())->setTime($prefs->getHour(), 0)->setISODate($now->format('Y'), $now->format('W'), $prefs->getDay());
219            case EmailNotificationPreference::FREQUENCY_MONTH:
220                // set cutoff date to this months selected day and week
221                return (new \DateTime())->modify($this->getEnglishNumber($prefs->getWeek()) . ' ' . $this->getEnglishDayFromNumber($prefs->getDay()) . ' of this month')->setTime($prefs->getHour(), 0);
222        }
223
224        return new \DateTime();
225    }
226
227    private function getEnglishDayFromNumber(int $day): string
228    {
229        return match ($day) {
230            default => 'monday',
231            2       => 'tuesday',
232            3       => 'wednesday',
233            4       => 'thursday',
234            5       => 'friday',
235            6       => 'saturday',
236            7       => 'sunday',
237        };
238    }
239
240    private function getEnglishNumber(int $num): string
241    {
242        return match ($num) {
243            default => 'first',
244            2       => 'second',
245            3       => 'third',
246            4       => 'fourth',
247        };
248    }
249}