Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
50.00% |
64 / 128 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
| SendTerminNotificationCommand | |
50.00% |
64 / 128 |
|
25.00% |
1 / 4 |
151.12 | |
0.00% |
0 / 1 |
| getNotificationSubscriptionType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| autogenerateSubscriptions | |
96.30% |
26 / 27 |
|
0.00% |
0 / 1 |
11 | |||
| getTerminNotificationState | |
97.06% |
33 / 34 |
|
0.00% |
0 / 1 |
7 | |||
| getNotifications | |
6.06% |
4 / 66 |
|
0.00% |
0 / 1 |
131.37 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Olz\Command\Notifications; |
| 4 | |
| 5 | use Doctrine\ORM\Query\ResultSetMapping; |
| 6 | use Olz\Command\Notifications\Common\BaseSendNotificationsCommand; |
| 7 | use Olz\Command\Notifications\Common\Notification; |
| 8 | use Olz\Constants\NotificationDeliveryType; |
| 9 | use Olz\Constants\NotificationType; |
| 10 | use Olz\Entity\NotificationSubscription; |
| 11 | use Olz\Entity\Roles\Role; |
| 12 | use Olz\Entity\TelegramLink; |
| 13 | use Olz\Entity\Throttling; |
| 14 | use Olz\Entity\Users\User; |
| 15 | use Olz\Utils\WithUtilsTrait; |
| 16 | use Symfony\Component\Console\Attribute\AsCommand; |
| 17 | |
| 18 | #[AsCommand(name: 'olz:send-termin-notifications')] |
| 19 | class SendTerminNotificationCommand extends BaseSendNotificationsCommand { |
| 20 | use WithUtilsTrait; |
| 21 | |
| 22 | protected \DateTime $today; |
| 23 | protected \DateTime $yesterday; |
| 24 | |
| 25 | public function getNotificationSubscriptionType(): NotificationType { |
| 26 | return NotificationType::TERMIN_NOTIFICATION; |
| 27 | } |
| 28 | |
| 29 | public function autogenerateSubscriptions(): void { |
| 30 | $this->log()->info("Generating termin notification subscriptions..."); |
| 31 | $termin_notifications_state = $this->getTerminNotificationState(); |
| 32 | |
| 33 | $now_datetime = new \DateTime($this->dateUtils()->getIsoNow()); |
| 34 | $notification_subscription_repo = $this->entityManager()->getRepository(NotificationSubscription::class); |
| 35 | $user_repo = $this->entityManager()->getRepository(User::class); |
| 36 | foreach ($termin_notifications_state as $user_id => $user_state) { |
| 37 | foreach ($user_state as $delivery_type => $state) { |
| 38 | $subscription_id = $state['subscription_id'] ?? false; |
| 39 | $needs_subscription = $state['needs_subscription'] ?? false; |
| 40 | $user = $user_repo->findOneBy(['id' => $user_id]); |
| 41 | if (!$user) { |
| 42 | $this->log()->warning("No user (ID:{$user_id}) for termin notification"); |
| 43 | } |
| 44 | if ($needs_subscription && !$subscription_id && $user) { |
| 45 | $this->log()->info("Generating termin notification subscription for '{$user}' via '{$delivery_type}'..."); |
| 46 | $subscription = new NotificationSubscription(); |
| 47 | $subscription->setUser($user); |
| 48 | $subscription->setDeliveryType(NotificationDeliveryType::from($delivery_type)); |
| 49 | $subscription->setNotificationType(NotificationType::TERMIN_NOTIFICATION); |
| 50 | $subscription->setNotificationTypeArgs(json_encode(['cancelled' => false]) ?: '{}'); |
| 51 | $subscription->setCreatedAt($now_datetime); |
| 52 | $this->entityManager()->persist($subscription); |
| 53 | } |
| 54 | if ($subscription_id && !$needs_subscription) { |
| 55 | $this->log()->info("Removing termin notification subscription ({$subscription_id}) for '{$user}' via '{$delivery_type}'..."); |
| 56 | $subscription = $notification_subscription_repo->findOneBy(['id' => $subscription_id]); |
| 57 | if ($subscription) { |
| 58 | $this->entityManager()->remove($subscription); |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | $this->entityManager()->flush(); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * @return array<int, array<NotificationDeliveryType, array{ |
| 68 | * subscription_id?: int, |
| 69 | * needs_subscription?: bool, |
| 70 | * }>> */ |
| 71 | protected function getTerminNotificationState(): array { |
| 72 | $termin_notifications_state = []; |
| 73 | |
| 74 | // Find users with existing termin notification subscriptions. |
| 75 | $notification_subscription_repo = $this->entityManager()->getRepository(NotificationSubscription::class); |
| 76 | $termin_notification_subscriptions = $notification_subscription_repo->findBy([ |
| 77 | 'notification_type' => NotificationType::TERMIN_NOTIFICATION, |
| 78 | ]); |
| 79 | foreach ($termin_notification_subscriptions as $subscription) { |
| 80 | $user_id = $subscription->getUser()->getId() ?: 0; |
| 81 | $delivery_type = $subscription->getDeliveryType()->value; |
| 82 | $user_state = $termin_notifications_state[$user_id][$delivery_type] ?? []; |
| 83 | $subscription_id = $subscription->getId(); |
| 84 | $this->generalUtils()->checkNotNull($subscription_id, "No subscription ID"); |
| 85 | $user_state['subscription_id'] = $subscription_id; |
| 86 | $termin_notifications_state[$user_id][$delivery_type] = $user_state; |
| 87 | } |
| 88 | |
| 89 | // Find users who should have termin notification subscriptions. |
| 90 | $user_repo = $this->entityManager()->getRepository(User::class); |
| 91 | $users_with_email = $user_repo->getUsersWithLogin(); |
| 92 | foreach ($users_with_email as $user_with_email) { |
| 93 | $user_id = $user_with_email->getId(); |
| 94 | if (!$user_id) { |
| 95 | continue; |
| 96 | } |
| 97 | $delivery_type = NotificationDeliveryType::EMAIL->value; |
| 98 | $user_state = $termin_notifications_state[$user_id][$delivery_type] ?? []; |
| 99 | $user_state['needs_subscription'] = true; |
| 100 | $termin_notifications_state[$user_id][$delivery_type] = $user_state; |
| 101 | } |
| 102 | $telegram_link_repo = $this->entityManager()->getRepository(TelegramLink::class); |
| 103 | $telegram_links = $telegram_link_repo->getActivatedTelegramLinks(); |
| 104 | foreach ($telegram_links as $telegram_link) { |
| 105 | $user_id = $telegram_link->getUser()?->getId(); |
| 106 | if (!$user_id) { |
| 107 | continue; |
| 108 | } |
| 109 | $delivery_type = NotificationDeliveryType::TELEGRAM->value; |
| 110 | $user_state = $termin_notifications_state[$user_id][$delivery_type] ?? []; |
| 111 | $user_state['needs_subscription'] = true; |
| 112 | $termin_notifications_state[$user_id][$delivery_type] = $user_state; |
| 113 | } |
| 114 | |
| 115 | return $termin_notifications_state; |
| 116 | } |
| 117 | |
| 118 | // --- |
| 119 | |
| 120 | public function getNotifications(array $args): array { |
| 121 | if ($args['cancelled'] ?? false) { |
| 122 | return []; |
| 123 | } |
| 124 | |
| 125 | $ident = 'send-termin-notifications'; |
| 126 | $throttling_repo = $this->entityManager()->getRepository(Throttling::class); |
| 127 | $last_occurrence = $throttling_repo->getLastOccurrenceOf($ident); |
| 128 | $now = new \DateTime($this->dateUtils()->getIsoNow()); |
| 129 | |
| 130 | // TODO: Add volunteers and participants |
| 131 | $sql = <<<'ZZZZZZZZZZ' |
| 132 | WITH |
| 133 | computed AS ( |
| 134 | SELECT |
| 135 | ADDTIME( |
| 136 | ADDDATE(t.start_date, INTERVAL -tn.fires_earlier_seconds SECOND), |
| 137 | COALESCE(t.start_time, '12:00:00') |
| 138 | ) AS fires_at, |
| 139 | tn.title AS notification_title, |
| 140 | tn.content AS notification_content, |
| 141 | tn.recipient_user_id AS recipient_user_id, |
| 142 | tn.recipient_role_id AS recipient_role_id, |
| 143 | tn.recipient_termin_owner_user AS recipient_termin_owner_user, |
| 144 | tn.recipient_termin_owner_role AS recipient_termin_owner_role, |
| 145 | tn.recipient_termin_organizer AS recipient_termin_organizer, |
| 146 | tn.recipient_termin_volunteers AS recipient_termin_volunteers, |
| 147 | tn.recipient_termin_participants AS recipient_termin_participants, |
| 148 | t.title AS termin_title, |
| 149 | t.start_date AS termin_date, |
| 150 | t.id AS termin_id, |
| 151 | t.owner_user_id AS termin_owner_user_id, |
| 152 | t.owner_role_id AS termin_owner_role_id, |
| 153 | t.organizer_user_id AS termin_organizer_user_id |
| 154 | FROM |
| 155 | termin_notifications tn |
| 156 | JOIN termine t ON (tn.termin_id = t.id) |
| 157 | ) |
| 158 | SELECT * |
| 159 | FROM computed |
| 160 | WHERE fires_at < :now AND fires_at >= :last_occurrence |
| 161 | ZZZZZZZZZZ; |
| 162 | |
| 163 | $rsm = new ResultSetMapping(); |
| 164 | $rsm->addScalarResult('fires_at', 'firesAt'); |
| 165 | $rsm->addScalarResult('notification_title', 'notificationTitle'); |
| 166 | $rsm->addScalarResult('notification_content', 'notificationContent'); |
| 167 | $rsm->addScalarResult('recipient_user_id', 'recipientUserId'); |
| 168 | $rsm->addScalarResult('recipient_role_id', 'recipientRoleId'); |
| 169 | $rsm->addScalarResult('recipient_termin_owner_user', 'recipientTerminOwnerUser'); |
| 170 | $rsm->addScalarResult('recipient_termin_owner_role', 'recipientTerminOwnerRole'); |
| 171 | $rsm->addScalarResult('recipient_termin_organizer', 'recipientTerminOrganizer'); |
| 172 | $rsm->addScalarResult('recipient_termin_volunteers', 'recipientTerminVolunteers'); |
| 173 | $rsm->addScalarResult('recipient_termin_participants', 'recipientTerminParticipants'); |
| 174 | $rsm->addScalarResult('termin_title', 'terminTitle'); |
| 175 | $rsm->addScalarResult('termin_date', 'terminDate'); |
| 176 | $rsm->addScalarResult('termin_id', 'terminId'); |
| 177 | $rsm->addScalarResult('termin_owner_user_id', 'terminOwnerUserId'); |
| 178 | $rsm->addScalarResult('termin_owner_role_id', 'terminOwnerRoleId'); |
| 179 | $rsm->addScalarResult('termin_organizer_user_id', 'terminOrganizerUserId'); |
| 180 | |
| 181 | $role_repo = $this->entityManager()->getRepository(Role::class); |
| 182 | $query = $this->entityManager()->createNativeQuery($sql, $rsm); |
| 183 | $query->setParameter('now', $now, 'datetime'); |
| 184 | $query->setParameter('last_occurrence', $last_occurrence, 'datetime'); |
| 185 | $result = $query->execute(); |
| 186 | |
| 187 | $base_href = $this->envUtils()->getBaseHref(); |
| 188 | $code_href = $this->envUtils()->getCodeHref(); |
| 189 | $termine_url = "{$base_href}{$code_href}termine"; |
| 190 | |
| 191 | $notifications = []; |
| 192 | foreach ($result as $row) { |
| 193 | $date = $this->dateUtils()->compactDate($row['terminDate']); |
| 194 | $title = "Termin-Erinnerung: {$row['notificationTitle']}"; |
| 195 | $text = <<<ZZZZZZZZZZ |
| 196 | Hallo %%userFirstName%%, |
| 197 | |
| 198 | Dies ist eine Termin-Erinnerung für den Termin [{$row['terminTitle']}]({$termine_url}/{$row['terminId']}) vom {$date}: |
| 199 | |
| 200 | {$row['notificationContent']} |
| 201 | |
| 202 | ZZZZZZZZZZ; |
| 203 | $recipient_user_ids = []; |
| 204 | if ($row['recipientUserId'] ?? false) { |
| 205 | $recipient_user_ids[] = $row['recipientUserId']; |
| 206 | } |
| 207 | if ($row['recipientRoleId'] ?? false) { |
| 208 | $role = $role_repo->findOneBy(['id' => $row['recipientRoleId']]); |
| 209 | foreach (($role?->getUsers() ?? []) as $user) { |
| 210 | $recipient_user_ids[] = $user->getId(); |
| 211 | } |
| 212 | } |
| 213 | if ($row['recipientTerminOwnerUser'] ?? false) { |
| 214 | $recipient_user_ids[] = $row['terminOwnerUserId']; |
| 215 | } |
| 216 | if ($row['recipientTerminOwnerRole'] ?? false) { |
| 217 | $role = $role_repo->findOneBy(['id' => $row['terminOwnerRoleId']]); |
| 218 | foreach (($role?->getUsers() ?? []) as $user) { |
| 219 | $recipient_user_ids[] = $user->getId(); |
| 220 | } |
| 221 | } |
| 222 | if ($row['recipientTerminOrganizer'] ?? false) { |
| 223 | $recipient_user_ids[] = $row['terminOrganizerUserId']; |
| 224 | } |
| 225 | if ($row['recipientTerminVolunteers'] ?? false) { |
| 226 | // TODO |
| 227 | $this->log()->notice("Termin notification to recipientTerminVolunteers: not implemented"); |
| 228 | } |
| 229 | if ($row['recipientTerminParticipants'] ?? false) { |
| 230 | // TODO |
| 231 | $this->log()->notice("Termin notification to recipientTerminParticipants: not implemented"); |
| 232 | } |
| 233 | $notifications[] = new Notification($title, $text, [ |
| 234 | 'notification_type' => NotificationType::TERMIN_NOTIFICATION, |
| 235 | 'recipient_user_ids' => $recipient_user_ids, |
| 236 | ]); |
| 237 | } |
| 238 | |
| 239 | return $notifications; |
| 240 | } |
| 241 | } |