diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 2f891dc79..809607c02 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -7,6 +7,7 @@ doctrine: mapping_types: user_type: string citext: citext + enumApplicationStatus: string # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/migrations/Version20241124155724.php b/migrations/Version20241124155724.php new file mode 100644 index 000000000..d97292c91 --- /dev/null +++ b/migrations/Version20241124155724.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE notification ADD new_user_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CA7C2D807B FOREIGN KEY (new_user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_BF5476CA7C2D807B ON notification (new_user_id)'); + $this->addSql('ALTER TABLE "user" ADD notify_on_user_signup BOOLEAN DEFAULT TRUE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CA7C2D807B'); + $this->addSql('DROP INDEX IDX_BF5476CA7C2D807B'); + $this->addSql('ALTER TABLE notification DROP new_user_id'); + $this->addSql('ALTER TABLE "user" DROP notify_on_user_signup'); + } +} diff --git a/src/DTO/UserSettingsDto.php b/src/DTO/UserSettingsDto.php index b986907c9..16c9f3d01 100644 --- a/src/DTO/UserSettingsDto.php +++ b/src/DTO/UserSettingsDto.php @@ -29,7 +29,8 @@ public function __construct( #[OA\Property(type: 'array', items: new OA\Items(type: 'string'))] public ?array $preferredLanguages = null, public ?string $customCss = null, - public ?bool $ignoreMagazinesCustomCss = null + public ?bool $ignoreMagazinesCustomCss = null, + public ?bool $notifyOnUserSignup = null, ) { } @@ -52,6 +53,7 @@ public function jsonSerialize(): mixed 'preferredLanguages' => $this->preferredLanguages, 'customCss' => $this->customCss, 'ignoreMagazinesCustomCss' => $this->ignoreMagazinesCustomCss, + 'notifyOnUserSignup' => $this->notifyOnUserSignup, ]; } diff --git a/src/Entity/NewSignupNotification.php b/src/Entity/NewSignupNotification.php new file mode 100644 index 000000000..f6ac16545 --- /dev/null +++ b/src/Entity/NewSignupNotification.php @@ -0,0 +1,41 @@ +newUser; + } + + public function getMessage(TranslatorInterface $trans, string $locale, UrlGeneratorInterface $urlGenerator): PushNotification + { + $message = str_replace('%u%', $this->newUser->username, $trans->trans('notification_body_new_signup', locale: $locale)); + $title = $trans->trans('notification_title_new_signup', locale: $locale); + $url = $urlGenerator->generate('user_overview', ['username' => $this->newUser->username]); + $slash = $this->newUser->avatar && !str_starts_with('/', $this->newUser->avatar->filePath) ? '/' : ''; + $avatarUrl = $this->newUser->avatar ? '/media/cache/resolve/avatar_thumb'.$slash.$this->newUser->avatar->filePath : null; + + return new PushNotification($message, $title, actionUrl: $url, avatarUrl: $avatarUrl); + } +} diff --git a/src/Entity/Notification.php b/src/Entity/Notification.php index 27a815628..2729531d2 100644 --- a/src/Entity/Notification.php +++ b/src/Entity/Notification.php @@ -45,6 +45,7 @@ 'report_created' => 'ReportCreatedNotification', 'report_approved' => 'ReportApprovedNotification', 'report_rejected' => 'ReportRejectedNotification', + 'new_signup' => 'NewSignupNotification', ])] abstract class Notification { diff --git a/src/Entity/User.php b/src/Entity/User.php index 010988427..38054ec2f 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -152,6 +152,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil public bool $notifyOnNewPostReply = true; #[Column(type: 'boolean', nullable: false)] public bool $notifyOnNewPostCommentReply = true; + #[Column(type: 'boolean', nullable: false, options: ['default' => true])] + public bool $notifyOnUserSignup = true; #[Column(type: 'boolean', nullable: false, options: ['default' => false])] public bool $addMentionsEntries = false; #[Column(type: 'boolean', nullable: false, options: ['default' => true])] diff --git a/src/Form/UserSettingsType.php b/src/Form/UserSettingsType.php index 4901ea774..225d75276 100644 --- a/src/Form/UserSettingsType.php +++ b/src/Form/UserSettingsType.php @@ -7,6 +7,7 @@ use App\DTO\UserSettingsDto; use App\Entity\User; use App\Form\DataTransformer\FeaturedMagazinesBarTransformer; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -19,8 +20,10 @@ class UserSettingsType extends AbstractType { - public function __construct(private readonly TranslatorInterface $translator) - { + public function __construct( + private readonly TranslatorInterface $translator, + private readonly Security $security, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -109,6 +112,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ) ->add('submit', SubmitType::class); + /** @var User $user */ + $user = $this->security->getUser(); + if ($user->isAdmin()) { + $builder->add('notifyOnUserSignup', CheckboxType::class, ['required' => false]); + } + $builder->get('featuredMagazines')->addModelTransformer( new FeaturedMagazinesBarTransformer() ); diff --git a/src/Message/Notification/SentNewSignupNotificationMessage.php b/src/Message/Notification/SentNewSignupNotificationMessage.php new file mode 100644 index 000000000..7b8be5164 --- /dev/null +++ b/src/Message/Notification/SentNewSignupNotificationMessage.php @@ -0,0 +1,14 @@ +workWrapper($message); + } + + public function doWork(MessageInterface $message): void + { + if (!($message instanceof SentNewSignupNotificationMessage)) { + throw new \LogicException(); + } + $user = $this->userRepository->findOneBy(['id' => $message->userId]); + if (!$user) { + throw new UnrecoverableMessageHandlingException('user not found'); + } + $this->signupNotificationManager->sendNewSignupNotification($user); + } +} diff --git a/src/Service/Notification/SignupNotificationManager.php b/src/Service/Notification/SignupNotificationManager.php new file mode 100644 index 000000000..8b58b794b --- /dev/null +++ b/src/Service/Notification/SignupNotificationManager.php @@ -0,0 +1,37 @@ +userRepository->findAllAdmins(); + foreach ($receivers as $receiver) { + if (!$receiver->notifyOnUserSignup) { + continue; + } + $notification = new NewSignupNotification($receiver); + $notification->newUser = $newUser; + $this->entityManager->persist($notification); + $this->dispatcher->dispatch(new NotificationCreatedEvent($notification)); + } + $this->entityManager->flush(); + } +} diff --git a/src/Service/UserManager.php b/src/Service/UserManager.php index 2ba9d9ef3..0381ad0c7 100644 --- a/src/Service/UserManager.php +++ b/src/Service/UserManager.php @@ -19,6 +19,7 @@ use App\Message\ClearDeletedUserMessage; use App\Message\DeleteImageMessage; use App\Message\DeleteUserMessage; +use App\Message\Notification\SentNewSignupNotificationMessage; use App\Message\UserCreatedMessage; use App\Message\UserUpdatedMessage; use App\MessageHandler\ClearDeletedUserHandler; @@ -173,10 +174,17 @@ public function create(UserDto $dto, bool $verifyUserEmail = true, $rateLimit = $this->entityManager->persist($user); $this->entityManager->flush(); + if (!$dto->apId) { + try { + $this->bus->dispatch(new SentNewSignupNotificationMessage($user->getId())); + } catch (\Throwable $e) { + } + } + if ($verifyUserEmail) { try { $this->bus->dispatch(new UserCreatedMessage($user->getId())); - } catch (\Exception $e) { + } catch (\Throwable $e) { } } diff --git a/src/Service/UserSettingsManager.php b/src/Service/UserSettingsManager.php index a632ce6fc..574bdb9e5 100644 --- a/src/Service/UserSettingsManager.php +++ b/src/Service/UserSettingsManager.php @@ -32,7 +32,8 @@ public function createDto(User $user): UserSettingsDto $user->featuredMagazines, $user->preferredLanguages, $user->customCss, - $user->ignoreMagazinesCustomCss + $user->ignoreMagazinesCustomCss, + $user->notifyOnUserSignup, ); } @@ -55,6 +56,10 @@ public function update(User $user, UserSettingsDto $dto): void $user->customCss = $dto->customCss; $user->ignoreMagazinesCustomCss = $dto->ignoreMagazinesCustomCss; + if (null !== $dto->notifyOnUserSignup) { + $user->notifyOnUserSignup = $dto->notifyOnUserSignup; + } + $this->entityManager->flush(); } } diff --git a/templates/notifications/_blocks.html.twig b/templates/notifications/_blocks.html.twig index 4e9a157e4..7e597bba9 100644 --- a/templates/notifications/_blocks.html.twig +++ b/templates/notifications/_blocks.html.twig @@ -156,3 +156,8 @@ {{ 'open_report'|trans }} {% endif %} {% endblock report_approved_notification %} + +{% block new_signup %} + {{ 'notification_title_new_signup'|trans }}
+ {{ component('user_inline', { user: notification.newUser } ) }} +{% endblock %} diff --git a/templates/user/settings/general.html.twig b/templates/user/settings/general.html.twig index 5af246fa0..701aedb8a 100644 --- a/templates/user/settings/general.html.twig +++ b/templates/user/settings/general.html.twig @@ -41,6 +41,9 @@ {{ form_row(form.notifyOnNewPostCommentReply, {label: 'notify_on_new_post_comment_reply', row_attr: {class: 'checkbox'}}) }} {{ form_row(form.notifyOnNewEntry, {label: 'notify_on_new_entry', row_attr: {class: 'checkbox'}}) }} {{ form_row(form.notifyOnNewPost, {label: 'notify_on_new_posts', row_attr: {class: 'checkbox'}}) }} + {% if app.user.admin %} + {{ form_row(form.notifyOnUserSignup, {label: 'notify_on_user_signup', row_attr: {class: 'checkbox'}}) }} + {% endif %}
{{ form_row(form.submit, {label: 'save', attr: {class: 'btn btn__primary'}}) }}
diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index a5952aad2..649a9e559 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -214,6 +214,7 @@ notify_on_new_post_comment_reply: Replies to my comments on any posts notify_on_new_entry: New threads (links or articles) in any magazine to which I'm subscribed notify_on_new_posts: New posts in any magazine to which I'm subscribed +notify_on_user_signup: New signups save: Save about: About old_email: Current email @@ -875,6 +876,9 @@ notification_title_message: New direct message notification_title_new_post: New Post notification_title_removed_post: A post was removed notification_title_edited_post: A post was edited +notification_title_new_signup: A new user registered +notification_body_new_signup: The user %u% registered. +notification_body2_new_signup_approval: You need to approve the request before they can log in show_related_magazines: Show random magazines show_related_entries: Show random threads show_related_posts: Show random posts