From 6fb3ed597b9760029b06abb0fa1cf3600190882b Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 29 Aug 2026 11:11:06 +0200 Subject: [PATCH] Add contact form with reCAPTCHA, saved to DB, viewable in admin Adds a public /contact page (name/email/message, protected by the same reCAPTCHA v3 setup used on registration) that persists submissions to a new contact_message table, plus a Contact section in the admin panel to read them. Linked from the site footer. --- migrations/Version20260829120000.php | 26 ++++++ .../Controller/GameAdminContactController.php | 24 +++++ src/Website/Controller/ContactController.php | 34 ++++++++ src/Website/Entity/ContactMessage.php | 87 +++++++++++++++++++ src/Website/Form/ContactFormType.php | 53 +++++++++++ .../Repository/ContactMessageRepository.php | 18 ++++ templates/game/admin/base.html.twig | 1 + templates/game/admin/contact/index.html.twig | 39 +++++++++ templates/layout/site.html.twig | 2 + templates/website/contact.html.twig | 31 +++++++ translations/messages.en.yaml | 1 + translations/messages.nl.yaml | 1 + 12 files changed, 317 insertions(+) create mode 100644 migrations/Version20260829120000.php create mode 100644 src/Game/Controller/GameAdminContactController.php create mode 100644 src/Website/Controller/ContactController.php create mode 100644 src/Website/Entity/ContactMessage.php create mode 100644 src/Website/Form/ContactFormType.php create mode 100644 src/Website/Repository/ContactMessageRepository.php create mode 100644 templates/game/admin/contact/index.html.twig create mode 100644 templates/website/contact.html.twig diff --git a/migrations/Version20260829120000.php b/migrations/Version20260829120000.php new file mode 100644 index 0000000..116e9b2 --- /dev/null +++ b/migrations/Version20260829120000.php @@ -0,0 +1,26 @@ +addSql('CREATE TABLE contact_message (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE contact_message'); + } +} diff --git a/src/Game/Controller/GameAdminContactController.php b/src/Game/Controller/GameAdminContactController.php new file mode 100644 index 0000000..69b7252 --- /dev/null +++ b/src/Game/Controller/GameAdminContactController.php @@ -0,0 +1,24 @@ +render('game/admin/contact/index.html.twig', [ + 'messages' => $contactMessageRepository->findBy([], ['createdAt' => 'DESC']), + ]); + } +} diff --git a/src/Website/Controller/ContactController.php b/src/Website/Controller/ContactController.php new file mode 100644 index 0000000..5ffafd8 --- /dev/null +++ b/src/Website/Controller/ContactController.php @@ -0,0 +1,34 @@ +createForm(ContactFormType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($form->getData()); + $entityManager->flush(); + + $this->addFlash('success', 'Thanks for reaching out — your message has been sent.'); + + return $this->redirectToRoute('website_contact'); + } + + return $this->render('website/contact.html.twig', [ + 'contactForm' => $form->createView(), + ]); + } +} diff --git a/src/Website/Entity/ContactMessage.php b/src/Website/Entity/ContactMessage.php new file mode 100644 index 0000000..1aca114 --- /dev/null +++ b/src/Website/Entity/ContactMessage.php @@ -0,0 +1,87 @@ +createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function setMessage(string $message): static + { + $this->message = $message; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } +} diff --git a/src/Website/Form/ContactFormType.php b/src/Website/Form/ContactFormType.php new file mode 100644 index 0000000..350e1a3 --- /dev/null +++ b/src/Website/Form/ContactFormType.php @@ -0,0 +1,53 @@ +add('name', TextType::class, [ + 'constraints' => [ + new NotBlank(message: 'Please enter your name'), + new Length(max: 255, maxMessage: 'Your name cannot be longer than {{ limit }} characters'), + ], + ]) + ->add('email', EmailType::class, [ + 'constraints' => [ + new NotBlank(message: 'Please enter your email address'), + ], + ]) + ->add('message', TextareaType::class, [ + 'attr' => ['rows' => 6], + 'constraints' => [ + new NotBlank(message: 'Please enter a message'), + new Length(max: 5000, maxMessage: 'Your message cannot be longer than {{ limit }} characters'), + ], + ]) + ->add('captcha', Recaptcha3Type::class, [ + 'constraints' => new Recaptcha3(), + 'action_name' => 'contact', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ContactMessage::class, + ]); + } +} diff --git a/src/Website/Repository/ContactMessageRepository.php b/src/Website/Repository/ContactMessageRepository.php new file mode 100644 index 0000000..2634367 --- /dev/null +++ b/src/Website/Repository/ContactMessageRepository.php @@ -0,0 +1,18 @@ + + */ +class ContactMessageRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ContactMessage::class); + } +} diff --git a/templates/game/admin/base.html.twig b/templates/game/admin/base.html.twig index fa89963..08d1266 100644 --- a/templates/game/admin/base.html.twig +++ b/templates/game/admin/base.html.twig @@ -136,6 +136,7 @@ { route: 'game_admin_games', label: 'Games', icon: '🎮' }, { route: 'game_admin_sessions', label: 'Sessions', icon: '▶' }, { route: 'game_admin_feedback', label: 'Feedback', icon: '💬' }, + { route: 'game_admin_contact', label: 'Contact', icon: '📨' }, { route: 'game_admin_email_log', label: 'Email Log', icon: '✉' }, ] %} diff --git a/templates/game/admin/contact/index.html.twig b/templates/game/admin/contact/index.html.twig new file mode 100644 index 0000000..504dd69 --- /dev/null +++ b/templates/game/admin/contact/index.html.twig @@ -0,0 +1,39 @@ +{% extends 'game/admin/base.html.twig' %} + +{% block title %}Contact Messages — Admin{% endblock %} + +{% block admin_body %} +
+

Contact Messages

+ {{ messages|length }} message{{ messages|length != 1 ? 's' : '' }} +
+ +
+ + + + + + + + + + + {% for entry in messages %} + + + + + + + {% else %} + + + + {% endfor %} + +
ReceivedNameEmailMessage
{{ entry.createdAt|date('Y-m-d H:i:s') }}{{ entry.name }} + {{ entry.email }} + {{ entry.message }}
No messages yet.
+
+{% endblock %} diff --git a/templates/layout/site.html.twig b/templates/layout/site.html.twig index 79a5730..c88c628 100644 --- a/templates/layout/site.html.twig +++ b/templates/layout/site.html.twig @@ -67,6 +67,8 @@