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 %}