mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
			v4.4.0
			...
			330-notifi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f88f1f1859 | |||
| e6cbba8c63 | |||
| c2782be56a | |||
| cf391d60fe | |||
| acad9d1553 | |||
| 0a19255a22 | |||
| bef5dcce14 | |||
| 33540f58d7 | |||
| cf780b6e36 | |||
| c9c565809a | |||
| c917c42789 | |||
| 1c426f560e | 
| @@ -12,3 +12,7 @@ framework: | ||||
|                 adapter: cache.adapter.redis | ||||
|                 public: false | ||||
|                 default_lifetime: 300 | ||||
|             cache.daily_notifications: | ||||
|                 adapter: cache.adapter.redis | ||||
|                 public: true | ||||
|                 default_lifetime: 90000  # 25 hours | ||||
|   | ||||
| @@ -45,6 +45,27 @@ framework: | ||||
|  | ||||
|                     auto_setup: false | ||||
|  | ||||
|             immediate_email: | ||||
|                 dsn: '%env(MESSENGER_TRANSPORT_DSN)%/priority' | ||||
|                 options: | ||||
|                     queue_name: immediate_notifications | ||||
|                     exchange: | ||||
|                         name: notifications | ||||
|                         type: direct | ||||
|                 retry_strategy: | ||||
|                     max_retries: 3 | ||||
|                     delay: 1000 | ||||
|                     multiplier: 2 | ||||
|  | ||||
|             daily_email: | ||||
|                 dsn: '%env(MESSENGER_TRANSPORT_DSN)%' | ||||
|                 options: | ||||
|                     queue_name: daily_notifications | ||||
|                     exchange: | ||||
|                         name: notifications | ||||
|                         type: direct | ||||
|                 # No automatic consumption - handled by cron job | ||||
|  | ||||
|         routing: | ||||
|             # routes added by chill-bundles recipes | ||||
|             'Chill\CalendarBundle\Messenger\Message\CalendarRangeMessage': async | ||||
| @@ -61,6 +82,9 @@ framework: | ||||
|             'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority | ||||
|             'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async | ||||
|             'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async | ||||
|             'Chill\MainBundle\Notification\Email\SendImmediateNotificationEmailMessage': immediate_email | ||||
|             'Chill\MainBundle\Notification\Email\ScheduleDailyNotificationEmailMessage': daily_email | ||||
|             'Chill\MainBundle\Notification\Email\SendDailyDigestMessage': daily_email | ||||
|             # end of routes added by chill-bundles recipes | ||||
|             # Route your messages to the transports | ||||
|             # 'App\Message\YourMessage': async | ||||
|   | ||||
| @@ -11,8 +11,10 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Controller; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\NotificationComment; | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Chill\MainBundle\Form\NotificationCommentType; | ||||
| use Chill\MainBundle\Form\NotificationType; | ||||
| use Chill\MainBundle\Notification\Exception\NotificationHandlerNotFound; | ||||
| @@ -22,6 +24,9 @@ use Chill\MainBundle\Repository\NotificationRepository; | ||||
| use Chill\MainBundle\Repository\UserRepository; | ||||
| use Chill\MainBundle\Security\Authorization\NotificationVoter; | ||||
| use Chill\MainBundle\Security\ChillSecurity; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||
| @@ -53,11 +58,29 @@ class NotificationController extends AbstractController | ||||
|             throw new BadRequestHttpException('missing entityId parameter'); | ||||
|         } | ||||
|  | ||||
|         $notificationType = ''; | ||||
|  | ||||
|         switch ($request->query->get('entityClass')) { | ||||
|             case Activity::class: | ||||
|                 $notificationType = NotificationFlagEnum::ACTIVITY; | ||||
|                 break; | ||||
|             case AccompanyingPeriod::class: | ||||
|                 $notificationType = NotificationFlagEnum::ACC_COURSE; | ||||
|                 break; | ||||
|             case AccompanyingPeriodWork::class: | ||||
|                 $notificationType = NotificationFlagEnum::ACC_COURSE_WORK; | ||||
|                 break; | ||||
|             case AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class: | ||||
|                 $notificationType = NotificationFlagEnum::ACC_COURSE_WORK_EVAL_DOC; | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         $notification = new Notification(); | ||||
|         $notification | ||||
|             ->setRelatedEntityClass($request->query->get('entityClass')) | ||||
|             ->setRelatedEntityId($request->query->getInt('entityId')) | ||||
|             ->setSender($this->security->getUser()); | ||||
|             ->setSender($this->security->getUser()) | ||||
|             ->setType($notificationType); | ||||
|  | ||||
|         $tos = $request->query->all('tos'); | ||||
|  | ||||
|   | ||||
| @@ -11,14 +11,12 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\MainBundle\Controller; | ||||
|  | ||||
| use Chill\MainBundle\Form\UserPhonenumberType; | ||||
| use Chill\MainBundle\Form\UserProfileType; | ||||
| use Chill\MainBundle\Security\ChillSecurity; | ||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||
| use Symfony\Component\Form\Extension\Core\Type\SubmitType; | ||||
| use Symfony\Component\Form\FormInterface; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; | ||||
| use Symfony\Component\Security\Core\User\UserInterface; | ||||
| use Symfony\Contracts\Translation\TranslatorInterface; | ||||
| use Symfony\Component\Routing\Annotation\Route; | ||||
|  | ||||
| @@ -41,16 +39,21 @@ final class UserProfileController extends AbstractController | ||||
|         } | ||||
|  | ||||
|         $user = $this->security->getUser(); | ||||
|         $editForm = $this->createPhonenumberEditForm($user); | ||||
|         $editForm = $this->createForm(UserProfileType::class, $user); | ||||
|  | ||||
|         $editForm->get('notificationFlags')->setData($user->getNotificationFlags()); | ||||
|  | ||||
|         $editForm->add('submit', SubmitType::class); | ||||
|         $editForm->handleRequest($request); | ||||
|  | ||||
|         if ($editForm->isSubmitted() && $editForm->isValid()) { | ||||
|             $phonenumber = $editForm->get('phonenumber')->getData(); | ||||
|             $notificationFlagsData = $editForm->get('notificationFlags')->getData(); | ||||
|             $user->setNotificationFlags($notificationFlagsData); | ||||
|  | ||||
|             $user->setPhonenumber($phonenumber); | ||||
|  | ||||
|             $this->managerRegistry->getManager()->flush(); | ||||
|             $this->addFlash('success', $this->translator->trans('user.profile.Phonenumber successfully updated!')); | ||||
|             $em = $this->managerRegistry->getManager(); | ||||
|             $em->persist($user); | ||||
|             $em->flush(); | ||||
|             $this->addFlash('success', $this->translator->trans('user.profile.Profile successfully updated!')); | ||||
|  | ||||
|             return $this->redirectToRoute('chill_main_user_profile'); | ||||
|         } | ||||
| @@ -60,13 +63,4 @@ final class UserProfileController extends AbstractController | ||||
|             'form' => $editForm->createView(), | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     private function createPhonenumberEditForm(UserInterface $user): FormInterface | ||||
|     { | ||||
|         return $this->createForm( | ||||
|             UserPhonenumberType::class, | ||||
|             $user, | ||||
|         ) | ||||
|             ->add('submit', SubmitType::class, ['label' => $this->translator->trans('Save')]); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Entity; | ||||
| use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | ||||
| use Doctrine\Common\Collections\Collection; | ||||
| use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\Validator\Constraints as Assert; | ||||
| use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||||
| @@ -24,7 +25,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; | ||||
| #[ORM\Index(name: 'chill_main_notification_related_entity_idx', columns: ['relatedentityclass', 'relatedentityid'])] | ||||
| class Notification implements TrackUpdateInterface | ||||
| { | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)] | ||||
|     #[ORM\Column(type: Types::TEXT, nullable: false)] | ||||
|     private string $accessKey; | ||||
|  | ||||
|     private array $addedAddresses = []; | ||||
| @@ -41,7 +42,7 @@ class Notification implements TrackUpdateInterface | ||||
|      * | ||||
|      * @var array|string[] | ||||
|      */ | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])] | ||||
|     #[ORM\Column(type: Types::JSON, options: ['default' => '[]', 'jsonb' => true])] | ||||
|     private array $addressesEmails = []; | ||||
|  | ||||
|     /** | ||||
| @@ -60,21 +61,21 @@ class Notification implements TrackUpdateInterface | ||||
|     #[ORM\OrderBy(['createdAt' => \Doctrine\Common\Collections\Criteria::ASC])] | ||||
|     private Collection $comments; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)] | ||||
|     #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] | ||||
|     private \DateTimeImmutable $date; | ||||
|  | ||||
|     #[ORM\Id] | ||||
|     #[ORM\GeneratedValue] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] | ||||
|     #[ORM\Column(type: Types::INTEGER)] | ||||
|     private ?int $id = null; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)] | ||||
|     #[ORM\Column(type: Types::TEXT)] | ||||
|     private string $message = ''; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)] | ||||
|     #[ORM\Column(type: Types::STRING, length: 255)] | ||||
|     private string $relatedEntityClass = ''; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] | ||||
|     #[ORM\Column(type: Types::INTEGER)] | ||||
|     private int $relatedEntityId; | ||||
|  | ||||
|     private array $removedAddresses = []; | ||||
| @@ -84,7 +85,7 @@ class Notification implements TrackUpdateInterface | ||||
|     private ?User $sender = null; | ||||
|  | ||||
|     #[Assert\NotBlank(message: 'notification.Title must be defined')] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])] | ||||
|     #[ORM\Column(type: Types::TEXT, options: ['default' => ''])] | ||||
|     private string $title = ''; | ||||
|  | ||||
|     /** | ||||
| @@ -94,12 +95,15 @@ class Notification implements TrackUpdateInterface | ||||
|     #[ORM\JoinTable(name: 'chill_main_notification_addresses_unread')] | ||||
|     private Collection $unreadBy; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)] | ||||
|     #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] | ||||
|     private ?\DateTimeImmutable $updatedAt = null; | ||||
|  | ||||
|     #[ORM\ManyToOne(targetEntity: User::class)] | ||||
|     private ?User $updatedBy = null; | ||||
|  | ||||
|     #[ORM\Column(name: 'type', type: Types::STRING, nullable: true, enumType: NotificationFlagEnum::class)] | ||||
|     private NotificationFlagEnum $type; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->addressees = new ArrayCollection(); | ||||
| @@ -389,4 +393,16 @@ class Notification implements TrackUpdateInterface | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setType(NotificationFlagEnum $type): self | ||||
|     { | ||||
|         $this->type = $type; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function getType(): NotificationFlagEnum | ||||
|     { | ||||
|         return $this->type; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/Bundle/ChillMainBundle/Entity/NotificationFlagEnum.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Bundle/ChillMainBundle/Entity/NotificationFlagEnum.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Entity; | ||||
|  | ||||
| enum NotificationFlagEnum: string | ||||
| { | ||||
|     case REFERRER_ACC_COURSE = 'referrer-acc-course-notif'; | ||||
|     case PERSON_MOVE = 'person-move-notif'; | ||||
|     case ACC_COURSE = 'acc-course-notif'; | ||||
|     case WORKFLOW_TRANS = 'workflow-trans-notif'; | ||||
|     case ACC_COURSE_WORK = 'acc-course-work-notif'; | ||||
|     case ACC_COURSE_WORK_EVAL_DOC = 'acc-course-work-eval-doc-notif'; | ||||
|     case ACTIVITY = 'activity-notif'; | ||||
| } | ||||
| @@ -116,6 +116,9 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter | ||||
|     #[PhonenumberConstraint] | ||||
|     private ?PhoneNumber $phonenumber = null; | ||||
|  | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])] | ||||
|     private $notificationFlags = []; | ||||
|  | ||||
|     /** | ||||
|      * User constructor. | ||||
|      */ | ||||
| @@ -613,4 +616,24 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function getNotificationFlags(): array | ||||
|     { | ||||
|         return $this->notificationFlags; | ||||
|     } | ||||
|  | ||||
|     public function setNotificationFlags(array $notificationFlags) | ||||
|     { | ||||
|         $this->notificationFlags = $notificationFlags; | ||||
|     } | ||||
|  | ||||
|     public function getNotificationFlagData(string $flag): array | ||||
|     { | ||||
|         return $this->notificationFlags[$flag] ?? []; | ||||
|     } | ||||
|  | ||||
|     public function setNotificationFlagData(string $flag, array $data): void | ||||
|     { | ||||
|         $this->notificationFlags[$flag] = $data; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,78 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Form\DataMapper; | ||||
|  | ||||
| use Symfony\Component\Form\DataMapperInterface; | ||||
|  | ||||
| final readonly class NotificationFlagDataMapper implements DataMapperInterface | ||||
| { | ||||
|     private array $notificationFlagProviders; | ||||
|  | ||||
|     public function __construct(array $notificationFlagProviders) | ||||
|     { | ||||
|         $this->notificationFlagProviders = $notificationFlagProviders; | ||||
|     } | ||||
|  | ||||
|     public function mapDataToForms($viewData, $forms): void | ||||
|     { | ||||
|         if (null === $viewData) { | ||||
|             $viewData = []; | ||||
|         } | ||||
|  | ||||
|         $formsArray = iterator_to_array($forms); | ||||
|  | ||||
|         foreach ($this->notificationFlagProviders as $flagProvider) { | ||||
|             $flag = $flagProvider->getFlag(); | ||||
|  | ||||
|             if (isset($formsArray[$flag])) { | ||||
|                 $flagForm = $formsArray[$flag]; | ||||
|  | ||||
|                 $immediateEmailChecked = in_array('immediate-email', $viewData[$flag] ?? []); | ||||
|                 $dailyEmailChecked = in_array('daily-email', $viewData[$flag] ?? []); | ||||
|  | ||||
|                 if ($flagForm->has('immediate_email')) { | ||||
|                     $flagForm->get('immediate_email')->setData($immediateEmailChecked); | ||||
|                 } | ||||
|                 if ($flagForm->has('daily_email')) { | ||||
|                     $flagForm->get('daily_email')->setData($dailyEmailChecked); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function mapFormsToData($forms, &$viewData): void | ||||
|     { | ||||
|         $formsArray = iterator_to_array($forms); | ||||
|         $viewData = []; | ||||
|  | ||||
|         foreach ($this->notificationFlagProviders as $flagProvider) { | ||||
|             $flag = $flagProvider->getFlag(); | ||||
|  | ||||
|             if (isset($formsArray[$flag])) { | ||||
|                 $flagForm = $formsArray[$flag]; | ||||
|                 $viewData[$flag] = []; | ||||
|  | ||||
|                 if ($flagForm['immediate_email']->getData()) { | ||||
|                     $viewData[$flag][] = 'immediate-email'; | ||||
|                 } | ||||
|  | ||||
|                 if ($flagForm['daily_email']->getData()) { | ||||
|                     $viewData[$flag][] = 'daily-email'; | ||||
|                 } | ||||
|  | ||||
|                 if (empty($viewData[$flag])) { | ||||
|                     $viewData[$flag][] = 'no-email'; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Form\Type; | ||||
|  | ||||
| use Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper; | ||||
| use Chill\MainBundle\Notification\NotificationFlagManager; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\CheckboxType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\FormType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
|  | ||||
| class NotificationFlagsType extends AbstractType | ||||
| { | ||||
|     private array $notificationFlagProviders; | ||||
|  | ||||
|     public function __construct(NotificationFlagManager $notificationFlagManager) | ||||
|     { | ||||
|         $this->notificationFlagProviders = $notificationFlagManager->getAllNotificationFlagProviders(); | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options): void | ||||
|     { | ||||
|         $builder->setDataMapper(new NotificationFlagDataMapper($this->notificationFlagProviders)); | ||||
|  | ||||
|         foreach ($this->notificationFlagProviders as $flagProvider) { | ||||
|             $flag = $flagProvider->getFlag(); | ||||
|             $builder->add($flag, FormType::class, [ | ||||
|                 'label' => $flagProvider->getLabel(), | ||||
|                 'required' => false, | ||||
|             ]); | ||||
|  | ||||
|             $builder->get($flag) | ||||
|                 ->add('immediate_email', CheckboxType::class, [ | ||||
|                     'label' => false, | ||||
|                     'required' => false, | ||||
|                     'mapped' => false, // Keep this here for the individual checkboxes | ||||
|                 ]) | ||||
|                 ->add('daily_email', CheckboxType::class, [ | ||||
|                     'label' => false, | ||||
|                     'required' => false, | ||||
|                     'mapped' => false, // Keep this here for the individual checkboxes | ||||
|                 ]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function configureOptions(OptionsResolver $resolver): void | ||||
|     { | ||||
|         $resolver->setDefaults([ | ||||
|             'data_class' => null, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								src/Bundle/ChillMainBundle/Form/UserProfileType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/Bundle/ChillMainBundle/Form/UserProfileType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Form; | ||||
|  | ||||
| use Chill\MainBundle\Form\Type\ChillPhoneNumberType; | ||||
| use Chill\MainBundle\Form\Type\NotificationFlagsType; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
|  | ||||
| class UserProfileType extends AbstractType | ||||
| { | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options) | ||||
|     { | ||||
|         $builder | ||||
|             ->add('phonenumber', ChillPhoneNumberType::class, [ | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('notificationFlags', NotificationFlagsType::class, [ | ||||
|                 'label' => false, | ||||
|                 'mapped' => false, | ||||
|             ]) | ||||
|         ; | ||||
|     } | ||||
|  | ||||
|     public function configureOptions(OptionsResolver $resolver) | ||||
|     { | ||||
|         $resolver->setDefaults([ | ||||
|             'data_class' => \Chill\MainBundle\Entity\User::class, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,70 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailHandlers; | ||||
|  | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationEmailMessage; | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendDailyDigestMessage; | ||||
| use Psr\Cache\InvalidArgumentException; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\Messenger\MessageBusInterface; | ||||
| use Symfony\Component\Messenger\Stamp\DelayStamp; | ||||
| use Symfony\Contracts\Cache\CacheInterface; | ||||
|  | ||||
| readonly class ScheduleDailyNotificationEmailHandler | ||||
| { | ||||
|     public function __construct( | ||||
|         private CacheInterface $dailyNotificationsCache, | ||||
|         private MessageBusInterface $messageBus, | ||||
|         private LoggerInterface $logger, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function __invoke(ScheduleDailyNotificationEmailMessage $message): void | ||||
|     { | ||||
|         $userId = $message->getAddresseeId(); | ||||
|         $notificationId = $message->getNotificationId(); | ||||
|  | ||||
|         // Store notification in cache grouped by user | ||||
|         $cacheKey = "daily_notifications_user_{$userId}"; | ||||
|         $existingNotifications = $this->dailyNotificationsCache->get($cacheKey, function () { | ||||
|             return []; | ||||
|         }); | ||||
|  | ||||
|         $existingNotifications[] = $notificationId; | ||||
|  | ||||
|         $this->dailyNotificationsCache->get($cacheKey, function () use ($existingNotifications) { | ||||
|             return $existingNotifications; | ||||
|         }); | ||||
|  | ||||
|         // Only send the daily digest message if this is the first notification for today otherwise it already exists | ||||
|         if (1 === count($existingNotifications)) { | ||||
|             $digestMessage = new SendDailyDigestMessage($userId); | ||||
|  | ||||
|             // Calculate delay until next 9 AM | ||||
|             $now = new \DateTimeImmutable(); | ||||
|             $nextNineAM = $now->modify('tomorrow 09:00'); | ||||
|             $delay = $nextNineAM->getTimestamp() - $now->getTimestamp(); | ||||
|  | ||||
|             $this->messageBus->dispatch($digestMessage, [ | ||||
|                 new DelayStamp($delay * 1000), | ||||
|             ]); | ||||
|         } | ||||
|  | ||||
|         $this->logger->info('[ScheduleDailyNotificationEmailHandler] Added notification to daily cache', [ | ||||
|             'notification_id' => $notificationId, | ||||
|             'user_id' => $userId, | ||||
|             'total_pending' => count($existingNotifications), | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,66 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailHandlers; | ||||
|  | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendDailyDigestMessage; | ||||
| use Chill\MainBundle\Notification\Email\NotificationMailer; | ||||
| use Chill\MainBundle\Repository\NotificationRepository; | ||||
| use Chill\MainBundle\Repository\UserRepository; | ||||
| use Psr\Cache\InvalidArgumentException; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Contracts\Cache\CacheInterface; | ||||
|  | ||||
| #[AsMessageHandler] | ||||
| class SendDailyDigestHandler | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly CacheInterface $dailyNotificationsCache, | ||||
|         private readonly NotificationRepository $notificationRepository, | ||||
|         private readonly UserRepository $userRepository, | ||||
|         private readonly NotificationMailer $notificationMailer, | ||||
|         private readonly LoggerInterface $logger, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @throws InvalidArgumentException | ||||
|      */ | ||||
|     public function __invoke(SendDailyDigestMessage $message): void | ||||
|     { | ||||
|         $userId = $message->getUserId(); | ||||
|         $cacheKey = "daily_notifications_user_{$userId}"; | ||||
|  | ||||
|         $notificationIds = $this->dailyNotificationsCache->get($cacheKey, []); | ||||
|  | ||||
|         if (empty($notificationIds)) { | ||||
|             $this->logger->info('[SendDailyDigestHandler] No notifications found for user', [ | ||||
|                 'user_id' => $userId, | ||||
|             ]); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $user = $this->userRepository->find($userId); | ||||
|         $notifications = $this->notificationRepository->findBy(['id' => $notificationIds]); | ||||
|  | ||||
|         if ($user && !empty($notifications)) { | ||||
|             $this->notificationMailer->sendDailyDigest($user, $notifications); | ||||
|  | ||||
|             // Clear the cache after sending | ||||
|             $this->dailyNotificationsCache->delete($cacheKey); | ||||
|  | ||||
|             $this->logger->info('[SendDailyDigestHandler] Sent daily digest', [ | ||||
|                 'user_id' => $userId, | ||||
|                 'notification_count' => count($notifications), | ||||
|             ]); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailHandlers; | ||||
|  | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage; | ||||
| use Chill\MainBundle\Notification\Email\NotificationMailer; | ||||
| use Chill\MainBundle\Repository\NotificationRepository; | ||||
| use Chill\MainBundle\Repository\UserRepository; | ||||
| use Psr\Log\LoggerInterface; | ||||
| use Symfony\Component\Mailer\Exception\TransportExceptionInterface; | ||||
| use Symfony\Component\Messenger\Attribute\AsMessageHandler; | ||||
|  | ||||
| #[AsMessageHandler] | ||||
| class SendImmediateNotificationEmailHandler | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly NotificationRepository $notificationRepository, | ||||
|         private readonly UserRepository $userRepository, | ||||
|         private readonly NotificationMailer $notificationMailer, | ||||
|         private readonly LoggerInterface $logger, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * @throws TransportExceptionInterface | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     public function __invoke(SendImmediateNotificationEmailMessage $message): void | ||||
|     { | ||||
|         $notification = $this->notificationRepository->find($message->getNotificationId()); | ||||
|         $addressee = $this->userRepository->find($message->getAddresseeId()); | ||||
|  | ||||
|         if (null === $notification) { | ||||
|             $this->logger->error('[SendImmediateNotificationEmailHandler] Notification not found', [ | ||||
|                 'notification_id' => $message->getNotificationId(), | ||||
|             ]); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (null === $addressee) { | ||||
|             $this->logger->error('[SendImmediateNotificationEmailHandler] Addressee not found', [ | ||||
|                 'addressee_id' => $message->getAddresseeId(), | ||||
|             ]); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             $this->notificationMailer->sendEmailToAddressee($notification, $addressee); | ||||
|         } catch (\Exception $e) { | ||||
|             $this->logger->error('[SendImmediateNotificationEmailHandler] Failed to send email', [ | ||||
|                 'notification_id' => $message->getNotificationId(), | ||||
|                 'addressee_id' => $message->getAddresseeId(), | ||||
|                 'error' => $e->getMessage(), | ||||
|             ]); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailMessages; | ||||
|  | ||||
| readonly class ScheduleDailyNotificationEmailMessage | ||||
| { | ||||
|     public function __construct( | ||||
|         private int $notificationId, | ||||
|         private int $addresseeId, | ||||
|     ) {} | ||||
|  | ||||
|     public function getNotificationId(): int | ||||
|     { | ||||
|         return $this->notificationId; | ||||
|     } | ||||
|  | ||||
|     public function getAddresseeId(): int | ||||
|     { | ||||
|         return $this->addresseeId; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailMessages; | ||||
|  | ||||
| class SendDailyDigestMessage | ||||
| { | ||||
|     public function __construct( | ||||
|         private readonly int $userId, | ||||
|     ) {} | ||||
|  | ||||
|     public function getUserId(): int | ||||
|     { | ||||
|         return $this->userId; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\Email\NotificationEmailMessages; | ||||
|  | ||||
| readonly class SendImmediateNotificationEmailMessage | ||||
| { | ||||
|     public function __construct( | ||||
|         private int $notificationId, | ||||
|         private int $addresseeId, | ||||
|     ) {} | ||||
|  | ||||
|     public function getNotificationId(): int | ||||
|     { | ||||
|         return $this->notificationId; | ||||
|     } | ||||
|  | ||||
|     public function getAddresseeId(): int | ||||
|     { | ||||
|         return $this->addresseeId; | ||||
|     } | ||||
| } | ||||
| @@ -13,6 +13,8 @@ namespace Chill\MainBundle\Notification\Email; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\NotificationComment; | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationEmailMessage; | ||||
| use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage; | ||||
| use Doctrine\ORM\Event\PostPersistEventArgs; | ||||
| use Doctrine\ORM\Event\PostUpdateEventArgs; | ||||
| use Psr\Log\LoggerInterface; | ||||
| @@ -20,11 +22,12 @@ use Symfony\Bridge\Twig\Mime\TemplatedEmail; | ||||
| use Symfony\Component\Mailer\Exception\TransportExceptionInterface; | ||||
| use Symfony\Component\Mailer\MailerInterface; | ||||
| use Symfony\Component\Mime\Email; | ||||
| use Symfony\Component\Messenger\MessageBusInterface; | ||||
| use Symfony\Contracts\Translation\TranslatorInterface; | ||||
|  | ||||
| class NotificationMailer | ||||
| readonly class NotificationMailer | ||||
| { | ||||
|     public function __construct(private readonly MailerInterface $mailer, private readonly LoggerInterface $logger, private readonly TranslatorInterface $translator) {} | ||||
|     public function __construct(private MailerInterface $mailer, private LoggerInterface $logger, private MessageBusInterface $messageBus, private readonly TranslatorInterface $translator) {} | ||||
|  | ||||
|     public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void | ||||
|     { | ||||
| @@ -80,38 +83,152 @@ class NotificationMailer | ||||
|  | ||||
|     private function sendNotificationEmailsToAddresses(Notification $notification): void | ||||
|     { | ||||
|         if (null === $notification->getType()) { | ||||
|             $this->logger->warning('[NotificationMailer] Notification has no type, skipping email processing', [ | ||||
|                 'notification_id' => $notification->getId(), | ||||
|             ]); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         foreach ($notification->getAddressees() as $addressee) { | ||||
|             if (null === $addressee->getEmail()) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if ($notification->isSystem()) { | ||||
|                 $email = new Email(); | ||||
|                 $email | ||||
|                     ->text($notification->getMessage()); | ||||
|             } else { | ||||
|                 $email = new TemplatedEmail(); | ||||
|                 $email | ||||
|                     ->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig') | ||||
|                     ->context([ | ||||
|                         'notification' => $notification, | ||||
|                         'dest' => $addressee, | ||||
|                     ]); | ||||
|             } | ||||
|             $this->processNotificationForAddressee($notification, $addressee); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function processNotificationForAddressee(Notification $notification, $addressee): void | ||||
|     { | ||||
|         $notificationFlags = $addressee->getNotificationFlags(); | ||||
|         $notificationType = $notification->getType(); | ||||
|  | ||||
|         $emailPreference = $notificationFlags[$notificationType->value] ?? null; | ||||
|  | ||||
|         match ($emailPreference) { | ||||
|             'immediate-email' => $this->scheduleImmediateEmail($notification, $addressee), | ||||
|             'daily-email' => $this->scheduleDailyEmail($notification, $addressee), | ||||
|             default => $this->logger->debug('[NotificationMailer] No email preference set for notification type', [ | ||||
|                 'notification_id' => $notification->getId(), | ||||
|                 'addressee_email' => $addressee->getEmail(), | ||||
|                 'notification_type' => $notificationType->value, | ||||
|                 'preference' => $emailPreference, | ||||
|             ]), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     private function scheduleImmediateEmail(Notification $notification, $addressee): void | ||||
|     { | ||||
|         $message = new SendImmediateNotificationEmailMessage( | ||||
|             $notification->getId(), | ||||
|             $addressee->getId() | ||||
|         ); | ||||
|  | ||||
|         $this->messageBus->dispatch($message); | ||||
|  | ||||
|         $this->logger->info('[NotificationMailer] Scheduled immediate email', [ | ||||
|             'notification_id' => $notification->getId(), | ||||
|             'addressee_email' => $addressee->getEmail(), | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     private function scheduleDailyEmail(Notification $notification, $addressee): void | ||||
|     { | ||||
|         $message = new ScheduleDailyNotificationEmailMessage( | ||||
|             $notification->getId(), | ||||
|             $addressee->getId() | ||||
|         ); | ||||
|  | ||||
|         $this->messageBus->dispatch($message); | ||||
|  | ||||
|         $this->logger->info('[NotificationMailer] Scheduled daily email', [ | ||||
|             'notification_id' => $notification->getId(), | ||||
|             'addressee_email' => $addressee->getEmail(), | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This method sends the email but is now called by the immediate notification email message handler. | ||||
|      * | ||||
|      * @throws TransportExceptionInterface | ||||
|      */ | ||||
|     public function sendEmailToAddressee(Notification $notification, $addressee): void | ||||
|     { | ||||
|         if (null === $addressee->getEmail()) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if ($notification->isSystem()) { | ||||
|             $email = new Email(); | ||||
|             $email->text($notification->getMessage()); | ||||
|         } else { | ||||
|             $email = new TemplatedEmail(); | ||||
|             $email | ||||
|                 ->subject($notification->getTitle()) | ||||
|                 ->to($addressee->getEmail()); | ||||
|  | ||||
|             try { | ||||
|                 $this->mailer->send($email); | ||||
|             } catch (TransportExceptionInterface $e) { | ||||
|                 $this->logger->warning('[NotificationMailer] could not send an email notification', [ | ||||
|                     'to' => $addressee->getEmail(), | ||||
|                     'error_message' => $e->getMessage(), | ||||
|                     'error_trace' => $e->getTraceAsString(), | ||||
|                 ->textTemplate('@ChillMain/Notification/email_non_system_notification_content.fr.md.twig') | ||||
|                 ->context([ | ||||
|                     'notification' => $notification, | ||||
|                     'dest' => $addressee, | ||||
|                 ]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $email | ||||
|             ->subject($notification->getTitle()) | ||||
|             ->to($addressee->getEmail()); | ||||
|  | ||||
|         try { | ||||
|             $this->mailer->send($email); | ||||
|             $this->logger->info('[NotificationMailer] Email sent successfully', [ | ||||
|                 'notification_id' => $notification->getId(), | ||||
|                 'addressee_email' => $addressee->getEmail(), | ||||
|             ]); | ||||
|         } catch (TransportExceptionInterface $e) { | ||||
|             $this->logger->warning('[NotificationMailer] Could not send an email notification', [ | ||||
|                 'to' => $addressee->getEmail(), | ||||
|                 'notification_id' => $notification->getId(), | ||||
|                 'error_message' => $e->getMessage(), | ||||
|                 'error_trace' => $e->getTraceAsString(), | ||||
|             ]); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Send daily digest email with multiple notifications to a user. | ||||
|      * @throws TransportExceptionInterface | ||||
|      */ | ||||
|     public function sendDailyDigest($user, array $notifications): void | ||||
|     { | ||||
|         if (null === $user->getEmail() || empty($notifications)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $email = new TemplatedEmail(); | ||||
|         $email | ||||
|             ->textTemplate('@ChillMain/Notification/email_daily_digest.fr.md.twig') | ||||
|             ->context([ | ||||
|                 'user' => $user, | ||||
|                 'notifications' => $notifications, | ||||
|                 'notification_count' => count($notifications), | ||||
|             ]) | ||||
|             ->subject($this->translator->trans('notification.Daily Notification Digest')) | ||||
|             ->to($user->getEmail()); | ||||
|  | ||||
|         try { | ||||
|             $this->mailer->send($email); | ||||
|             $this->logger->info('[NotificationMailer] Daily digest email sent successfully', [ | ||||
|                 'user_email' => $user->getEmail(), | ||||
|                 'notification_count' => count($notifications), | ||||
|             ]); | ||||
|         } catch (TransportExceptionInterface $e) { | ||||
|             $this->logger->warning('[NotificationMailer] Could not send daily digest email', [ | ||||
|                 'to' => $user->getEmail(), | ||||
|                 'notification_count' => count($notifications), | ||||
|                 'error_message' => $e->getMessage(), | ||||
|                 'error_trace' => $e->getTraceAsString(), | ||||
|             ]); | ||||
|             throw $e; // Re-throw so the message handler can handle the failure | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class AccompanyingCourseNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::ACC_COURSE->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.acc-course'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class AccompanyingCourseWorkEvalDocNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::ACC_COURSE_WORK_EVAL_DOC->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.acc-course-work-eval-doc'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class AccompanyingCourseWorkNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::ACC_COURSE_WORK->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.acc-course-work'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class ActivityNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::ACTIVITY->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.activity'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class DesignatedReferrerNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::REFERRER_ACC_COURSE->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.referrer-acc-course'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
| use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; | ||||
|  | ||||
| #[AutoconfigureTag('chill_main.notification_flag_provider')] | ||||
| interface NotificationFlagProviderInterface | ||||
| { | ||||
|     public function getFlag(): string; | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface; | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class PersonAddressMoveNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::PERSON_MOVE->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.person-address-move'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\MainBundle\Notification\FlagProviders; | ||||
|  | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Symfony\Component\Translation\TranslatableMessage; | ||||
| use Symfony\Contracts\Translation\TranslatableInterface; | ||||
|  | ||||
| class WorkflowTransitionNotificationFlagProvider implements NotificationFlagProviderInterface | ||||
| { | ||||
|  | ||||
|     public function getFlag(): string | ||||
|     { | ||||
|         return NotificationFlagEnum::WORKFLOW_TRANS->value; | ||||
|     } | ||||
|  | ||||
|     public function getLabel(): TranslatableInterface | ||||
|     { | ||||
|         return new TranslatableMessage('notification.flags.workflow-trans'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Notification; | ||||
|  | ||||
| use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface; | ||||
|  | ||||
| final readonly class NotificationFlagManager | ||||
| { | ||||
|     /** | ||||
|      * @var array<NotificationFlagProviderInterface> | ||||
|      */ | ||||
|     private array $notificationFlagProviders; | ||||
|  | ||||
|     public function __construct( | ||||
|         iterable $notificationFlagProviders, | ||||
|     ) { | ||||
|         $this->notificationFlagProviders = iterator_to_array($notificationFlagProviders); | ||||
|     } | ||||
|  | ||||
|     public function getAllNotificationFlagProviders(): array | ||||
|     { | ||||
|         return $this->notificationFlagProviders; | ||||
|     } | ||||
|  | ||||
|     public function getNotificationFlagProviderByLabel(string $label): ?NotificationFlagProviderInterface | ||||
|     { | ||||
|         foreach ($this->notificationFlagProviders as $provider) { | ||||
|             if ($provider->getLabel() == $label) { | ||||
|                 return $provider; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| # Résumé quotidien des notifications | ||||
|  | ||||
| Bonjour {{ user.name ?? user.email }}, | ||||
|  | ||||
| Voici vos {{ notification_count }} notification{% if notification_count > 1 %}s{% endif %} du jour : | ||||
|  | ||||
| {% for notification in notifications %} | ||||
| ## {{ notification.title }} | ||||
|  | ||||
| {{ notification.message }} | ||||
|  | ||||
| Vous pouvez visualiser la notification et y répondre ici: | ||||
|  | ||||
| {{ absolute_url(path('chill_main_notification_show', {'_locale': 'fr', 'id': notification.id }, false)) }} | ||||
| {% endfor %} | ||||
|  | ||||
| -- | ||||
| Le logiciel Chill | ||||
| @@ -45,6 +45,32 @@ | ||||
|             {{ form_start(form) }} | ||||
|             {{ form_row(form.phonenumber) }} | ||||
|  | ||||
|             <h2 class="mb-4">{{ 'user.profile.notification_preferences'|trans }}</h2> | ||||
|             <table class="table table-bordered border-dark align-middle"> | ||||
|                 <thead> | ||||
|                 <tr> | ||||
|                     <th>{{ 'notification.flags.type'|trans }}</th> | ||||
|                     <th>{{ 'notification.flags.preferences.immediate_email'|trans }}</th> | ||||
|                     <th>{{ 'notification.flags.preferences.daily_email'|trans }}</th> | ||||
|                 </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 {% for flag in form.notificationFlags %} | ||||
|                     <tr> | ||||
|                         <td class="col-sm-6"> | ||||
|                             <label>{{ form_label(flag) }}</label> | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{ form_widget(flag.immediate_email) }} | ||||
|                         </td> | ||||
|                         <td> | ||||
|                             {{ form_widget(flag.daily_email) }} | ||||
|                         </td> | ||||
|                     </tr> | ||||
|                 {% endfor %} | ||||
|                 </tbody> | ||||
|             </table> | ||||
|  | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     {{ form_widget(form.submit, { 'attr': { 'class': 'btn btn-save' } } ) }} | ||||
|   | ||||
| @@ -12,6 +12,7 @@ declare(strict_types=1); | ||||
| namespace Chill\MainBundle\Workflow\EventSubscriber; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Entity\UserGroup; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| @@ -125,7 +126,8 @@ class NotificationOnTransition implements EventSubscriberInterface | ||||
|                 ->setRelatedEntityClass(EntityWorkflow::class) | ||||
|                 ->setTitle($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context)) | ||||
|                 ->setMessage($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig', $context)) | ||||
|                 ->addAddressee($subscriber); | ||||
|                 ->addAddressee($subscriber) | ||||
|                 ->setType(NotificationFlagEnum::WORKFLOW_TRANS); | ||||
|             $this->entityManager->persist($notification); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -139,6 +139,11 @@ services: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|  | ||||
|     Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|  | ||||
|     Chill\MainBundle\Form\UserProfileType: ~ | ||||
|     Chill\MainBundle\Form\AbsenceType: ~ | ||||
|     Chill\MainBundle\Form\DataMapper\RegroupmentDataMapper: ~ | ||||
|     Chill\MainBundle\Form\RegroupmentType: ~ | ||||
|   | ||||
| @@ -12,6 +12,10 @@ services: | ||||
|         arguments: | ||||
|             $routeParameters: '%chill_main.notifications%' | ||||
|  | ||||
|     Chill\MainBundle\Notification\NotificationFlagManager: | ||||
|         arguments: | ||||
|             $notificationFlagProviders: !tagged_iterator chill_main.notification_flag_provider | ||||
|  | ||||
|     Chill\MainBundle\Notification\NotificationHandlerManager: | ||||
|         arguments: | ||||
|             $handlers: !tagged_iterator chill_main.notification_handler | ||||
|   | ||||
| @@ -0,0 +1,29 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\Migrations\Main; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
| final class Version20250610102953 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add notification flags property to User'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE users ADD notificationFlags JSONB DEFAULT '[]' NOT NULL | ||||
|         SQL); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE users DROP notificationFlags | ||||
|         SQL); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\Migrations\Main; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| final class Version20250618115938 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add type property to notifications'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_main_notification ADD type VARCHAR(255) | ||||
|         SQL); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql(<<<'SQL' | ||||
|             ALTER TABLE chill_main_notification DROP type | ||||
|         SQL); | ||||
|     } | ||||
| } | ||||
| @@ -51,9 +51,10 @@ Label: Nom | ||||
| user: | ||||
|     profile: | ||||
|         title: Mon profil | ||||
|         Phonenumber successfully updated!: Numéro de téléphone mis à jour! | ||||
|         Profile successfully updated!: Votre profil a été mis à jour! | ||||
|         no job: Pas de métier assigné | ||||
|         no scope: Pas de cercle assigné | ||||
|         notification_preferences: Préférences pour mes notifications | ||||
|  | ||||
| user_group: | ||||
|     inactive: Inactif | ||||
| @@ -715,6 +716,21 @@ notification: | ||||
|     mark_as_read: Marquer comme lu | ||||
|     mark_as_unread: Marquer comme non-lu | ||||
|  | ||||
|     flags: | ||||
|         type: Type de notification | ||||
|         referrer-acc-course: Notification lors de la désignation comme référent | ||||
|         acc-course-work-eval-doc: Notification sur un document d'évaluation | ||||
|         acc-course-work: Notification sur un action d'accompagnement | ||||
|         activity: Notification sur un échange | ||||
|         acc-course: Notification sur un parcours d'accompagnement | ||||
|         person-address-move: Notification lors que l'usager qui localise un parcours a déménagé | ||||
|         person: Notification sur un usager | ||||
|         workflow-trans: Notification sur une transition d'un workflow | ||||
|         preferences: | ||||
|             immediate_email: Recevoir un email immédiatement | ||||
|             daily_email: Recevoir un récapitulatif quotidien | ||||
|  | ||||
|  | ||||
|  | ||||
| export: | ||||
|     address_helper: | ||||
|   | ||||
| @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\AccompanyingPeriod\Events; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Address; | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Chill\MainBundle\Notification\NotificationPersisterInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; | ||||
| @@ -65,7 +66,8 @@ class PersonAddressMoveEventSubscriber implements EventSubscriberInterface | ||||
|                         ->setMessage($this->engine->render('@ChillPerson/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig', [ | ||||
|                             'oldPersonLocation' => $person, | ||||
|                             'period' => $period, | ||||
|                         ])); | ||||
|                         ])) | ||||
|                         ->setType(NotificationFlagEnum::PERSON_MOVE); | ||||
|  | ||||
|                     $this->notificationPersister->persist($notification); | ||||
|                 } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ declare(strict_types=1); | ||||
| namespace Chill\PersonBundle\AccompanyingPeriod\Events; | ||||
|  | ||||
| use Chill\MainBundle\Entity\Notification; | ||||
| use Chill\MainBundle\Entity\NotificationFlagEnum; | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Notification\NotificationPersisterInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| @@ -73,7 +74,8 @@ class UserRefEventSubscriber implements EventSubscriberInterface | ||||
|                     'accompanyingCourse' => $period, | ||||
|                 ] | ||||
|             )) | ||||
|             ->addAddressee($period->getUser()); | ||||
|             ->addAddressee($period->getUser()) | ||||
|             ->setType(NotificationFlagEnum::REFERRER_ACC_COURSE); | ||||
|  | ||||
|         $this->notificationPersister->persist($notification); | ||||
|     } | ||||
|   | ||||
| @@ -14,6 +14,15 @@ | ||||
|             "config/routes/annotations.yaml" | ||||
|         ] | ||||
|     }, | ||||
|     "doctrine/deprecations": { | ||||
|         "version": "1.1", | ||||
|         "recipe": { | ||||
|             "repo": "github.com/symfony/recipes", | ||||
|             "branch": "main", | ||||
|             "version": "1.0", | ||||
|             "ref": "87424683adc81d7dc305eefec1fced883084aab9" | ||||
|         } | ||||
|     }, | ||||
|     "doctrine/doctrine-bundle": { | ||||
|         "version": "2.13", | ||||
|         "recipe": { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user